diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 40d4f46..676b025 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,6 +3,12 @@ fail_fast: true
repos:
- repo: local
hooks:
+ - id: doctests
+ name: Run the xdoctests in the source files
+ entry: poetry run nox -s doctests --
+ language: system
+ stages: [commit, merge-commit]
+ types: [python]
- id: fix-branch-references
name: Check for wrong branch references
entry: poetry run nox -s fix-branch-references --
diff --git a/00_intro/00_content.ipynb b/00_intro/00_content.ipynb
index bc62059..2bc24f2 100644
--- a/00_intro/00_content.ipynb
+++ b/00_intro/00_content.ipynb
@@ -775,7 +775,7 @@
" - *Chapter 9*: [Mappings & Sets ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/00_content.ipynb)\n",
" - *Chapter 10*: Arrays & Dataframes\n",
"- How can we create custom data types?\n",
- " - *Chapter 11*: Classes & Instances"
+ " - *Chapter 11*: [Classes & Instances ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb)"
]
},
{
diff --git a/02_functions/02_content.ipynb b/02_functions/02_content.ipynb
index 06f1765..5162e9c 100644
--- a/02_functions/02_content.ipynb
+++ b/02_functions/02_content.ipynb
@@ -1571,7 +1571,7 @@
}
},
"source": [
- "Packages are a generalization of modules, and we look at one in detail in [Chapter 11 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb). You may, however, already look at a [sample package ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\n",
+ "Packages are a generalization of modules, and we look at one in detail in [Chapter 11 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/04_content.ipynb#Packages-vs.-Modules).\n",
"\n",
"As a further reading on modules and packages, we refer to the [official tutorial ](https://docs.python.org/3/tutorial/modules.html)."
]
diff --git a/11_classes/00_content.ipynb b/11_classes/00_content.ipynb
new file mode 100644
index 0000000..34e3df3
--- /dev/null
+++ b/11_classes/00_content.ipynb
@@ -0,0 +1,2146 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/00_content.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Chapter 11: Classes & Instances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In contrast to all the built-in data types introduced in the previous chapters, **classes** allow us to create **user-defined data types**. They enable us to model **data** and its **associated behavior** in an *abstract* way. *Concrete* **instances** of these custom data types then **encapsulate** the **state** in a running program. Often, classes are **blueprints** modeling \"real world things.\"\n",
+ "\n",
+ "Classes and instances follow the **[object-oriented programming ](https://en.wikipedia.org/wiki/Object-oriented_programming)** (OOP) paradigm where a *large program* is broken down into many *small components* (i.e., the objects) that *reuse* code. This way, a program that is too big for a programmer to fully comprehend as a whole becomes maintainable via its easier to understand individual pieces.\n",
+ "\n",
+ "Often, we see the terminology \"classes & objects\" used instead of \"classes & instances\" in Python related texts. In this book, we are more precise as *both* classes and instances are objects as specified already in the \"*Objects vs. Types vs. Values*\" section in [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/01_elements/00_content.ipynb#Objects-vs.-Types-vs.-Values)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Example: Vectors & Matrices"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Neither core Python nor the standard library offer an implementation of common [linear algebra ](https://en.wikipedia.org/wiki/Linear_algebra) functionalities. While we introduce the popular third-party library [numpy](http://www.numpy.org/) in [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/10_arrarys/00_content.ipynb) as the de-facto standard for that and recommend to use it in real-life projects, we show how one could use Python's object-oriented language features to implement common matrix and vector operations throughout this chapter. Once we have achieved that, we compare our own library with [numpy](http://www.numpy.org/).\n",
+ "\n",
+ "Without classes, we could model a vector, for example, with a `tuple` or a `list` object, depending on if we want it to be mutable or not.\n",
+ "\n",
+ "Let's take the following vector $\\vec{x}$ as an example and model it as a `tuple`:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "$\\vec{x} = \\begin{pmatrix} 1 \\\\ 2 \\\\ 3 \\end{pmatrix}$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x = (1, 2, 3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, 2, 3)"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We can extend this approach and model a matrix as either a `tuple` holding other `tuple`s or as a `list` holding other `list`s or as a mixture of both. Then, we *must* decide if the inner objects represent rows or columns. A common convention is to go with the former.\n",
+ "\n",
+ "For example, let's model the matrix $\\bf{A}$ below as a `list` of row `list`s:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "$\\bf{A} = \\begin{bmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \\end{bmatrix}$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[[1, 2, 3], [4, 5, 6], [7, 8, 9]]"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "While this way of representing vectors and matrices in memory keeps things simple, we cannot work with them easily as Python does not know about the **semantics** (i.e., \"rules\") of vectors and matrices modeled as `tuple`s and `list`s of `list`s.\n",
+ "\n",
+ "For example, we should be able to multiply $\\bf{A}$ with $\\vec{x}$ if their dimensions match. However, Python does not know how to do this and raises a `TypeError`."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "$\\bf{A} * \\vec{x} = \\begin{bmatrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \\end{bmatrix} * \\begin{pmatrix} 1 \\\\ 2 \\\\ 3 \\end{pmatrix} = \\begin{pmatrix} 14 \\\\ 32 \\\\ 50 \\end{pmatrix}$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "can't multiply sequence by non-int of type 'tuple'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mA\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: can't multiply sequence by non-int of type 'tuple'"
+ ]
+ }
+ ],
+ "source": [
+ "A * x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Throughout this chapter, we \"teach\" Python the rules of [linear algebra ](https://en.wikipedia.org/wiki/Linear_algebra)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Class Definition"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The compound `class` statement creates a new variable that references a **class object** in memory.\n",
+ "\n",
+ "Following the *header* line, the indented *body* syntactically consists of function definitions (i.e., `.dummy_method()`) and variable assignments (i.e., `.dummy_variable`). Any code put here is executed just as if it were outside the `class` statement. However, the class object acts as a **namespace**, meaning that all the names do *not* exist in the global scope but may only be accessed with the dot operator `.` on the class object. In this context, the names are called **class attributes**.\n",
+ "\n",
+ "Within classes, functions are referred to as **methods** that are **bound** to *future* **instance objects**. This binding process means that Python *implicitly* inserts a reference to a *concrete* instance object as the first argument to any **method invocation** (i.e., \"function call\"). By convention, we call this parameter `self` as it references the instance object on which the method is invoked. Then, as the method is executed, we can set and access attributes via the dot operator `.` on `self`. That is how we manage the *state* of a *concrete* instance within a *generically* written class. At the same time, the code within a method is reused whenever we invoke a method on *any* instance.\n",
+ "\n",
+ "As indicated by [PEP 257 ](https://www.python.org/dev/peps/pep-0257/) and also section 3.8.4 of the [Google Python Style Guide ](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#384-classes), we use docstrings to document relevant parts of the new data type. With respect to naming, classes are named according to the [CamelCase ](https://en.wikipedia.org/wiki/Camel_case) convention while instances are treated like normal variables and named in [snake\\_case ](https://en.wikipedia.org/wiki/Snake_case)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "code_folding": [],
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ " \"\"\"A one-dimensional vector from linear algebra.\"\"\"\n",
+ "\n",
+ " dummy_variable = \"I am a vector\"\n",
+ "\n",
+ " def dummy_method(self):\n",
+ " \"\"\"A dummy method for illustration purposes.\"\"\"\n",
+ " return self.dummy_variable"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`Vector` is an object on its own with an identity, a type, and a value."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "94113690586816"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "id(Vector)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Its type is `type` indicating that it represents a user-defined data type and it evaluates to its fully qualified name (i.e., `__main__` as it is defined in this Jupyter notebook).\n",
+ "\n",
+ "We have seen the type `type` before in the \"*Constructors*\" section in [Chapter 2 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/02_functions/00_content.ipynb#Constructors) and also in the \"*The `namedtuple` Type*\" section in [Chapter 7's Appendix ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/05_appendix.ipynb#The-namedtuple-Type). In the latter case, we could also use a `Point` class but the [namedtuple() ](https://docs.python.org/3/library/collections.html#collections.namedtuple) function from the [collections ](https://docs.python.org/3/library/collections.html) module in the [standard library ](https://docs.python.org/3/library/index.html) is a convenient shortcut to create custom data types that can be derived out of a plain `tuple`.\n",
+ "\n",
+ "In all examples, if an object's type is `type`, we can simply view it as a blueprint for a \"family\" of objects."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "type"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(Vector)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "__main__.Vector"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The docstrings are transformed into convenient help texts."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[0;31mInit signature:\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mDocstring:\u001b[0m A one-dimensional vector from linear algebra.\n",
+ "\u001b[0;31mType:\u001b[0m type\n",
+ "\u001b[0;31mSubclasses:\u001b[0m \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Vector?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Help on class Vector in module __main__:\n",
+ "\n",
+ "class Vector(builtins.object)\n",
+ " | A one-dimensional vector from linear algebra.\n",
+ " | \n",
+ " | Methods defined here:\n",
+ " | \n",
+ " | dummy_method(self)\n",
+ " | A dummy method for illustration purposes.\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data descriptors defined here:\n",
+ " | \n",
+ " | __dict__\n",
+ " | dictionary for instance variables (if defined)\n",
+ " | \n",
+ " | __weakref__\n",
+ " | list of weak references to the object (if defined)\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data and other attributes defined here:\n",
+ " | \n",
+ " | dummy_variable = 'I am a vector'\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "help(Vector)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We can use the built-in [vars() ](https://docs.python.org/3/library/functions.html#vars) function as an alternative to [dir() ](https://docs.python.org/3/library/functions.html#dir) to obtain a *brief* summary of the attributes on `Vector`. Whereas [vars() ](https://docs.python.org/3/library/functions.html#vars) returns a read-only `dict`-like overview on mostly the *explicitly* defined attributes, [dir() ](https://docs.python.org/3/library/functions.html#dir) also shows all *implicitly* added attributes in a `list`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "mappingproxy({'__module__': '__main__',\n",
+ " '__doc__': 'A one-dimensional vector from linear algebra.',\n",
+ " 'dummy_variable': 'I am a vector',\n",
+ " 'dummy_method': ,\n",
+ " '__dict__': ,\n",
+ " '__weakref__': })"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vars(Vector)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['__class__',\n",
+ " '__delattr__',\n",
+ " '__dict__',\n",
+ " '__dir__',\n",
+ " '__doc__',\n",
+ " '__eq__',\n",
+ " '__format__',\n",
+ " '__ge__',\n",
+ " '__getattribute__',\n",
+ " '__gt__',\n",
+ " '__hash__',\n",
+ " '__init__',\n",
+ " '__init_subclass__',\n",
+ " '__le__',\n",
+ " '__lt__',\n",
+ " '__module__',\n",
+ " '__ne__',\n",
+ " '__new__',\n",
+ " '__reduce__',\n",
+ " '__reduce_ex__',\n",
+ " '__repr__',\n",
+ " '__setattr__',\n",
+ " '__sizeof__',\n",
+ " '__str__',\n",
+ " '__subclasshook__',\n",
+ " '__weakref__',\n",
+ " 'dummy_method',\n",
+ " 'dummy_variable']"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "dir(Vector)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "With the dot operator `.` we access the class attributes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'I am a vector'"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector.dummy_variable"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector.dummy_method"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "However, invoking the `.dummy_method()` raises a `TypeError`. That makes sense as the method expects a *concrete* instance passed in as the `self` argument. However, we have not yet created one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "dummy_method() missing 1 required positional argument: 'self'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mVector\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdummy_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: dummy_method() missing 1 required positional argument: 'self'"
+ ]
+ }
+ ],
+ "source": [
+ "Vector.dummy_method()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "## Instantiation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To create a *new* instance, we need to **instantiate** one.\n",
+ "\n",
+ "In the `class` statement, we see a `.__init__()` method that contains all the validation logic that we require a `Vector` instance to adhere to. In a way, this method serves as a constructor-like function.\n",
+ "\n",
+ "`.__init__()` is an example of a so-called **special method** that we use to make new data types integrate with Python's language features. Their naming follows the dunder convention. In this chapter, we introduce some of the more common special methods, and we refer to the [language reference ](https://docs.python.org/3/reference/datamodel.html) for an exhaustive list of all special methods. Special methods not *explicitly* defined in a class are *implicitly* added with a default implementation.\n",
+ "\n",
+ "The `.__init__()` method (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__init__)) is responsible for **initializing** a *new* instance object immediately after its creation. That usually means setting up some **instance attributes**. In the example, a new `Vector` instance is created from some sequence object (e.g., a `tuple` like `x`) passed in as the `data` argument. The elements provided by the `data` argument are first cast as `float` objects and then stored in a `list` object named `._entries` on the *new* instance object. Together, the `float`s represent the state encapsulated within an instance.\n",
+ "\n",
+ "A best practice is to *separate* the way we use a data type (i.e., its \"behavior\") from how we implement it. By convention, attributes that should not be accessed from \"outside\" of an instance start with one leading underscore `_`. In the example, the instance attribute `._entries` is such an **implementation detail**: We could have decided to store a `Vector`'s entries in a `tuple` instead of a `list`. However, this decision should *not* affect how a `Vector` instance is to be used. Moreover, if we changed how the `._entries` are modeled later on, this must *not* break any existing code using `Vector`s. This idea is also known as **[information hiding ](https://en.wikipedia.org/wiki/Information_hiding)** in software engineering."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ " \"\"\"A one-dimensional vector from linear algebra.\n",
+ "\n",
+ " All entries are converted to floats.\n",
+ " \"\"\"\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " \"\"\"Create a new vector.\n",
+ "\n",
+ " Args:\n",
+ " data (sequence): the vector's entries\n",
+ "\n",
+ " Raises:\n",
+ " ValueError: if no entries are provided\n",
+ " \"\"\"\n",
+ " self._entries = list(float(x) for x in data)\n",
+ " if len(self._entries) == 0:\n",
+ " raise ValueError(\"a vector must have at least one entry\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To create a `Vector` instance, we call the `Vector` class with the `()` operator. This call is forwarded to the `.__init__()` method behind the scenes. That is what we mean by saying \"make new data types integrate with Python's language features\" above: We use `Vector` just as any other built-in constructor."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`v` is an object as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "140306181183328"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "id(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Unsurprisingly, the type of `v` is `Vector`. That is the main point of this chapter."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "__main__.Vector"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`v`'s semantic \"value\" is not so clear yet. We fix this in the next section."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "<__main__.Vector at 0x7f9b9416d760>"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Although the `.__init__()` method defines *two* parameters, we must call it with only *one* `data` argument. As noted above, Python implicitly inserts a reference to the newly created instance object (i.e., `v`) as the first argument as `self`.\n",
+ "\n",
+ "Calling a class object with a wrong number of arguments leads to generic `TypeError`s ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "__init__() missing 1 required positional argument: 'data'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: __init__() missing 1 required positional argument: 'data'"
+ ]
+ }
+ ],
+ "source": [
+ "Vector()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "__init__() takes 2 positional arguments but 4 were given",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: __init__() takes 2 positional arguments but 4 were given"
+ ]
+ }
+ ],
+ "source": [
+ "Vector(1, 2, 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... while creating a `Vector` instance from an empty sequence raises a custom `ValueError`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "a vector must have at least one entry",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlist\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"a vector must have at least one entry\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m: a vector must have at least one entry"
+ ]
+ }
+ ],
+ "source": [
+ "Vector([])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Even though we can access the `._entries` attribute on the `v` object (i.e., \"from outside\"), we are *not* supposed to do that because of the underscore `_` convention. In other words, we should access `._entries` only from within a method via `self`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[1.0, 2.0, 3.0]"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v._entries # by convention not allowed"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Text Representations"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For all the built-in data types, an object's value is represented in a *literal notation*, implying that we can simply copy and paste the value into another code cell to create a *new* object with the *same* value.\n",
+ "\n",
+ "The exact representation of the value does *not* have to be identical to the one used to create the object. For example, we can create a `tuple` object without using parentheses and Python still outputs its value with `(` and `)`. That was an *arbitrary* design decision by the core development team."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x = 1, 2, 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, 2, 3)"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, 2, 3)"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(1, 2, 3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To control how objects of a user-defined data type are represented as text, we implement the `.__repr__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__repr__)) and `.__str__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__str__)) methods. Both take only a `self` argument and must return a `str` object."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self._entries)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __str__(self):\n",
+ " first, last = self._entries[0], self._entries[-1]\n",
+ " n_entries = len(self._entries)\n",
+ " return f\"Vector({first!r}, ..., {last!r})[{n_entries:d}]\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, when `v` is evaluated in a code cell, we see the return value of the `.__repr__()` method.\n",
+ "\n",
+ "According to the specification, `.__repr__()` should return a `str` object that, when used as a literal, creates a *new* instance with the *same* state (i.e., their `._entries` attributes compare equal) as the original one. In other words, it should return a **text representation** of the object optimized for direct consumption by the Python interpreter. That is often useful when debugging or logging large applications.\n",
+ "\n",
+ "Our implementation of `.__repr__()` in the `Vector` class uses to a `tuple` notation for the `data` argument. So, even if we create `v` from a `list` object like `[1, 2, 3]` and even though the `_entries` are stored as a `list` object internally, a `Vector` instance's text representation \"defaults\" to `((` and `))` in the output. This decision is arbitrary and we could have used a `list` notation for the `data` argument as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If we copy and paste the value of the `v` object into another code cell, we create a *new* `Vector` instance with the *same* state as `v`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Alternatively, the built-in [repr()]( https://docs.python.org/3/library/functions.html#repr) function returns an object's value as a `str` object (i.e., with the quotes `'`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Vector((1.0, 2.0, 3.0))'"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "repr(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "On the contrary, the `.__str__()` method should return a *human-readable* text representation of the object, and we use the built-in [str() ](https://docs.python.org/3/library/functions.html#func-str) and [print() ](https://docs.python.org/3/library/functions.html#print) functions to obtain this representation explicitly.\n",
+ "\n",
+ "For our `Vector` class, this representation only shows a `Vector`'s first and last entries followed by the total number of entries in brackets. So, even for a `Vector` containing millions of entries, we could easily make sense of the representation.\n",
+ "\n",
+ "While [str() ](https://docs.python.org/3/library/functions.html#func-str) returns the text representation as a `str` object, ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Vector(1.0, ..., 3.0)[3]'"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "str(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... [print() ](https://docs.python.org/3/library/functions.html#print) does not show the enclosing quotes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Vector(1.0, ..., 3.0)[3]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "From a theoretical point of view, the text representation provided by `.__repr__()` contains all the information (i.e., the $0$s and $1$s in memory) that is needed to model something in a computer. In a way, it is a natural extension from the binary (cf., [Chapter 5 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/00_content.ipynb#Binary-Representations)), hexadecimal (cf., [Chapter 5 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/00_content.ipynb#Hexadecimal-Representations)), and `bytes` (cf., [Chapter 6 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/06_text/02_content.ipynb#The-bytes-Type)) representations of information. After all, just like Unicode characters are encoded in `bytes`, the more \"complex\" objects in this chapter are encoded in Unicode characters via their text representations."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### The `Matrix` Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Below is a first implementation of the `Matrix` class that stores the `._entries` internally as a `list` of `list`s.\n",
+ "\n",
+ "The `.__init__()` method ensures that all the rows come with the same number of columns. Again, we do not allow `Matrix` instances without any entries."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "code_folding": [],
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " for row in self._entries[1:]:\n",
+ " if len(row) != len(self._entries[0]):\n",
+ " raise ValueError(\"rows must have the same number of entries\")\n",
+ " if len(self._entries) == 0:\n",
+ " raise ValueError(\"a matrix must have at least one entry\")\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " def __str__(self):\n",
+ " first, last = self._entries[0][0], self._entries[-1][-1]\n",
+ " m, n = len(self._entries), len(self._entries[0])\n",
+ " return f\"Matrix(({first!r}, ...), ..., (..., {last!r}))[{m:d}x{n:d}]\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`Matrix` is an object as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "94113690738160"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "id(Matrix)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "type"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(Matrix)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "__main__.Matrix"
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Matrix"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's create a new `Matrix` instance from a `list` of `tuple`s."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "140306180401856"
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "id(m)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "__main__.Matrix"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The text representations work as above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Matrix((1.0, ...), ..., (..., 9.0))[3x3]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Passing an invalid `data` argument when instantiating a `Matrix` results in the documented exceptions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "a matrix must have at least one entry",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 7\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"rows must have the same number of entries\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 9\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"a matrix must have at least one entry\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 10\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 11\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: a matrix must have at least one entry"
+ ]
+ }
+ ],
+ "source": [
+ "Matrix(())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "rows must have the same number of entries",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, data)\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrow\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrow\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"rows must have the same number of entries\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 8\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"a matrix must have at least one entry\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: rows must have the same number of entries"
+ ]
+ }
+ ],
+ "source": [
+ "Matrix([(1, 2, 3), (4, 5)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Instance Methods vs. Class Methods"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The methods we have seen so far are all **instance methods**. The characteristic idea behind an instance method is that the behavior it provides either depends on the state of a concrete instance or mutates it. In other words, an instance method *always* works with attributes on the `self` argument. If a method does *not* need access to `self` to do its job, it is conceptually *not* an instance method and we should probably convert it into another kind of method as shown below.\n",
+ "\n",
+ "An example of an instance method from linear algebra is the `.transpose()` method below that switches the rows and columns of an *existing* `Matrix` instance and returns a *new* `Matrix` instance based off that. It is implemented by passing the *iterator* created with the [zip() ](https://docs.python.org/3/library/functions.html#zip) built-in as the `data` argument to the `Matrix` constructor: The expression `zip(*self._entries)` may be a bit hard to understand because of the involved unpacking but simply flips a `Matrix`'s rows and columns. The built-in [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor within the `.__init__()` method then materializes the iterator into the `._entries` attribute. Without a concrete `Matrix`'s rows and columns, `.transpose()` does not make sense, conceptually speaking.\n",
+ "\n",
+ "Also, we see that it is ok to reference a class from within one of its methods. While this seems trivial to some readers, others may find this confusing. The final versions of the `Vector` and `Matrix` classes in the [fourth part ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/04_content.ipynb#The-final-Vector-and-Matrix-Classes) of this chapter show how this \"hard coded\" redundancy can be avoided."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " def transpose(self):\n",
+ " return Matrix(zip(*self._entries))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `.transpose()` method returns a *new* `Matrix` instance where the rows and columns are flipped."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 4.0, 7.0,), (2.0, 5.0, 8.0,), (3.0, 6.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 50,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.transpose()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Two invocations of `.transpose()` may be chained, which negates its overall effect but still creates a *new* instance object (i.e., `m is n` is `False`)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "n = m.transpose().transpose()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 53,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m is n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Unintuitively, the comparison operator `==` returns a wrong result as `m` and `n` have `_entries` attributes that compare equal. We fix this in the \"*Operator Overloading*\" section later in this chapter."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m == n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Sometimes, it is useful to attach functionality to a class object that does *not* depend on the state of a concrete instance but on the class as a whole. Such methods are called **class methods** and can be created with the [classmethod() ](https://docs.python.org/3/library/functions.html#classmethod) built-in combined with the `@` **decorator** syntax. Then, Python adapts the binding process described above such that it implicitly inserts a reference to the class object itself instead of the instance when the method is invoked. By convention, we name this parameter `cls`.\n",
+ "\n",
+ "Class methods are often used to provide an alternative way to create instances, usually from a different kind of arguments. As an example, `.from_columns()` expects a sequence of columns instead of rows as its `data` argument. It forwards the invocation to the `.__init__()` method (i.e., what `cls(data)` does; `cls` references the *same* class object as `Matrix`), then calls the `.transpose()` method on the newly created instance, and lastly returns the instance created by `.transpose()`. Again, we are intelligently *reusing* a lot of code."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " def transpose(self):\n",
+ " return Matrix(zip(*self._entries))\n",
+ "\n",
+ " @classmethod\n",
+ " def from_columns(cls, data):\n",
+ " return cls(data).transpose()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We use the alternative `.from_columns()` constructor to create a `Matrix` equivalent to `m` above from a `list` of columns instead of rows."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix.from_columns([(1, 4, 7), (2, 5, 8), (3, 6, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "There is also a [staticmethod() ](https://docs.python.org/3/library/functions.html#staticmethod) built-in to be used with the `@` syntax to define methods that are *independent* from both the class and instance objects but nevertheless related semantically to a class. In this case, the binding process is disabled an no argument is implicitly inserted upon a method's invocation. Such **static methods** are not really needed most of the time and we omit them here fore brevity."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Computed Properties"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "After creation, a `Matrix` instance exhibits certain properties that depend only on the concrete `data` encapsulated in it. For example, every `Matrix` instance implicitly has *two* dimensions: These are commonly denoted as $m$ and $n$ in math and represent the number of rows and columns.\n",
+ "\n",
+ "We would like our `Matrix` instances to have two attributes, `.n_rows` and `.n_cols`, that provide the correct dimensions as `int` objects. To achieve that, we implement two instance methods, `.n_rows()` and `.n_cols()`, and make them **derived attributes** by decorating them with the [property() ](https://docs.python.org/3/library/functions.html#property) built-in. They work like methods except that they do not need to be invoked with the call operator `()` but can be accessed as if they were instance variables.\n",
+ "\n",
+ "To reuse their code, we integrate the new properties already within the `.__init__()` method."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " for row in self._entries[1:]:\n",
+ " if len(row) != self.n_cols:\n",
+ " raise ValueError(\"rows must have the same number of entries\")\n",
+ " if self.n_rows == 0:\n",
+ " raise ValueError(\"a matrix must have at least one entry\")\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " @property\n",
+ " def n_rows(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " @property\n",
+ " def n_cols(self):\n",
+ " return len(self._entries[0])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The revised `m` models a $2 \\times 3$ matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(2, 3)"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.n_rows, m.n_cols"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In its basic form, properties are *read-only* attributes. This makes sense for `Matrix` instances where we can *not* \"set\" how many rows and columns there are while keeping the `_entries` unchanged."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "AttributeError",
+ "evalue": "can't set attribute",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m: can't set attribute"
+ ]
+ }
+ ],
+ "source": [
+ "m.n_rows = 3"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "livereveal": {
+ "auto_select": "code",
+ "auto_select_fragment": true,
+ "scroll": true,
+ "theme": "serif"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "384px"
+ },
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/01_exercises.ipynb b/11_classes/01_exercises.ipynb
new file mode 100644
index 0000000..915dbc9
--- /dev/null
+++ b/11_classes/01_exercises.ipynb
@@ -0,0 +1,1502 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Run All*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *after* finishing the exercises to ensure that your solution runs top to bottom *without* any errors. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/01_exercises.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Chapter 11: Classes & Instances (Coding Exercises)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The exercises below assume that you have read the [first part ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb) of Chapter 11.\n",
+ "\n",
+ "The `...`'s in the code cells indicate where you need to fill in code snippets. The number of `...`'s within a code cell give you a rough idea of how many lines of code are needed to solve the task. You should not need to create any additional code cells for your final solution. However, you may want to use temporary code cells to try out some ideas."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Berlin Tourist Guide: A Traveling Salesman Problem"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This notebook is a hands-on and tutorial-like application to show how to load data from web services like [Google Maps](https://developers.google.com/maps) and use them to solve a logistics problem, namely a **[Traveling Salesman Problem ](https://en.wikipedia.org/wiki/Traveling_salesman_problem)**.\n",
+ "\n",
+ "Imagine that a tourist lands at Berlin's [Tegel Airport ](https://en.wikipedia.org/wiki/Berlin_Tegel_Airport) in the morning and has his \"connecting\" flight from [Schönefeld Airport ](https://en.wikipedia.org/wiki/Berlin_Sch%C3%B6nefeld_Airport) in the evening. By the time, the flights were scheduled, the airline thought that there would be only one airport in Berlin.\n",
+ "\n",
+ "Having never been in Berlin before, the tourist wants to come up with a plan of sights that he can visit with a rental car on his way from Tegel to Schönefeld.\n",
+ "\n",
+ "With a bit of research, he creates a `list` of `sights` like below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "arrival = \"Berlin Tegel Airport (TXL), Berlin\"\n",
+ "\n",
+ "sights = [\n",
+ " \"Alexanderplatz, Berlin\",\n",
+ " \"Brandenburger Tor, Pariser Platz, Berlin\",\n",
+ " \"Checkpoint Charlie, Friedrichstraße, Berlin\",\n",
+ " \"Kottbusser Tor, Berlin\",\n",
+ " \"Mauerpark, Berlin\",\n",
+ " \"Siegessäule, Berlin\",\n",
+ " \"Reichstag, Platz der Republik, Berlin\",\n",
+ " \"Soho House Berlin, Torstraße, Berlin\",\n",
+ " \"Tempelhofer Feld, Berlin\",\n",
+ "]\n",
+ "\n",
+ "departure = \"Berlin Schönefeld Airport (SXF), Berlin\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "With just the street addresses, however, he cannot calculate a route. He needs `latitude`-`longitude` coordinates instead. While he could just open a site like [Google Maps](https://www.google.com/maps) in a web browser, he wonders if he can download the data with a bit of Python code using a [web API ](https://en.wikipedia.org/wiki/Web_API) offered by [Google](https://www.google.com).\n",
+ "\n",
+ "So, in this notebook, we solve the entire problem with code."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Geocoding"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In order to obtain coordinates for the given street addresses above, a process called **geocoding**, we use the [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/start).\n",
+ "\n",
+ "**Q1**: Familiarize yourself with this [documentation](https://developers.google.com/maps/documentation/geocoding/start), register a developer account, create a project, and [create an API key](https://console.cloud.google.com/apis/credentials) that is necessary for everything to work! Then, [enable the Geocoding API](https://console.developers.google.com/apis/library/geocoding-backend.googleapis.com) and link a [billing account](https://console.developers.google.com/billing)!\n",
+ "\n",
+ "Info: The first 200 Dollars per month are not charged (cf., [pricing page](https://cloud.google.com/maps-platform/pricing/)), so no costs will incur for this tutorial. You must sign up because Google simply wants to know the people using its services.\n",
+ "\n",
+ "**Q2**: Assign the API key as a `str` object to the `key` variable!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "key = \" < your API key goes here > \""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To use external web services, our application needs to make HTTP requests just like web browsers do when surfing the web.\n",
+ "\n",
+ "We do not have to implement this on our own. Instead, we use the official Python Client for the Google Maps Services provided by Google in one of its corporate [GitHub repositories ](https://github.com/googlemaps).\n",
+ "\n",
+ "**Q3**: Familiarize yourself with the [googlemaps ](https://github.com/googlemaps/google-maps-services-python) package! Then, install it with the `pip` command line tool!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install googlemaps"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q4**: Finish the following code cells and instantiate a `Client` object named `api`! Use the `key` from above. `api` provides us with a lot of methods to talk to the API."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import googlemaps"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "api = googlemaps.Client(...)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "api"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "type(api)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q5**: Execute the next code cell to list the methods and attributes on the `api` object!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "[x for x in dir(api) if not x.startswith(\"_\")]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To obtain all kinds of information associated with a street address, we call the `geocode()` method with the address as the sole argument.\n",
+ "\n",
+ "For example, let's search for Brandenburg Gate. Its street address is `\"Brandenburger Tor, Pariser Platz, Berlin\"`.\n",
+ "\n",
+ "**Q6**: Execute the next code cell!\n",
+ "\n",
+ "Hint: If you get an error message, follow the instructions in it to debug it.\n",
+ "\n",
+ "If everything works, we receive a `list` with a single `dict` in it. That means the [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/start) only knows about one place at the address. Unfortunately, the `dict` is pretty dense and hard to read."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "api.geocode(\"Brandenburger Tor, Pariser Platz, Berlin\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q7**: Capture the first and only search result in the `brandenburg_gate` variable and \"pretty print\" it with the help of the [pprint() ](https://docs.python.org/3/library/pprint.html#pprint.pprint) function in the [pprint ](https://docs.python.org/3/library/pprint.html) module in the [standard library ](https://docs.python.org/3/library/index.html)!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "response = api.geocode(\"Brandenburger Tor, Pariser Platz, Berlin\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate = ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `dict` has several keys that are of use for us: `\"formatted_address\"` is a cleanly formatted version of the address. `\"geometry\"` is a nested `dict` with several `lat`-`lng` coordinates representing the place where `\"location\"` is the one we need for our calculations. Lastly, `\"place_id\"` is a unique identifier that allows us to obtain further information about the address from other Google APIs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pprint import pprint"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pprint(brandenburg_gate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Place` Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To keep our code readable and maintainable, we create a `Place` class to manage the API results in a clean way.\n",
+ "\n",
+ "The `.__init__()` method takes a `street_address` (e.g., an element of `sights`) and a `client` argument (e.g., an object like `api`) and stores them on `self`. The place's `.name` is parsed out of the `street_address` as well: It is the part before the first comma. Also, the instance attributes `.latitude`, `.longitude`, and `.place_id` are initialized to `None`.\n",
+ "\n",
+ "**Q8**: Finish the `.__init__()` method according to the description!\n",
+ "\n",
+ "The `.sync_from_google()` method uses the internally kept `client` and synchronizes the place's state with the [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/start). In particular, it updates the `.address` with the `formatted_address` and stores the values for `.latitude`, `.longitude`, and `.place_id`. It enables method chaining.\n",
+ "\n",
+ "**Q9**: Implement the `.sync_from_google()` method according to the description!\n",
+ "\n",
+ "**Q10**: Add a read-only `.location` property on the `Place` class that returns the `.latitude` and `.longitude` as a `tuple`!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Place:\n",
+ " \"\"\"A place connected to the Google Maps Geocoding API.\"\"\"\n",
+ "\n",
+ " # answer to Q8\n",
+ " def __init__(self, street_address, *, client):\n",
+ " \"\"\"Create a new place.\n",
+ "\n",
+ " Args:\n",
+ " street_address (str): street address of the place\n",
+ " client (googlemaps.Client): access to the Google Maps Geocoding API\n",
+ " \"\"\"\n",
+ " ...\n",
+ " ...\n",
+ " ...\n",
+ " ...\n",
+ " ...\n",
+ " ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " cls, name = self.__class__.__name__, self.name\n",
+ " synced = \" [SYNCED]\" if self.place_id else \"\"\n",
+ " return f\"<{cls}: {name}{synced}>\"\n",
+ "\n",
+ " # answer to Q9\n",
+ " def sync_from_google(self):\n",
+ " \"\"\"Download the place's coordinates and other info.\"\"\"\n",
+ " response = ...\n",
+ " first_hit = ...\n",
+ " ... = first_hit[...]\n",
+ " ... = first_hit[...][...][...]\n",
+ " ... = first_hit[...][...][...]\n",
+ " ... = first_hit[...]\n",
+ " return ...\n",
+ "\n",
+ " # answer to Q10\n",
+ " ...\n",
+ " ...\n",
+ " ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q11**: Verify that the instantiating a `Place` object works!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate = Place(\"Brandenburger Tor, Pariser Platz, Berlin\", client=api)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q12**: What do the angle brackets `<` and `>` mean in the text representation?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, we can obtain the geo-data from the [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/start) in a clean way. As we enabled method chaining for `.sync_from_google()`, we get back the instance after calling the method.\n",
+ "\n",
+ "**Q13**: Verify that the `.sync_from_google()` method works!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.sync_from_google()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.address"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.place_id"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.location"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Place` Class (continued): Batch Synchronization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q14**: Add an alternative constructor method named `.from_addresses()` that takes an `addresses`, a `client`, and a `sync` argument! `addresses` is a finite iterable of `str` objects (e.g., like `sights`). The method returns a `list` of `Place`s, one for each `str` in `addresses`. All `Place`s are initialized with the same `client`. `sync` is a flag and defaults to `False`. If it is set to `True`, the alternative constructor invokes the `.sync_from_google()` method on the `Place`s before returning them."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "code_folding": []
+ },
+ "outputs": [],
+ "source": [
+ "class Place:\n",
+ " \"\"\"A place connected to the Google Maps Geocoding API.\"\"\"\n",
+ "\n",
+ " # answers from above\n",
+ "\n",
+ " # answer to Q14\n",
+ " ...\n",
+ " def from_addresses(cls, addresses, *, client, sync=False):\n",
+ " \"\"\"Create new places in a batch.\n",
+ "\n",
+ " Args:\n",
+ " addresses (iterable of str's): the street addresses of the places\n",
+ " client (googlemaps.Client): access to the Google Maps Geocoding API\n",
+ " Returns:\n",
+ " list of Places\n",
+ " \"\"\"\n",
+ " places = ...\n",
+ " for address in addresses:\n",
+ " place = ...\n",
+ " if sync:\n",
+ " ...\n",
+ " ...\n",
+ " return places"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q15**: Verify that the alternative constructor works with and without the `sync` flag!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Place.from_addresses(sights, client=api)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Place.from_addresses(sights, client=api, sync=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Visualization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "For geo-data it always makes sense to plot them on a map. We use the third-party library [folium ](https://github.com/python-visualization/folium) to achieve that.\n",
+ "\n",
+ "**Q16**: Familiarize yourself with [folium ](https://github.com/python-visualization/folium) and install it with the `pip` command line tool!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install folium"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q17**: Execute the code cells below to create an empty map of Berlin!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import folium"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin = folium.Map(location=(52.513186, 13.3944349), zoom_start=14)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "type(berlin)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`folium.Map` instances are shown as interactive maps in Jupyter notebooks whenever they are the last expression in a code cell."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In order to put something on the map, [folium ](https://github.com/python-visualization/folium) works with so-called `Marker` objects.\n",
+ "\n",
+ "**Q18**: Review its docstring and then create a marker `m` with the location data of Brandenburg Gate! Use the `brandenburg_gate` object from above!\n",
+ "\n",
+ "Hint: You may want to use HTML tags for the `popup` argument to format the text output on the map in a nicer way. So, instead of just passing `\"Brandenburger Tor\"` as the `popup` argument, you could use, for example, `\"Brandenburger Tor
(Pariser Platz, 10117 Berlin, Germany)\"`. Then, the name appears in bold and the street address is put on the next line. You could use an f-string to parametrize the argument."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "folium.Marker?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "m = folium.Marker(\n",
+ " location=...,\n",
+ " popup=...,\n",
+ " tooltip=...,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "type(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q19**: Execute the next code cells that add `m` to the `berlin` map!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "m.add_to(berlin)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Place` Class (continued): Marker Representation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q20**: Finish the `.as_marker()` method that returns a `Marker` instance when invoked on a `Place` instance! The method takes an optional `color` argument that uses [folium ](https://github.com/python-visualization/folium)'s `Icon` type to control the color of the marker."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Place:\n",
+ " \"\"\"A place connected to the Google Maps Geocoding API.\"\"\"\n",
+ "\n",
+ " # answers from above\n",
+ "\n",
+ " # answer to Q20\n",
+ " def as_marker(self, *, color=\"blue\"):\n",
+ " \"\"\"Create a Marker representation of the place.\n",
+ "\n",
+ " Args:\n",
+ " color (str): color of the marker, defaults to \"blue\"\n",
+ "\n",
+ " Returns:\n",
+ " marker (folium.Marker)\n",
+ "\n",
+ " Raises:\n",
+ " RuntimeError: if the place is not synchronized with\n",
+ " the Google Maps Geocoding API\n",
+ " \"\"\"\n",
+ " if not self.place_id:\n",
+ " raise RuntimeError(\"must synchronize with Google first\")\n",
+ " return folium.Marker(\n",
+ " location=...,\n",
+ " popup=...,\n",
+ " tooltip=...,\n",
+ " icon=folium.Icon(color=color),\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q21**: Execute the next code cells that create a new `Place` and obtain a `Marker` for it!\n",
+ "\n",
+ "Notes: Without synchronization, we get a `RuntimeError`. `.as_marker()` can be chained right after `.sync_from_google()`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate = Place(\"Brandenburger Tor, Pariser Platz, Berlin\", client=api)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.as_marker()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "brandenburg_gate.sync_from_google().as_marker()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q22**: Use the alternative `.from_addresses()` constructor to create a `list` named `places` with already synced `Place`s!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "places = Place.from_addresses(sights, client=api, sync=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "places"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Map` Class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "subslide"
+ }
+ },
+ "source": [
+ "To make [folium ](https://github.com/python-visualization/folium)'s `Map` class work even better with our `Place` instances, we write our own `Map` class wrapping [folium ](https://github.com/python-visualization/folium)'s. Then, we add further functionality to the class throughout this tutorial.\n",
+ "\n",
+ "The `.__init__()` method takes mandatory `name`, `center`, `start`, `end`, and `places` arguments. `name` is there for convenience, `center` is the map's initial center, `start` and `end` are `Place` instances, and `places` is a finite iterable of `Place` instances. Also, `.__init__()` accepts an optional `initial_zoom` argument defaulting to `12`.\n",
+ "\n",
+ "Upon initialization, a `folium.Map` instance is created and stored as an implementation detail `_map`. Also, `.__init__()` puts markers for each place on the `_map` object: `\"green\"` and `\"red\"` markers for the `start` and `end` locations and `\"blue\"` ones for the `places` to be visited. To do that, `.__init__()` invokes another `.add_marker()` method on the `Map` class, once for every `Place` object. `.add_marker()` itself invokes the `.add_to()` method on the `folium.Marker` representation of a `Place` instance and enables method chaining.\n",
+ "\n",
+ "To keep the state in a `Map` instance consistent, all passed in arguments except `name` are treated as implementation details. Otherwise, a user of the `Map` class could, for example, change the `start` attribute, which would not be reflected in the internally kept `folium.Map` object.\n",
+ "\n",
+ "**Q23**: Implement the `.__init__()` and `.add_marker()` methods on the `Map` class as described!\n",
+ "\n",
+ "**Q24**: Add a `.show()` method on the `Map` class that simply returns the internal `folium.Map` object!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Map:\n",
+ " \"\"\"A map with plotting and routing capabilities.\"\"\"\n",
+ "\n",
+ " # answer to Q23\n",
+ " def __init__(self, name, center, start, end, places, initial_zoom=12):\n",
+ " \"\"\"Create a new map.\n",
+ "\n",
+ " Args:\n",
+ " name (str): name of the map\n",
+ " center (float, float): coordinates of the map's center\n",
+ " start (Place): start of the tour\n",
+ " end (Place): end of the tour\n",
+ " places (iterable of Places): the places to be visitied\n",
+ " initial_zoom (integer): zoom level according to folium's\n",
+ " specifications; defaults to 12\n",
+ " \"\"\"\n",
+ " self.name = name\n",
+ " ...\n",
+ " ...\n",
+ " ...\n",
+ " ... = folium.Map(...)\n",
+ "\n",
+ " # Add markers to the map.\n",
+ " ...\n",
+ " ...\n",
+ " for place in places:\n",
+ " ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " return f\"\"\n",
+ "\n",
+ " # answer to Q24\n",
+ " def show(self):\n",
+ " \"\"\"Return a folium.Map representation of the map.\"\"\"\n",
+ " return ...\n",
+ "\n",
+ " # answer to Q23\n",
+ " def add_marker(self, marker):\n",
+ " \"\"\"Add a marker to the map.\n",
+ "\n",
+ " Args:\n",
+ " marker (folium.Marker): marker to be put on the map\n",
+ " \"\"\"\n",
+ " ...\n",
+ " return ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's put all the sights, the two airports, and three more places, the [Bundeskanzleramt ](https://en.wikipedia.org/wiki/German_Chancellery), the [Olympic Stadium ](https://en.wikipedia.org/wiki/Olympiastadion_%28Berlin%29), and the [East Side Gallery ](https://en.wikipedia.org/wiki/East_Side_Gallery), on the map.\n",
+ "\n",
+ "**Q25**: Execute the next code cells to create a map of Berlin with all the places on it!\n",
+ "\n",
+ "Note: Because we implemented method chaining everywhere, the code below is only *one* expression written over several lines. It almost looks like a self-explanatory and compact \"language\" on its own."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin = (\n",
+ " Map(\n",
+ " \"Sights in Berlin\",\n",
+ " center=(52.5015154, 13.4066838),\n",
+ " start=Place(arrival, client=api).sync_from_google(),\n",
+ " end=Place(departure, client=api).sync_from_google(),\n",
+ " places=places,\n",
+ " initial_zoom=10,\n",
+ " )\n",
+ " .add_marker(\n",
+ " Place(\"Bundeskanzleramt, Willy-Brandt-Straße, Berlin\", client=api)\n",
+ " .sync_from_google()\n",
+ " .as_marker(color=\"orange\")\n",
+ " )\n",
+ " .add_marker(\n",
+ " Place(\"Olympiastadion Berlin\", client=api)\n",
+ " .sync_from_google()\n",
+ " .as_marker(color=\"orange\")\n",
+ " )\n",
+ " .add_marker(\n",
+ " Place(\"East Side Gallery, Berlin\", client=api)\n",
+ " .sync_from_google()\n",
+ " .as_marker(color=\"orange\")\n",
+ " )\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Distance Matrix"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before we can find out the best order in which to visit all the sights, we must calculate the pairwise distances between all points. While Google also offers a [Directions API](https://developers.google.com/maps/documentation/directions/start) and a [Distance Matrix API](https://developers.google.com/maps/documentation/distance-matrix/start), we choose to calculate the air distances using the third-party library [geopy ](https://github.com/geopy/geopy).\n",
+ "\n",
+ "**Q26**: Familiarize yourself with the [documentation](https://geopy.readthedocs.io/en/stable/) and install [geopy ](https://github.com/geopy/geopy) with the `pip` command line tool!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "!pip install geopy"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We use [geopy ](https://github.com/geopy/geopy) primarily for converting the `latitude`-`longitude` coordinates into a [distance matrix ](https://en.wikipedia.org/wiki/Distance_matrix).\n",
+ "\n",
+ "Because the [earth is not flat ](https://en.wikipedia.org/wiki/Flat_Earth), [geopy ](https://github.com/geopy/geopy) provides a `great_circle()` function that calculates the so-called [orthodromic distance ](https://en.wikipedia.org/wiki/Great-circle_distance) between two places on a sphere."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from geopy.distance import great_circle"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q27**: For quick reference, read the docstring of `great_circle()` and execute the code cells below to calculate the distance between the `arrival` and the `departure`!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "great_circle?"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tegel = Place(arrival, client=api).sync_from_google()\n",
+ "schoenefeld = Place(departure, client=api).sync_from_google()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "great_circle(tegel.location, schoenefeld.location)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "great_circle(tegel.location, schoenefeld.location).km"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "great_circle(tegel.location, schoenefeld.location).meters"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Place` Class (continued): Distance to another `Place`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q28**: Finish the `distance_to()` method in the `Place` class that takes a `other` argument and returns the distance in meters! Adhere to the given docstring!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Place:\n",
+ " \"\"\"A place connected to the Google Maps Geocoding API.\"\"\"\n",
+ "\n",
+ " # answers from above\n",
+ "\n",
+ " # answer to Q28\n",
+ " def distance_to(self, other):\n",
+ " \"\"\"Calculate the distance to another place in meters.\n",
+ "\n",
+ " Args:\n",
+ " other (Place): the other place to calculate the distance to\n",
+ "\n",
+ " Returns:\n",
+ " distance (int)\n",
+ "\n",
+ " Raises:\n",
+ " RuntimeError: if one of the places is not synchronized with\n",
+ " the Google Maps Geocoding API\n",
+ " \"\"\"\n",
+ " if not self.place_id or not other.place_id:\n",
+ " raise RuntimeError(\"must synchronize places with Google first\")\n",
+ " return ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q29**: Execute the code cells below to test the new feature!\n",
+ "\n",
+ "Note: If done right, object-oriented code reads almost like plain English."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tegel = Place(arrival, client=api).sync_from_google()\n",
+ "schoenefeld = Place(departure, client=api).sync_from_google()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tegel.distance_to(schoenefeld)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q30**: Execute the next code cell to instantiate the `Place`s in `sights` again!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "places = Place.from_addresses(sights, client=api, sync=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "places"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Map` Class (continued): Pairwise Distances"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now, we add a read-only `distances` property on our `Map` class. As we are working with air distances, these are *symmetric* which reduces the number of distances we must calculate.\n",
+ "\n",
+ "To do so, we use the [combinations() ](https://docs.python.org/3/library/itertools.html#itertools.combinations) generator function in the [itertools ](https://docs.python.org/3/library/itertools.html) module in the [standard library ](https://docs.python.org/3/library/index.html). That produces all possible `r`-`tuple`s from an `iterable` argument. `r` is `2` in our case as we are looking at `origin`-`destination` pairs.\n",
+ "\n",
+ "Let's first look at an easy example of [combinations() ](https://docs.python.org/3/library/itertools.html#itertools.combinations) to understand how it works: It gives us all the `2`-`tuple`s from a `list` of five `numbers` disregarding the order of the `tuple`s' elements."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import itertools"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "numbers = [1, 2, 3, 4, 5]\n",
+ "\n",
+ "for x, y in itertools.combinations(numbers, 2):\n",
+ " print(x, y)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`distances` uses the internal `._start`, `._end`, and `._places` attributes and creates a `dict` with the keys consisting of all pairs of `Place`s and the values being their distances in meters. As this operation is rather costly, we cache the distances the first time we calculate them into a hidden instance attribute `._distances`, which must be initialized with `None` in the `.__init__()` method.\n",
+ "\n",
+ "**Q31**: Finish the `.distances` property as described!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Map:\n",
+ " \"\"\"A map with plotting and routing capabilities.\"\"\"\n",
+ "\n",
+ " # answers from above with a tiny adjustment\n",
+ "\n",
+ " # answer to Q31\n",
+ " ...\n",
+ " def distances(self):\n",
+ " \"\"\"Return a dict with the pairwise distances of all places.\n",
+ "\n",
+ " Implementation note: The results of the calculations are cached.\n",
+ " \"\"\"\n",
+ " if not self._distances:\n",
+ " distances = ...\n",
+ " all_pairs = itertools.combinations(\n",
+ " ...,\n",
+ " r=2,\n",
+ " )\n",
+ " for origin, destination in all_pairs:\n",
+ " distance = ...\n",
+ " distances[origin, destination] = distance\n",
+ " distances[destination, origin] = distance\n",
+ " self._distances = ...\n",
+ " return ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We pretty print the total distance matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin = Map(\n",
+ " \"Berlin\",\n",
+ " center=(52.5015154, 13.4066838),\n",
+ " start=Place(arrival, client=api).sync_from_google(),\n",
+ " end=Place(departure, client=api).sync_from_google(),\n",
+ " places=places,\n",
+ " initial_zoom=10,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pprint(berlin.distances)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "How can we be sure the matrix contains all possible pairs? As we have 9 `sights` plus the `start` and the `end` of the tour, we conclude that there must be `11 * 10 = 110` distances excluding the `0` distances of a `Place` to itself that are not in the distance matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_places = len(places) + 2\n",
+ "\n",
+ "n_places * (n_places - 1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "len(berlin.distances)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Route Optimization"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let us find the cost minimal order of traveling from the `arrival` airport to the `departure` airport while visiting all the `sights`.\n",
+ "\n",
+ "This problem can be expressed as finding the shortest so-called [Hamiltonian path ](https://en.wikipedia.org/wiki/Hamiltonian_path) from the `start` to `end` on the `Map` (i.e., a path that visits each intermediate node exactly once). With the \"hack\" of assuming the distance of traveling from the `end` to the `start` to be `0` and thereby effectively merging the two airports into a single node, the problem can be viewed as a so-called [traveling salesman problem ](https://en.wikipedia.org/wiki/Traveling_salesman_problem) (TSP).\n",
+ "\n",
+ "The TSP is a hard problem to solve but also well studied in the literature. Assuming symmetric distances, a TSP with $n$ nodes has $\\frac{(n-1)!}{2}$ possible routes. $(n-1)$ because any node can be the `start` / `end` and divided by $2$ as the problem is symmetric.\n",
+ "\n",
+ "Starting with about $n = 20$, the TSP is almost impossible to solve exactly in a reasonable amount of time. Luckily, we do not have that many `sights` to visit, and so we use a [brute force ](https://en.wikipedia.org/wiki/Brute-force_search) approach and simply loop over all possible routes to find the shortest.\n",
+ "\n",
+ "In the case of our tourist, we \"only\" need to try out `181_440` possible routes because the two airports are effectively one node and $n$ becomes $10$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import math"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "math.factorial(len(places) + 1 - 1) // 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Analyzing the problem a bit further, all we need is a list of [permutations ](https://en.wikipedia.org/wiki/Permutation) of the sights as the two airports are always the first and last location.\n",
+ "\n",
+ "The [permutations() ](https://docs.python.org/3/library/itertools.html#itertools.permutations) generator function in the [itertools ](https://docs.python.org/3/library/itertools.html) module in the [standard library ](https://docs.python.org/3/library/index.html) helps us with the task. Let's see an example to understand how it works."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "numbers = [1, 2, 3]\n",
+ "\n",
+ "for permutation in itertools.permutations(numbers):\n",
+ " print(permutation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "However, if we use [permutations() ](https://docs.python.org/3/library/itertools.html#itertools.permutations) as is, we try out *redundant* routes. For example, transferred to our case, the tuples `(1, 2, 3)` and `(3, 2, 1)` represent the *same* route as the distances are symmetric and the tourist could be going in either direction. To obtain the *unique* routes, we use an `if`-clause in a \"hacky\" way by only accepting routes where the first node has a smaller value than the last. Thus, we keep, for example, `(1, 2, 3)` and discard `(3, 2, 1)`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for permutation in itertools.permutations(numbers):\n",
+ " if permutation[0] < permutation[-1]:\n",
+ " print(permutation)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In order to compare `Place`s as numbers, we would have to implement, among others, the `.__eq__()` special method. Otherwise, we get a `TypeError`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Place(arrival, client=api) < Place(departure, client=api)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As a quick and dirty solution, we use the `.location` property on a `Place` to do the comparison."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "Place(arrival, client=api).location < Place(departure, client=api).location"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "As the code cell below shows, combining [permutations() ](https://docs.python.org/3/library/itertools.html#itertools.permutations) with an `if`-clause results in the correct number of routes to be looped over."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "sum(\n",
+ " 1\n",
+ " for route in itertools.permutations(places)\n",
+ " if route[0].location < route[-1].location\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To implement the brute force algorithm, we split the logic into two methods.\n",
+ "\n",
+ "First, we create an `.evaluate()` method that takes a `route` argument that is a sequence of `Place`s and returns the total distance of the route. Internally, this method uses the `.distances` property repeatedly, which is why we built in caching above.\n",
+ "\n",
+ "**Q32**: Finish the `.evaluate()` method as described!\n",
+ "\n",
+ "Second, we create a `.brute_force()` method that needs no arguments. It loops over all possible routes to find the shortest. As the `start` and `end` of a route are fixed, we only need to look at `permutation`s of inner nodes. Each `permutation` can then be traversed in a forward and a backward order. `.brute_force()` enables method chaining as well.\n",
+ "\n",
+ "**Q33**: Finish the `.brute_force()` method as described!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Map` Class (continued): Brute Forcing the TSP"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Map:\n",
+ " \"\"\"A map with plotting and routing capabilities.\"\"\"\n",
+ "\n",
+ " # answers from above\n",
+ "\n",
+ " # answer to Q32\n",
+ " def evaluate(self, route):\n",
+ " \"\"\"Calculate the total distance of a route.\n",
+ "\n",
+ " Args:\n",
+ " route (sequence of Places): the ordered nodes in a tour\n",
+ "\n",
+ " Returns:\n",
+ " cost (int)\n",
+ " \"\"\"\n",
+ " cost = ...\n",
+ " # Loop over all pairs of consecutive places.\n",
+ " origin = ...\n",
+ " for destination in ...:\n",
+ " cost += self.distances[...]\n",
+ " ...\n",
+ "\n",
+ " return ...\n",
+ "\n",
+ " # answer to Q33\n",
+ " def brute_force(self):\n",
+ " \"\"\"Calculate the shortest route by brute force.\n",
+ "\n",
+ " The route is plotted on the folium.Map.\n",
+ " \"\"\"\n",
+ " # Assume a very high cost to begin with.\n",
+ " min_cost = ...\n",
+ " best_route = None\n",
+ "\n",
+ " # Loop over all permutations of the intermediate nodes to visit.\n",
+ " for permutation in ...:\n",
+ " # Skip redundant permutations.\n",
+ " if ...:\n",
+ " ...\n",
+ " # Travel through the routes in both directions.\n",
+ " for route in (permutation, permutation[::-1]):\n",
+ " # Add start and end to the route.\n",
+ " route = (..., *route, ...)\n",
+ " # Check if a route is cheaper than all routes seen before.\n",
+ " cost = ...\n",
+ " if ...:\n",
+ " min_cost = ...\n",
+ " best_route = ...\n",
+ "\n",
+ " # Plot the route on the map\n",
+ " folium.PolyLine(\n",
+ " [x.location for x in best_route],\n",
+ " color=\"orange\", weight=3, opacity=1\n",
+ " ).add_to(self._map)\n",
+ "\n",
+ " return ..."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q34**: Find the best route for our tourist by executing the code cells below!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin = Map(\n",
+ " \"Berlin\",\n",
+ " center=(52.4915154, 13.4066838),\n",
+ " start=Place(arrival, client=api).sync_from_google(),\n",
+ " end=Place(departure, client=api).sync_from_google(),\n",
+ " places=places,\n",
+ " initial_zoom=12,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "berlin.brute_force().show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "320px"
+ },
+ "toc_section_display": true,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/02_content.ipynb b/11_classes/02_content.ipynb
new file mode 100644
index 0000000..90d650e
--- /dev/null
+++ b/11_classes/02_content.ipynb
@@ -0,0 +1,1983 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/02_content.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Chapter 11: Classes & Instances (continued)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In this second part of the chapter, we learn how we make our `Vector` and `Matrix` instances behave like Python's built-in sequence types, for example, `list` or `tuple` objects."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Sequence Emulation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As discussed in detail in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/00_content.ipynb#Collections-vs.-Sequences), a sequence is any finite and iterable container type with a *predictable* order of its elements such that we can label each element with an index in the range `0 <= index < len(sequence)`.\n",
+ "\n",
+ "To make `Vector` and `Matrix` instances emulate sequences, we implement the `.__len__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__len__)) and `.__getitem__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__getitem__)) methods. While the former returns the total number of elements in a container and is automatically invoked on any object passed to the built-in [len() ](https://docs.python.org/3/library/functions.html#len) function, the latter is invoked by the interpreter behind the scenes when we use the indexing operator `[]`.\n",
+ "\n",
+ "In the example, both `.__len__()` and `.__getitem__()` delegate parts of the work to the embedded `list` object named `._entries`. This is a design principle known as [delegation ](https://en.wikipedia.org/wiki/Delegation_%28object-oriented_programming%29) in software engineering. Also, we implicitly invoke the `.__len__()` method inside the `.__init__()` method already via the `len(self)` expression. This reuses code and also ensures that we calculate the number of entries in one way only within the entire class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(float(x) for x in data)\n",
+ " if len(self) == 0:\n",
+ " raise ValueError(\"a vector must have at least one entry\")\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self._entries)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __len__(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " def __getitem__(self, index):\n",
+ " return self._entries[index]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, we may obtain the number of elements with [len() ](https://docs.python.org/3/library/functions.html#len) and index into `Vector` instances."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(v)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "1.0"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v[0]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Negative indexes work \"out of the box\" because of the delegation to the internal `list` object."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3.0"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v[-1]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Somehow \"magically\" we can loop over `v` with a `for` statement. This works as Python simply loops over the indexes implied by `len(v)` and obtains the entries one by one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0 2.0 3.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in v:\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`v` may also be looped over in reverse order with the [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "3.0 2.0 1.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in reversed(v):\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Membership testing with the `in` operator also comes \"for free.\" Here, Python compares the object to be searched to each element with the `==` operator and stops early once one compares equal. That constitutes a [linear search ](https://en.wikipedia.org/wiki/Linear_search) as seen before."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 in v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "99 in v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So far, indexing is a *read-only* operation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "'Vector' object does not support item assignment",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mv\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m99\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: 'Vector' object does not support item assignment"
+ ]
+ }
+ ],
+ "source": [
+ "v[0] = 99"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Because a `Matrix` is two-dimensional, we must decide how we *flatten* the `._entries`. We *choose* to loop over the first row, then the second row, and so on. This is called a **[row major approach ](https://en.wikipedia.org/wiki/Row-_and_column-major_order)**.\n",
+ "\n",
+ "In addition to indexing by `int` objects, we also implement indexing by 2-`tuple`s of `int`s where the first element indicates the row and the second the column. Deciding what to do inside a method depending on the *type* of an argument is known as **type dispatching**. We achieve that with the built-in [isinstance() ](https://docs.python.org/3/library/functions.html#isinstance) function.\n",
+ "\n",
+ "Lastly, we ensure that integer indexing also works with negative values as we are used to from sequences in general.\n",
+ "\n",
+ "Note how all of the methods work together:\n",
+ "- `.__init__()`, `.__len__()`, and `.__getitem__()` reuse the `.n_rows` and `.n_cols` properties, and\n",
+ "- `.__init__()` and `.__getitem__()` invoke `.__len__()` via the `len(self)` expressions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "code_folding": [],
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " for row in self._entries[1:]:\n",
+ " if len(row) != self.n_cols:\n",
+ " raise ValueError(\"rows must have the same number of entries\")\n",
+ " if len(self) == 0:\n",
+ " raise ValueError(\"a matrix must have at least one entry\")\n",
+ "\n",
+ " @property\n",
+ " def n_rows(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " @property\n",
+ " def n_cols(self):\n",
+ " return len(self._entries[0])\n",
+ "\n",
+ " def __len__(self):\n",
+ " return self.n_rows * self.n_cols\n",
+ "\n",
+ " def __getitem__(self, index):\n",
+ " if isinstance(index, int):\n",
+ " if index < 0:\n",
+ " index += len(self)\n",
+ " if not (0 <= index < len(self)):\n",
+ " raise IndexError(\"integer index out of range\")\n",
+ " row, col = divmod(index, self.n_cols)\n",
+ " return self._entries[row][col]\n",
+ " elif (\n",
+ " isinstance(index, tuple) and len(index) == 2\n",
+ " and isinstance(index[0], int) and isinstance(index[1], int)\n",
+ " ):\n",
+ " return self._entries[index[0]][index[1]]\n",
+ " raise TypeError(\"index must be either an int or a tuple of two int's\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now, we may use a `Matrix` instance just like any other sequence ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "9"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(m)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "1.0"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m[0] # entry in the upper left corner"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "9.0"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m[-1] # entry in the lower right corner"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... but also index in the two dimensions separately."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3.0"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m[0, 2] # first row, third column"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "9.0"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m[-1, -1] # last row, last column / lower right corner"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As before, Python figures out the iteration on its own ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in m:\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in reversed(m):\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... and makes the `in` operator do a linear search."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 in m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "99 in m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "### The Python Data Model"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Sequence emulation itself is *not* a property of object-oriented languages in general. Instead, it is a behavior any data type may or may not exhibit in Python.\n",
+ "\n",
+ "The collection of all such behaviors a programming language offers is commonly referred to as its **object model**. In Python, the term **data model** is used instead and all possible behaviors are documented in the [language reference ](https://docs.python.org/3/reference/datamodel.html), in particular, in the section on special methods. We can think of the data model as the collection of all the behaviors we can make our user-defined data types follow. Pythonistas also use the term **protocol** instead of behavior, for example, we may say that \"the `Vector` and `Matrix` classes follow the sequence protocol.\"\n",
+ "\n",
+ "So, merely defining the *two* `.__len__()` and `.__getitem__()` methods is enough to make instances of any user-defined type behave like the built-in sequences in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/00_content.ipynb). Yet, there we defined sequences as all objects having the *four* properties of being finite, iterable, and ordered container types. And, these properties correspond to special methods by the names of `.__len__()`, `.__iter__()`, `.__reversed__()`, and `.__contains__()` as we see in the next section. Thus, Python \"magically\" knows how to derive the logic for the `.__iter__()`, `.__reversed__()`, and `.__contains__()` methods from the combination of the `.__len__()` and `.__getitem__()` methods. In general, while some special methods are related, others are not. Understanding these relationships means understanding the Python data model and vice versa. That is what every aspiring data scientist should aim for.\n",
+ "\n",
+ "On the contrary, we could also look at special methods individually. Whereas `.__len__()` is invoked on the object passed to [len() ](https://docs.python.org/3/library/functions.html#len), Python \"translates\" the indexing operator applied on any name like `a[i]`, for example, into the method invocation `a.__getitem__(i)`. So, in both cases, the special methods are executed according to a deterministic rule of the language. In that sense, they act as some sort of syntactic sugar. Thus, they even work if only one of them is defined. For example, without `.__len__()`, iteration with a `for`-loop still works but only in forward direction."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### More on Iteration"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "When implementing the sequence protocol for our `Matrix` class, we had to make the assumption that the user of our class wants to loop over the entries in a rows first fashion. While such assumptions can often be justified by referring to popular conventions (e.g., mathematicians usually look at matrices also in a \"row by column\" way), we could instead provide several iteration methods such that the user may choose one, just like `dict` objects come with several built-in methods that provide iteration.\n",
+ "\n",
+ "In the revised `Matrix` class below, we add the `.rows()`, `.cols()`, and `.entries()` methods that return `generator`s providing different and memory efficient ways of looping over the entries. `.rows()` and `.cols()` sequentially produce `Vector` instances representing individual rows and columns. This is in line with a popular idea in linear algebra to view a matrix as a collection of either row or column vectors. Further, `.entries()` by default produces the entries in the matrix one by one in a flat and row major fashion. Called with the optional `row_major=False` flag, it does the same in a column major fashion. The optional `reverse=True` flag allows iteration in backwards order.\n",
+ "\n",
+ "We also implement the `.__iter__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__iter__)) and `.__reversed__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__reversed__)) methods that immediately forward invocation to `.entries()`. So, Python does not need to fall back to `.__len__()` and `.__getitem__()` as we described above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " @property\n",
+ " def n_rows(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " @property\n",
+ " def n_cols(self):\n",
+ " return len(self._entries[0])\n",
+ "\n",
+ " def rows(self):\n",
+ " return (Vector(r) for r in self._entries)\n",
+ "\n",
+ " def cols(self):\n",
+ " return (\n",
+ " Vector(self._entries[r][c] for r in range(self.n_rows)) for c in range(self.n_cols)\n",
+ " )\n",
+ "\n",
+ " def entries(self, *, reverse=False, row_major=True):\n",
+ " if reverse:\n",
+ " rows, cols = (range(self.n_rows - 1, -1, -1), range(self.n_cols - 1, -1, -1))\n",
+ " else:\n",
+ " rows, cols = range(self.n_rows), range(self.n_cols)\n",
+ " if row_major:\n",
+ " return (self._entries[r][c] for r in rows for c in cols)\n",
+ " return (self._entries[r][c] for c in cols for r in rows)\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return self.entries()\n",
+ "\n",
+ " def __reversed__(self):\n",
+ " return self.entries(reverse=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The revised version of `Vector` below also works without `.__len__()` and `.__getitem__()` methods and leaves the creation of memory efficient `generator`s up to the embedded `list` object in `._entries` by using the built-in [iter() ](https://docs.python.org/3/library/functions.html#iter) and [reversed() ](https://docs.python.org/3/library/functions.html#reversed) functions. Also, `.__repr__()` now relies on the sequence protocol as the instance loops over \"itself\" with `for x in self`, a subtle reuse of code again. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return iter(self._entries)\n",
+ "\n",
+ " def __reversed__(self):\n",
+ " return reversed(self._entries)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Iteration works as before ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in m:\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9.0 8.0 7.0 6.0 5.0 4.0 3.0 2.0 1.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in reversed(m):\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... but now we have some ways of customizing it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Vector((1.0, 2.0, 3.0)) Vector((4.0, 5.0, 6.0)) Vector((7.0, 8.0, 9.0)) "
+ ]
+ }
+ ],
+ "source": [
+ "for row_vector in m.rows():\n",
+ " print(row_vector, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Vector((1.0, 4.0, 7.0)) Vector((2.0, 5.0, 8.0)) Vector((3.0, 6.0, 9.0)) "
+ ]
+ }
+ ],
+ "source": [
+ "for col_vector in m.cols():\n",
+ " print(col_vector, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in m.entries():\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "1.0 4.0 7.0 2.0 5.0 8.0 3.0 6.0 9.0 "
+ ]
+ }
+ ],
+ "source": [
+ "for entry in m.entries(row_major=False):\n",
+ " print(entry, end=\" \")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Mutability vs. Immutability"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the above implementations, the instance attribute `._entries` on a `Vector` or `Matrix` instance references either a `list` or a `list` of row `list`s , which is by the convention of the leading underscore `_` an implementation detail. If users of our classes adhere to this convention, `Vector` and `Matrix` instances can be regarded as *immutable*.\n",
+ "\n",
+ "In line with the implied immutability, we implemented the `.transpose()` method such that it returns a *new* `Matrix` instance. Instead, we could make the method change the internal `self._entries` attribute *in place* as we do in the next example. To indicate this mutation to the user of the `Matrix` class clearly, the revised `.transpose()` method returns `None`. That mirrors, for example, how the mutating methods of the built-in `list` type behave (cf., [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/01_content.ipynb#List-Methods)).\n",
+ "\n",
+ "Such decisions are better made consciously when designing a custom data type. The main trade-off is that immutable data types are typically easier to reason about when reading code whereas mutable data types tend to be more memory efficient and make programs faster as less copying operations take place in memory. However, this trade-off only becomes critical when we deal with big amounts of data."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " def transpose(self):\n",
+ " self._entries = list(list(float(x) for x in r) for r in (zip(*self._entries)))\n",
+ " return None"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Transposing `m` has *no* cell output ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m.transpose()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... so we must look at `m` again."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 4.0, 7.0,), (2.0, 5.0, 8.0,), (3.0, 6.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A downside of returning `None` is that we can *not* chain repeated invocations of `.transpose()`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "AttributeError",
+ "evalue": "'NoneType' object has no attribute 'transpose'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtranspose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtranspose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mAttributeError\u001b[0m: 'NoneType' object has no attribute 'transpose'"
+ ]
+ }
+ ],
+ "source": [
+ "m.transpose().transpose()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Enabling Method Chaining"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To fix the missing method chaining, we end the `.transpose()` method with `return self`, which returns a reference to the instance on which the method is invoked."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(list(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " def __iter__(self): # adapted for brevity; uses parts of entries()\n",
+ " rows, cols = range(len(self._entries)), range(len(self._entries[0]))\n",
+ " return (self._entries[r][c] for r in rows for c in cols)\n",
+ "\n",
+ " def transpose(self):\n",
+ " self._entries = list(list(float(x) for x in r) for r in (zip(*self._entries)))\n",
+ " return self"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The downside of this approach is that a user may unknowingly end up with *two* references to the *same* instance. That can only be mitigated by clear documentation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "n = m.transpose()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 4.0, 7.0,), (2.0, 5.0, 8.0,), (3.0, 6.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 4.0, 7.0,), (2.0, 5.0, 8.0,), (3.0, 6.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 44,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m is n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "### More on Indexing"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Analogous to the `.__getitem__()` method above, there are also the `.__setitem__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__setitem__)) and `.__delitem__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__delitem__)) methods that assign a new element to or delete an existing element from a sequence.\n",
+ "\n",
+ "Whereas deleting an individual entry in a `Vector` or `Matrix` instance may *not* really make sense semantically, we interpret this as setting the corresponding entry to \"unknown\" (i.e., `NaN`). Also, we implement changing individual entries via index assignment. Here, `.__setitem__()` delegates the assignment to the embedded `list` object after casting the assigned value as a `float`. While the example below only allows indexing by an integer, it could be generalized to slicing as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = list(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __getitem__(self, index):\n",
+ " return self._entries[index]\n",
+ "\n",
+ " def __setitem__(self, index, value):\n",
+ " self._entries[index] = float(value)\n",
+ "\n",
+ " def __delitem__(self, index):\n",
+ " self._entries[index] = float(\"NaN\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`v` can now be changed in place."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "del v[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((nan, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v[0] = 99"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((99.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "After this discussion of mutable `Vector` and `Matrix` classes, we continue with immutable implementations in the rest of this chapter. To lower the chance that we accidently design parts of our classes to be mutable, we replace the built-in [list() ](https://docs.python.org/3/library/functions.html#func-list) constructor with [tuple() ](https://docs.python.org/3/library/functions.html#func-tuple) in the `.__init__()` methods. As we learn in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/07_sequences/03_content.ipynb#Tuples-are-like-%22Immutable-Lists%22), `tuple`s are like immutable `list`s."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Polymorphism"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A function is considered **polymorphic** if it can work with *different* data types. The main advantage is reuse of the function's code. Polymorphism goes hand in hand with the concept of [duck typing ](https://en.wikipedia.org/wiki/Duck_typing), first mentioned in [Chapter 4 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/04_iteration/00_content.ipynb#Duck-Typing) in the context of input validation.\n",
+ "\n",
+ "We know polymorphic functions already: The built-in [sum() ](https://docs.python.org/3/library/functions.html#sum) function is a trivial example that works with all kinds of `iterable` arguments."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10"
+ ]
+ },
+ "execution_count": 52,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum((1, 2, 3, 4))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10"
+ ]
+ },
+ "execution_count": 53,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum([1, 2, 3, 4])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10"
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum({1, 2, 3, 4})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10"
+ ]
+ },
+ "execution_count": 55,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum({1: 996, 2: 997, 3: 998, 4: 999}) # loops over the keys"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As we implemented the `Vector` and `Matrix` classes to be iterable, we may pass them to [sum() ](https://docs.python.org/3/library/functions.html#sum) as well."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10.0"
+ ]
+ },
+ "execution_count": 56,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum(Vector([1, 2, 3, 4]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "10.0"
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "sum(Matrix([(1, 2), (3, 4)]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A polymorphic function with a semantic meaning in the context of linear algebra would be one that calculates the [Euclidean norm ](https://en.wikipedia.org/wiki/Norm_%28mathematics%29#Euclidean_norm) for vectors, which is a generalization of the popular [Pythagorean theorem ](https://en.wikipedia.org/wiki/Pythagorean_theorem). Extending the same kind of computation to a matrix results in the even more general [Frobenius norm ](https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm):"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "$$\\lVert \\bf{X} \\rVert_F = \\sqrt{ \\sum_{i=1}^m \\sum_{j=1}^n x_{ij}^2 }$$"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `norm()` function below can handle both a `Vector` or a `Matrix` instance and is therefore polymorphic. In this sense, `Vector` and `Matrix` instances \"walk\" and \"quack\" alike. In particular, they they both can provide all their entries as a flat sequence."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import math"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def norm(vec_or_mat):\n",
+ " \"\"\"Calculate the Frobenius or Euclidean norm of a matrix or vector.\n",
+ "\n",
+ " Args:\n",
+ " vec_or_mat (Vector / Matrix): object whose entries are squared and summed up\n",
+ "\n",
+ " Returns:\n",
+ " norm (float)\n",
+ " \"\"\"\n",
+ " return math.sqrt(sum(x ** 2 for x in vec_or_mat))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "While `norm()` is intended to work with `Vector` or `Matrix` instances ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5.477225575051661"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "norm(Vector([1, 2, 3, 4]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5.477225575051661"
+ ]
+ },
+ "execution_count": 61,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "norm(Matrix([(1, 2), (3, 4)]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... it also works for any sequence of numbers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5.477225575051661"
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "norm([1, 2, 3, 4])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "An important criterion if different classes are compatible in the sense that the same polymorphic function can work with them is that they implement the same **interface**.\n",
+ "\n",
+ "Whereas many other programming languages formalize this [concept ](https://en.wikipedia.org/wiki/Protocol_%28object-oriented_programming%29), in Python the term refers to the loose idea that different classes define the same attributes and implement the various protocols behind the special methods in a consistent way. This is what it means to \"walk\" and \"quack\" alike."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "livereveal": {
+ "auto_select": "code",
+ "auto_select_fragment": true,
+ "scroll": true,
+ "theme": "serif"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "384px"
+ },
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/03_content.ipynb b/11_classes/03_content.ipynb
new file mode 100644
index 0000000..32491d8
--- /dev/null
+++ b/11_classes/03_content.ipynb
@@ -0,0 +1,2412 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/03_content.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Chapter 11: Classes & Instances (continued)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The implementations of our `Vector` and `Matrix` classes so far do not know any of the rules of [linear algebra ](https://en.wikipedia.org/wiki/Linear_algebra). In this third part of the chapter, we add a couple of typical vector and matrix operations, for example, vector addition or matrix-vector multiplication, and some others. Before we do so, we briefly talk about how we can often model the *same* underlying data with a *different* data type."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Representations of Data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "source": [
+ "> \"If you change the way you look at things, the things you look at change.\"\n",
+ "> -- philosopher and personal coach [Dr. Wayne Dyer ](https://en.wikipedia.org/wiki/Wayne_Dyer)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Sometimes, it is helpful to view a `Vector` as a `Matrix` with either one row or one column. On the contrary, such a `Matrix` can always be interpreted as a `Vector` again. Changing the representation of the same underlying data (i.e., the `_entries`) can be viewed as \"changing\" an object's data type, for which, however, there is no built-in syntax.\n",
+ "\n",
+ "Thus, we implement the `.as_matrix()` and `.as_vector()` methods below that create *new* `Matrix` or `Vector` instances out of existing `Vector` or `Matrix` instances, respectively. Internally, both methods rely on the sequence protocol again (i.e., `for x in self`). Also, `.as_matrix()` interprets the `Vector` instance as a column vector by default (i.e., the `column=True` flag)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return iter(self._entries)\n",
+ "\n",
+ " def as_matrix(self, *, column=True):\n",
+ " if column:\n",
+ " return Matrix([x] for x in self)\n",
+ " return Matrix([(x for x in self)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(tuple(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " @property\n",
+ " def n_rows(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " @property\n",
+ " def n_cols(self):\n",
+ " return len(self._entries[0])\n",
+ "\n",
+ " def __iter__(self): # adapted for brevity; uses parts of .entries()\n",
+ " return (self._entries[r][c] for r in range(self.n_rows) for c in range(self.n_cols))\n",
+ "\n",
+ " def as_vector(self):\n",
+ " if not (self.n_rows == 1 or self.n_cols == 1):\n",
+ " raise RuntimeError(\"one dimension (m or n) must be 1\")\n",
+ " return Vector(x for x in self)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's interpret `v` as a column vector and create a matrix with dimension $3 \\times 1$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = v.as_matrix()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0,), (2.0,), (3.0,)))"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(3, 1)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.n_rows, m.n_cols"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By chaining `.as_matrix()` and `.as_vector()` we get a *new* `Vector` instance back that is equivalent to the given `v`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 8,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v.as_matrix().as_vector()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the same way, we can also interpret `v` as a row vector and create a $1 \\times 3$ matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = v.as_matrix(column=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,)))"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, 3)"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.n_rows, m.n_cols"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v.as_matrix(column=False).as_vector()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Interpreting a matrix as a vector only works if one of the two dimensions, $m$ or $n$, is $1$. If this requirement is not satisfied, we get the `RuntimeError` raised in `.as_vector()` above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "RuntimeError",
+ "evalue": "one dimension (m or n) must be 1",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_vector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36mas_vector\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mas_vector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 24\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"one dimension (m or n) must be 1\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 25\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mRuntimeError\u001b[0m: one dimension (m or n) must be 1"
+ ]
+ }
+ ],
+ "source": [
+ "m.as_vector()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Operator Overloading"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "By implementing special methods such as `.__add__()`, `.__sub__()`, `.__mul__()`, and some others, we can make user-defined data types emulate how numeric types operate with each other (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types)): Then, `Vector` and `Matrix` instances can be added together, subtracted from one another, or be multiplied together. We use them to implement the arithmetic rules from linear algebra.\n",
+ "\n",
+ "The OOP concept behind this is **[operator overloading ](https://en.wikipedia.org/wiki/Operator_overloading)** as first mentioned in the context of `str` concatenation in [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/01_elements/00_content.ipynb#Operator-Overloading)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Arithmetic Operators"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To understand the protocol behind arithmetic operators, we first look at the simple case of how an `int` object and a `float` object are added. The expression `1 + 2.0` is \"translated\" by Python into a method invocation of the form `1.__add__(2.0)`. This is why all the special methods behind binary operators take two arguments, `self` and, by convention, `other`. To allow binary operators to work with objects of *different* data types, Python expects the `.__add__()` method on the `1` object to return `NotImplemented` if it does not know how to deal with the `2.0` object and then proceeds by invoking the *reverse* special method `2.0.__radd__(1)`. With this protocol, one can create *new* data types that know how to execute arithmetic operations with *existing* data types *without* having to change the latter. By convention, the result of a binary operation should always be a *new* instance object and *not* a mutation of an existing one."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3.0"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "1 + 2.0"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Before implementing the arithmetic operators, we must first determine what other data types are allowed to interact with our `Vector` and `Matrix` instances and also how the two interact with each other. Conceptually, this is the same as to ask how strict we want the rules from linear algebra to be enforced in our model world. For example, while it is obvious that two vectors with the same number of entries may be added or subtracted, we could also allow a scalar value to be added to a vector. That seems awkward at first because it is an illegal operation in linear algebra. However, for convenience in our programs, we could interpret any scalar as a \"constants\" vector of the \"right size\" and add it to each entry in a `Vector`. This idea can be generalized into what is called **[broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html)** in [numpy](https://docs.scipy.org/doc/numpy/index.html). We often see \"dirty hacks\" like this in code. They are no bugs but features supposed to make the user of a library more productive.\n",
+ "\n",
+ "In this chapter, we model the following binary arithmetic operations:"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "- **Addition** / **Subtraction**\n",
+ " - `Vector` with `Vector` (if number of entries match; commutative)\n",
+ " - `Matrix` with `Matrix` (if dimensions $m$ and $n$ match; commutative)\n",
+ " - `Matrix` / `Vector` with scalar (the scalar is broadcasted; non-commutative for subtraction)\n",
+ "- **Multiplication**\n",
+ " - `Vector` with `Vector` ([dot product ](https://en.wikipedia.org/wiki/Dot_product) if number of entries match; commutative)\n",
+ " - `Matrix` with `Vector` (if dimensions are compatible; vector interpreted as column vector; non-commutative)\n",
+ " - `Vector` with `Matrix` (if dimensions are compatible; vector interpreted as row vector; non-commutative)\n",
+ " - `Matrix` / `Vector` with scalar ([scalar multiplication ](https://en.wikipedia.org/wiki/Scalar_multiplication); commutative)\n",
+ " - `Matrix` with `Matrix` ([matrix-matrix multiplication ](https://en.wikipedia.org/wiki/Matrix_multiplication) if dimensions are compatible; generally non-commutative)\n",
+ "- **Division**\n",
+ " - `Matrix` / `Vector` by a scalar (inverse of [scalar multiplication ](https://en.wikipedia.org/wiki/Scalar_multiplication); non-commutative)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "This listing shows the conceptual complexity behind the task of writing a \"little\" linear algebra library. Not to mention that some of the operations are [commutative ](https://en.wikipedia.org/wiki/Commutative_property) while others are not.\n",
+ "\n",
+ "As the available special methods correspond to the high-level grouping in the listing, we must implement a lot of **type dispatching** within them. This is why you see the built-in [isinstance() ](https://docs.python.org/3/library/functions.html#isinstance) function in most of the methods below. We use it to check if the `other` argument passed in is a `Vector` or `Matrix` instance or a scalar."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 16,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "isinstance(m, Vector)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 17,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "isinstance(v, Vector)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To check if `other` is a scalar, we need to specify what data type constitutes a scalar. We use a goose typing strategy as explained in [Chapter 5 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/02_content.ipynb#Goose-Typing): Any object that behaves like a `numbers.Number` from the [numbers ](https://docs.python.org/3/library/numbers.html) module in the [standard library ](https://docs.python.org/3/library/index.html) is considered a scalar.\n",
+ "\n",
+ "For example, the integer `1` is an instance of the built-in `int` type. At the same time, [isinstance() ](https://docs.python.org/3/library/functions.html#isinstance) also confirms that it is a `numbers.Number` in the abstract sense."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "isinstance(1, int)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import numbers"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "isinstance(1, numbers.Number)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Now with all the preparation work done, let's look at a \"minimal\" implementation of `Vector` that supports all the arithmetic operations specified above. *None* of the special methods inside the `Vector` class is aware that the `Matrix` class exists! Thus, all operations involving at least one `Matrix` instance are implemented only in the `Matrix` class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "code_folding": [],
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __len__(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return iter(self._entries)\n",
+ "\n",
+ " def __add__(self, other):\n",
+ " if isinstance(other, Vector): # vector addition\n",
+ " if len(self) != len(other):\n",
+ " raise ValueError(\"vectors must be of the same length\")\n",
+ " return Vector(x + y for (x, y) in zip(self, other))\n",
+ " elif isinstance(other, numbers.Number): # broadcasting addition\n",
+ " return Vector(x + other for x in self)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __radd__(self, other):\n",
+ " return self + other\n",
+ "\n",
+ " def __sub__(self, other):\n",
+ " if isinstance(other, Vector): # vector subtraction\n",
+ " if len(self) != len(other):\n",
+ " raise ValueError(\"vectors must be of the same length\")\n",
+ " return Vector(x - y for (x, y) in zip(self, other))\n",
+ " elif isinstance(other, numbers.Number): # broadcasting subtraction\n",
+ " return Vector(x - other for x in self)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __rsub__(self, other):\n",
+ " # Reverse vector subtraction is already handled in __sub__().\n",
+ " if isinstance(other, numbers.Number): # broadcasting subtraction\n",
+ " return Vector(other - x for x in self)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __mul__(self, other):\n",
+ " if isinstance(other, Vector): # dot product\n",
+ " if len(self) != len(other):\n",
+ " raise ValueError(\"vectors must be of the same length\")\n",
+ " return sum(x * y for (x, y) in zip(self, other))\n",
+ " elif isinstance(other, numbers.Number): # scalar multiplication\n",
+ " return Vector(x * other for x in self)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __rmul__(self, other):\n",
+ " return self * other\n",
+ "\n",
+ " def __truediv__(self, other):\n",
+ " if isinstance(other, numbers.Number):\n",
+ " return self * (1 / other)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def as_matrix(self, *, column=True):\n",
+ " if column:\n",
+ " return Matrix([x] for x in self)\n",
+ " return Matrix([(x for x in self)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "w = Vector([4, 5])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`.__mul__()` implements both scalar multiplication and the dot product of two `Vector`s. As both operations are commutative, `.__rmul__()` dispatches to `.__mul__()` via the `self * other` expression."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((2.0, 4.0, 6.0))"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "2 * v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((3.0, 6.0, 9.0))"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v * 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "14.0"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v * v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If two `Vector`s do *not* have a matching number of entries, a `ValueError` is raised."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "vectors must be of the same length",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mw\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__mul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# dot product\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 46\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 47\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors must be of the same length\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 48\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0my\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# scalar multiplication\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: vectors must be of the same length"
+ ]
+ }
+ ],
+ "source": [
+ "v * w"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`.__truediv__()` implements the ordinary division operator `/` while `.__floordiv__()` would implement the integer division operator `//`. Here, `.__truediv__()` dispatches to `.__mul__()` after inverting the `other` argument via the `self * (1 / other)` expression."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((0.3333333333333333, 0.6666666666666666, 1.0))"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v / 3"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`.__add__()` and `.__sub__()` implement vector addition and subtraction according to standard linear algebra rules, meaning that both `Vector`s must have the same number of entries or a `ValueError` is raised. Furthermore, both methods are able to broadcast the `other` argument to the dimension of a `Vector` and then execute either vector addition or subtraction. As addition is commutative, `.__radd__()` dispatches to `.__add__()`. For now, we have to explicitly implement `.__rsub__()`. Further below, we see how it can be re-factored to be commutative."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((2.0, 4.0, 6.0))"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v + v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((0.0, 0.0, 0.0))"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v - v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "vectors must be of the same length",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mv\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mw\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__add__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# vector addition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors must be of the same length\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 21\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# broadcasting addition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: vectors must be of the same length"
+ ]
+ }
+ ],
+ "source": [
+ "v + w"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((101.0, 102.0, 103.0))"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v + 100"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((99.0, 98.0, 97.0))"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "100 - v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For `Matrix` instances, the implementation is a bit more involved as we need to distinguish between matrix-matrix, matrix-vector, vector-matrix, and scalar multiplication and check for compatible dimensions. To review the underlying rules, check this [article ](https://en.wikipedia.org/wiki/Matrix_multiplication) or watch the video below."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChALCAgOCggIDRUNDhERExMTCAsWGBYSGBASExIBBQUFCAcIDwkJDhIODw8SEhIVEhISFRISEhISEhIVEhISFhISEhISEhISEhISEhISEhISEhISEhISEhISEhIeEv/AABEIAWgB4AMBIgACEQEDEQH/xAAdAAEAAgIDAQEAAAAAAAAAAAAABQgGBwEDBAIJ/8QAURAAAQQBAgMCCQcIBggGAgMAAQACAwQFBhESEyEHMQgUFSI0QVF0swkyVGFxktQjNkJSdYGRtCQ1cqGxwRY3Q2J2grK1M1Njc4PRRKIXJWT/xAAbAQEBAQEBAQEBAAAAAAAAAAAAAgMBBAYHBf/EADMRAQACAQMBBQYEBgMAAAAAAAABEQIDITESBEFRYXEFMoGRsfAGE9HxFCIjQsHhM1Jy/9oADAMBAAIRAxEAPwCmSIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICKXt4ZkUkkT71QPje6NwDbxHExxa7Yir1G4K6vJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VdtTDMlkjiZeqF8j2xtBbeA4nuDW7k1eg3IQdWqPTrvvdn4z1GqS1R6dd97s/Geo1AREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFJaX9Ope91vjMUapLS/p1L3ut8ZiBqj06773Z+M9RqktUenXfe7PxnqNQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBSWl/TqXvdb4zFGqS0v6dS97rfGYgao9Ou+92fjPUapLVHp133uz8Z6jUBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRqktL+nUve63xmIGqPTrvvdn4z1GqS1R6dd97s/Geo1AREQEREBERAREQEREBERBy0E9y7Z6ksY3fHIwb7bvY5o39gJHepDRx//saHvtX48a/VztR0dVz+Ju4m2PydyEsa/YOdBMNnQWGD9eOQMd9fDsehKD8jUUrq3AWcXet464wx2qc8tednXbjicW8TCQOOJw2c13c5rmkdCvPgcVYvWq9OrG6azamjrwRN24pJZXhkbBv0G7iOp6DvQdENSV+/BG94HQljHOAPsJaOhXU4EEgggg7EHoQR3gj2r9Z+xrQ1fTeFpYivwuNePexM0bGzbk8+zYO/XZ0hdsCT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uDm9CNd9ofbzPb1fV1Xiq7qctavXhFew4StlayORliKXl7cUMjZXs6bEDYjY7baURBbzJeFNpmR7cu3SLHakZG3l2pxUfHFM1vAx/jgbz3tYOgPLa7YbAt7xqjth7cp8vqPF6lx0L8fcxtKpCGvc2RpsQTWZpSAO+u/xlzOE9S3ffvWmkQW4n8JnSWQMORzOjmWc3CxoEzWU54XOi6xnnz7SBgPUB7Hlm/Qnbc6u7Ou2etjdZWdUSYpsUFnxvfHUHhrYTZjDd2PlGznFwL3HZoLpHEBo2aNMIg2/gO1yvW12/Vzqczq7rt2z4mJGc7htVJ6zW8wjg4gZg4/2SFzle12vNrxurhTmFYXK1nxMyM5/DBUiqlvMA4OImMu/eAtPog2x2r64l1bq+DI4mCWpZtz4yrRjkkY6VtthhhgfxNHCN5uEjvVlPlB9eOp4ejgGSDxnKPbYu8HQeKVHNcBw77tbLa4SO/pWkCp92Sa0dp7L1cuypBclp810UNhz2x8ySJ8Qk3jIPE3jJH1gH1Ar0dsvaHc1RlpstcayJ744YYoIi4xV4YWcLY4y/wA7YvMkh3/Sld9iDDEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRqktL+nUve63xmIGqPTrvvdn4z1Grvv2XTSyzOADpZHyODdw0OkcXENBJO259q6EBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRq76Fl0MsUzQC6KRkjQ7ctLo3BwDgCDtuPag6EREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQf/2Q==\n",
+ "text/html": [
+ "\n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from IPython.display import YouTubeVideo\n",
+ "YouTubeVideo(\"OMA2Mwo0aZg\", width=\"60%\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To summarize the video, the multiplication of two matrices $\\bf{A}$ and $\\bf{B}$ with dimensions $m$ by $n$ and $n$ by $p$ yields a matrix $\\bf{C}$ with dimensions $m$ and $p$. To obtain an entry $c_{ij}$ of matrix $\\bf{C}$ where $i$ and $j$ represent the index labels of the rows and columns, we have to calculate the dot product of the $i$th row vector of $\\bf{A}$ with the $j$th column vector of $\\bf{B}$. So, it makes a difference if we multiply $\\bf{A}$ with $\\bf{B}$ from the right or left.\n",
+ "\n",
+ "When multiplying a `Matrix` with a `Vector`, we follow the convention that a `Vector` on the left is interpreted as a row vector and a `Vector` on the right as a column vector. The `Vector`'s length must match the `Matrix`'s corresponding dimension. As row and column vectors can be viewed as a `Matrix` as well, matrix-vector multiplication is a special case of matrix-matrix multiplication."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In the revised `Matrix` class, the `.__add__()`, `.__radd__()`, `.__sub__()`, `.__rsub__()`, and `.__truediv__()` methods follow the same logic as in the `Vector` class above.\n",
+ "\n",
+ "Besides implementing scalar multiplication, `.__mul__()` and `.__rmul__()` are responsible for converting `Vector`s into `Matrix`s and back. In particular, all the different ways of performing a multiplication are reduced into *one* generic form, which is a matrix-matrix multiplication. That is achieved by the `._matrix_multiply()` method, another implementation detail."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Matrix:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(tuple(float(x) for x in r) for r in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
+ " return f\"Matrix(({args}))\"\n",
+ "\n",
+ " @property\n",
+ " def n_rows(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " @property\n",
+ " def n_cols(self):\n",
+ " return len(self._entries[0])\n",
+ "\n",
+ " def rows(self):\n",
+ " return (Vector(r) for r in self._entries)\n",
+ "\n",
+ " def cols(self):\n",
+ " return (\n",
+ " Vector(self._entries[r][c] for r in range(self.n_rows)) for c in range(self.n_cols)\n",
+ " )\n",
+ "\n",
+ " def __iter__(self): # adapted for brevity; uses parts of entries()\n",
+ " return (self._entries[r][c] for r in range(self.n_rows) for c in range(self.n_cols))\n",
+ "\n",
+ " def __add__(self, other):\n",
+ " if isinstance(other, Matrix): # matrix addition\n",
+ " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n",
+ " raise ValueError(\"matrices must have the same dimensions\")\n",
+ " return Matrix((s_col + o_col for (s_col, o_col) in zip(s_row, o_row))\n",
+ " for (s_row, o_row) in zip(self._entries, other._entries))\n",
+ " elif isinstance(other, numbers.Number): # broadcasting addition\n",
+ " return Matrix((c + other for c in r) for r in self._entries)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __radd__(self, other):\n",
+ " if isinstance(other, Vector):\n",
+ " raise TypeError(\"vectors and matrices cannot be added\")\n",
+ " return self + other\n",
+ "\n",
+ " def __sub__(self, other):\n",
+ " if isinstance(other, Matrix): # matrix subtraction\n",
+ " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n",
+ " raise ValueError(\"matrices must have the same dimensions\")\n",
+ " return Matrix((s_col - o_col for (s_col, o_col) in zip(s_row, o_row))\n",
+ " for (s_row, o_row) in zip(self._entries, other._entries))\n",
+ " elif isinstance(other, numbers.Number): # broadcasting subtraction\n",
+ " return Matrix((c - other for c in r) for r in self._entries)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __rsub__(self, other):\n",
+ " if isinstance(other, Vector):\n",
+ " raise TypeError(\"vectors and matrices cannot be subtracted\")\n",
+ " # Reverse matrix subtraction is already handled in __sub__().\n",
+ " if isinstance(other, numbers.Number): # broadcasting subtraction\n",
+ " return Matrix((other - c for c in r) for r in self._entries)\n",
+ " return NotImplemented\n",
+ " \n",
+ " def _matrix_multiply(self, other):\n",
+ " if self.n_cols != other.n_rows:\n",
+ " raise ValueError(\"matrices must have compatible dimensions\")\n",
+ " return Matrix((rv * cv for cv in other.cols()) for rv in self.rows())\n",
+ "\n",
+ " def __mul__(self, other):\n",
+ " if isinstance(other, numbers.Number):\n",
+ " return Matrix((x * other for x in r) for r in self._entries)\n",
+ " elif isinstance(other, Vector):\n",
+ " return self._matrix_multiply(other.as_matrix()).as_vector()\n",
+ " elif isinstance(other, Matrix):\n",
+ " return self._matrix_multiply(other)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __rmul__(self, other):\n",
+ " if isinstance(other, numbers.Number):\n",
+ " return self * other\n",
+ " elif isinstance(other, Vector):\n",
+ " return other.as_matrix(column=False)._matrix_multiply(self).as_vector()\n",
+ " return NotImplemented\n",
+ "\n",
+ " def __truediv__(self, other):\n",
+ " if isinstance(other, numbers.Number):\n",
+ " return self * (1 / other)\n",
+ " return NotImplemented\n",
+ "\n",
+ " def as_vector(self):\n",
+ " if not (self.n_rows == 1 or self.n_cols == 1):\n",
+ " raise RuntimeError(\"one dimension (m or n) must be 1\")\n",
+ " return Vector(x for x in self)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])\n",
+ "n = Matrix([(10, 11, 12), (13, 14, 15)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Scalar multiplication, addition, and subtraction work as before."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((10.0, 20.0, 30.0,), (40.0, 50.0, 60.0,), (70.0, 80.0, 90.0,)))"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10 * m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(2 * m + m * 3) / 5"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((0.0, 0.0, 0.0,), (0.0, 0.0, 0.0,), (0.0, 0.0, 0.0,)))"
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m - m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Matrix-matrix multiplication works if the dimensions are compatible ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((138.0, 171.0, 204.0,), (174.0, 216.0, 258.0,)))"
+ ]
+ },
+ "execution_count": 40,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "n * m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... and results in a `ValueError` otherwise."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "matrices must have compatible dimensions",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mm\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__mul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_vector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 73\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 74\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 75\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mNotImplemented\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 76\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m_matrix_multiply\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 65\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"matrices must have compatible dimensions\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 66\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcols\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: matrices must have compatible dimensions"
+ ]
+ }
+ ],
+ "source": [
+ "m * n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The same holds for matrix-vector and vector-matrix multiplication. These operations always return `Vector` instances in line with standard linear algebra. If a `Vector`'s length is not compatible with the respective dimension of a `Matrix`, we receive a `ValueError`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((14.0, 32.0, 50.0))"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m * v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((30.0, 36.0, 42.0))"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v * m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((68.0, 86.0))"
+ ]
+ },
+ "execution_count": 44,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "n * v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "matrices must have compatible dimensions",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__rmul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 79\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 80\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 81\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolumn\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_vector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 82\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mNotImplemented\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 83\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m_matrix_multiply\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 65\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"matrices must have compatible dimensions\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 66\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcols\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mValueError\u001b[0m: matrices must have compatible dimensions"
+ ]
+ }
+ ],
+ "source": [
+ "v * n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Lastly, the broadcasting addition and subtraction also work for our `Matrix` instances."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((101.0, 102.0, 103.0,), (104.0, 105.0, 106.0,), (107.0, 108.0, 109.0,)))"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m + 100"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((99.0, 98.0, 97.0,), (96.0, 95.0, 94.0,), (93.0, 92.0, 91.0,)))"
+ ]
+ },
+ "execution_count": 47,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "100 - m"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We do not allow addition or subtraction of matrices with vectors and raise a `TypeError` instead. Alternatively, we could have implemented broadcasting here as well, just like [numpy](https://www.numpy.org/) does."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "vectors and matrices cannot be added",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mm\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__radd__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__radd__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 28\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__sub__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__radd__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__radd__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 42\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors and matrices cannot be added\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 43\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mTypeError\u001b[0m: vectors and matrices cannot be added"
+ ]
+ }
+ ],
+ "source": [
+ "m + v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "### Relational Operators"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As we have seen before, two different `Vector`s with the same `._entries` do *not* compare equal. The reason is that for user-defined types Python by default only assumes two instances to be equal if they are actually the same object. This is, of course, semantically wrong for our `Vector`s and `Matrix`s."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])\n",
+ "w = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 50,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v == w"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 51,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v == v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We implement the `.__eq__()` (cf., [reference ](https://docs.python.org/3/reference/datamodel.html#object.__eq__)) method to control how the comparison operator `==` is carried out. For brevity, we show this only for the `Vector` class. The `.__eq__()` method exits early as soon as the first pair of entries does not match. Also, for reasons discussed in [Chapter 5 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/01_content.ipynb#Imprecision), we compare the absolute difference of two corresponding entries to a very small `zero_threshold` that is stored as a class attribute shared among all `Vector` instances. If the `Vector`s differ in their numbers of entries, we fail loudly and raise a `ValueError`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " zero_threshold = 1e-12\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __len__(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return iter(self._entries)\n",
+ "\n",
+ " def __eq__(self, other):\n",
+ " if isinstance(other, Vector):\n",
+ " if len(self) != len(other):\n",
+ " raise ValueError(\"vectors must be of the same length\")\n",
+ " for x, y in zip(self, other):\n",
+ " if abs(x - y) > self.zero_threshold:\n",
+ " return False # exit early if two corresponding entries differ\n",
+ " return True\n",
+ " return NotImplemented"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])\n",
+ "w = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 54,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v == w"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Besides `.__eq__()`, there are other special methods to implement the `!=`, `<`, `>`, `<=`, and `>=` operators, the last four of which are not semantically meaningful in the context of linear algebra.\n",
+ "\n",
+ "Python implicitly creates the `!=` for us in that it just inverts the result of `.__eq__()`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])\n",
+ "w = Vector([1, 2, 4])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 56,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v == w"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 57,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v != w"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Number Emulation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Our `Vector` and `Matrix` classes do not fully behave like a `numbers.Number` in the abstract sense. Besides the not yet talked about but useful unary `+` and `-` operators, numbers in Python usually support being passed to built-in functions like [abs() ](https://docs.python.org/3/library/functions.html#abs), [bin() ](https://docs.python.org/3/library/functions.html#bin), [bool() ](https://docs.python.org/3/library/functions.html#bool), [complex() ](https://docs.python.org/3/library/functions.html#complex), [float() ](https://docs.python.org/3/library/functions.html#float), [hex() ](https://docs.python.org/3/library/functions.html#hex), [int() ](https://docs.python.org/3/library/functions.html#int), and others (cf., the [reference ](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types) for an exhaustive list).\n",
+ "\n",
+ "To see that our classes lack simple but expected behaviors, let's try to invert the signs of all entries in the vector `v` ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "bad operand type for unary -: 'Vector'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m-\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: bad operand type for unary -: 'Vector'"
+ ]
+ }
+ ],
+ "source": [
+ "-v"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... or pass `v` to [abs() ](https://docs.python.org/3/library/functions.html#abs)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "bad operand type for abs(): 'Vector'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mabs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: bad operand type for abs(): 'Vector'"
+ ]
+ }
+ ],
+ "source": [
+ "abs(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For our example, we decide to implement the unary `+` and `-` operators, [abs() ](https://docs.python.org/3/library/functions.html#abs) as the Euclidean / Frobenius norm (i.e., the [magnitude ](https://en.wikipedia.org/wiki/Magnitude_%28mathematics%29#Euclidean_vector_space)), [bool() ](https://docs.python.org/3/library/functions.html#bool) to check if that norm is greater than `0` or not, and [float() ](https://docs.python.org/3/library/functions.html#float) to obtain a single scalar if the vector or matrix consists of only one entry. To achieve that, we implement the `.__pos__()`, `.__neg__()`, `.__abs__()`, `.__bool__()`, and `.__float__()` methods. For brevity, we do this only for the `Vector` class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import math"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "-"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "def norm(vec_or_mat):\n",
+ " \"\"\"Calculate the Frobenius or Euclidean norm of a matrix or vector.\"\"\"\n",
+ " return math.sqrt(sum(x ** 2 for x in vec_or_mat))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "class Vector:\n",
+ "\n",
+ " def __init__(self, data):\n",
+ " self._entries = tuple(float(x) for x in data)\n",
+ " # ...\n",
+ "\n",
+ " def __repr__(self):\n",
+ " args = \", \".join(repr(x) for x in self)\n",
+ " return f\"Vector(({args}))\"\n",
+ "\n",
+ " def __iter__(self):\n",
+ " return iter(self._entries)\n",
+ "\n",
+ " def __len__(self):\n",
+ " return len(self._entries)\n",
+ "\n",
+ " def __pos__(self):\n",
+ " return self\n",
+ "\n",
+ " def __neg__(self):\n",
+ " return Vector(-x for x in self)\n",
+ "\n",
+ " def __abs__(self):\n",
+ " return norm(self)\n",
+ "\n",
+ " def __bool__(self):\n",
+ " return bool(abs(self))\n",
+ "\n",
+ " def __float__(self):\n",
+ " if len(self) != 1:\n",
+ " raise RuntimeError(\"vector must have exactly one entry to become a scalar\")\n",
+ " return self._entries[0]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])\n",
+ "w = Vector([3, 4])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The unary `+` operator is not only conceptually an identity operator but simply returns the instance itself."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "+v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "+v is v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((-3.0, -4.0))"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "-w"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The magnitude of a `Vector` is only `0` if *all* entries are `0`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "5.0"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "abs(w)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "z = Vector([0, 0])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.0"
+ ]
+ },
+ "execution_count": 70,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "abs(z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Only an all `0`s `Vector` is `False` in a boolean context. As mentioned in [Chapter 3 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/03_conditionals/00_content.ipynb#Truthy-vs.-Falsy), commonly we view an *empty* sequence as falsy; however, as we do not allow `Vector`s without any entries, we choose the all `0`s alternative. In that regard, the `Vector` class does not behave like the built-in sequence types."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 71,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "True"
+ ]
+ },
+ "execution_count": 71,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "bool(v)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "False"
+ ]
+ },
+ "execution_count": 72,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "bool(z)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Lastly, single entry `Vector`s can be casted as scalars."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 73,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "s = Vector([42])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 74,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "42.0"
+ ]
+ },
+ "execution_count": 74,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "float(s)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "RuntimeError",
+ "evalue": "vector must have exactly one entry to become a scalar",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfloat\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m__float__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__float__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 31\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vector must have exactly one entry to become a scalar\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 32\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mRuntimeError\u001b[0m: vector must have exactly one entry to become a scalar"
+ ]
+ }
+ ],
+ "source": [
+ "float(v)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "livereveal": {
+ "auto_select": "code",
+ "auto_select_fragment": true,
+ "scroll": true,
+ "theme": "serif"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "384px"
+ },
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/04_content.ipynb b/11_classes/04_content.ipynb
new file mode 100644
index 0000000..b495a9a
--- /dev/null
+++ b/11_classes/04_content.ipynb
@@ -0,0 +1,2985 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud ](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/04_content.ipynb)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Chapter 11: Classes & Instances (continued)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In this fourth part of the chapter, we finalize our `Vector` and `Matrix` classes. As both `class` definitions have become rather lengthy, we learn how we to organize them into a Python package and import them in this Jupyter notebook. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Packages vs. Modules"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In [Chapter 2 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/02_functions/02_content.ipynb#Local-Modules-and-Packages), we introduce the concept of a Python module that is imported with the `import` statement. Essentially, a **module** is a single plain text \\*.py file on disk that contains Python code (e.g., [*sample_module.py* ](https://github.com/webartifex/intro-to-python/blob/develop/02_functions/sample_module.py) in [Chapter 2's folder ](https://github.com/webartifex/intro-to-python/tree/develop/02_functions)).\n",
+ "\n",
+ "Conceptually, a **package** is a generalization of a module whose code is split across several \\*.py to achieve a better organization of the individual parts. The \\*.py files are stored within a folder (e.g., [*sample_package* ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes/sample_package) in [Chapter 11's folder ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes)). In addition to that, a \"*\\_\\_init\\_\\_.py*\" file that may be empty must be put inside the folder. The latter is what the Python interpreter looks for to decide if a folder is a package or not.\n",
+ "\n",
+ "Let's look at an example with the final version of our `Vector` and `Matrix` classes.\n",
+ "\n",
+ "`!pwd` shows the location of this Jupyter notebook on the computer you are running [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) on: It is the local equivalent of [Chapter 11's folder ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes) in this book's [GitHub repository ](https://github.com/webartifex/intro-to-python)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "/home/webartifex/repos/intro-to-python/11_classes\n"
+ ]
+ }
+ ],
+ "source": [
+ "!pwd"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "`!ls` lists all the files and folders in the current location: These are Chapter 11's Jupyter notebooks (i.e., the \\*.ipynb files) and the [*sample_package* ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes/sample_package) folder. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "00_content.ipynb 02_content.ipynb 04_content.ipynb\n",
+ "01_exercises.ipynb 03_content.ipynb sample_package\n"
+ ]
+ }
+ ],
+ "source": [
+ "!ls"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "If we run `!ls` with the `sample_package` folder as the argument, we see the folder's contents: Four \\*.py files. Alternatively, you can use [JupyterLab' File Browser](https://jupyterlab.readthedocs.io/en/stable/user/interface.html?highlight=file%20browser#left-sidebar) on the left to navigate into the package."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "__init__.py matrix.py\tutils.py vector.py\n"
+ ]
+ }
+ ],
+ "source": [
+ "!ls sample_package"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The package is organized such that the [*matrix.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/matrix.py) and [*vector.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/vector.py) modules each define just one class, `Matrix` and `Vector`. That is intentional as both classes consist of several hundred lines of code and comments.\n",
+ "\n",
+ "The [*utils.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/utils.py) module contains code that is shared by both classes. Such code snippets are commonly called \"utilities\" or \"helpers,\" which explains the module's name.\n",
+ "\n",
+ "Finally, the [*\\_\\_init\\_\\_.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/__init__.py) file contains mostly meta information and defines what objects should be importable from the package's top level.\n",
+ "\n",
+ "With the `import` statement, we can import the entire package just as we would import a module from the [standard library ](https://docs.python.org/3/library/index.html)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import sample_package as pkg"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The above cell runs the code in the [*\\_\\_init\\_\\_.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/__init__.py) file from top to bottom, which in turn runs the [*matrix.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/matrix.py), [*utils.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/utils.py), and [*vector.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/vector.py) modules (cf., look at the `import` statements in the four \\*.py files to get the idea). As both [*matrix.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/matrix.py) and [*vector.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/vector.py) depend on each other (i.e., the `Matrix` class needs the `Vector` class to work and vice versa), understanding the order in that the modules are executed is not trivial. Without going into detail, we mention that Python guarantees that each \\*.py file is run only once and figures out the order on its own. If Python is unable to do that, for example, due to unresolvable cirular imports, it aborts with an `ImportError`.\n",
+ "\n",
+ "Below, `pkg` is an object of type `module` ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "module"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(pkg)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... and we use the built-in [dir() ](https://docs.python.org/3/library/functions.html#dir) function to check what attributes `pkg` comes with."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "['Matrix',\n",
+ " 'Vector',\n",
+ " '__all__',\n",
+ " '__author__',\n",
+ " '__builtins__',\n",
+ " '__cached__',\n",
+ " '__doc__',\n",
+ " '__file__',\n",
+ " '__loader__',\n",
+ " '__name__',\n",
+ " '__package__',\n",
+ " '__path__',\n",
+ " '__spec__',\n",
+ " '__version__',\n",
+ " 'matrix',\n",
+ " 'utils',\n",
+ " 'vector']"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "dir(pkg)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The package's meta information and documentation are automatically parsed from the [*\\_\\_init\\_\\_.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/__init__.py) file."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Help on package linear_algebra_tools:\n",
+ "\n",
+ "NAME\n",
+ " linear_algebra_tools - This package provides linear algebra functionalities.\n",
+ "\n",
+ "DESCRIPTION\n",
+ " The package is split into three modules:\n",
+ " - matrix: defines the Matrix class\n",
+ " - vector: defines the Vector class\n",
+ " - utils: defines the norm() function that is shared by Matrix and Vector\n",
+ " and package-wide constants\n",
+ " \n",
+ " The classes implement arithmetic operations involving vectors and matrices.\n",
+ " \n",
+ " See the docstrings in the modules and classes for further info.\n",
+ "\n",
+ "PACKAGE CONTENTS\n",
+ " matrix\n",
+ " utils\n",
+ " vector\n",
+ "\n",
+ "CLASSES\n",
+ " builtins.object\n",
+ " sample_package.matrix.Matrix\n",
+ " sample_package.vector.Vector\n",
+ " \n",
+ " class Matrix(builtins.object)\n",
+ " | Matrix(data)\n",
+ " | \n",
+ " | An m-by-n-dimensional matrix from linear algebra.\n",
+ " | \n",
+ " | All entries are converted to floats, or whatever is set in the typing attribute.\n",
+ " | \n",
+ " | Attributes:\n",
+ " | storage (callable): data type used to store the entries internally;\n",
+ " | defaults to tuple\n",
+ " | typing (callable): type casting applied to all entries upon creation;\n",
+ " | defaults to float\n",
+ " | vector_cls (vector.Vector): a reference to the Vector class to work with\n",
+ " | zero_threshold (float): max. tolerance when comparing an entry to zero;\n",
+ " | defaults to 1e-12\n",
+ " | \n",
+ " | Methods defined here:\n",
+ " | \n",
+ " | __abs__(self)\n",
+ " | The Frobenius norm of a Matrix.\n",
+ " | \n",
+ " | __add__(self, other)\n",
+ " | Handle `self + other` and `other + self`.\n",
+ " | \n",
+ " | This may be either matrix addition or broadcasting addition.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2), (3, 4)]) + Matrix([(2, 3), (4, 5)])\n",
+ " | Matrix(((3.0, 5.0,), (7.0, 9.0,)))\n",
+ " | \n",
+ " | >>> Matrix([(1, 2), (3, 4)]) + 5\n",
+ " | Matrix(((6.0, 7.0,), (8.0, 9.0,)))\n",
+ " | \n",
+ " | >>> 10 + Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((11.0, 12.0,), (13.0, 14.0,)))\n",
+ " | \n",
+ " | __bool__(self)\n",
+ " | A Matrix is truthy if its Frobenius norm is strictly positive.\n",
+ " | \n",
+ " | __eq__(self, other)\n",
+ " | Handle `self == other`.\n",
+ " | \n",
+ " | Compare two Matrix instances for equality.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2), (3, 4)]) == Matrix([(1, 2), (3, 4)])\n",
+ " | True\n",
+ " | \n",
+ " | >>> Matrix([(1, 2), (3, 4)]) == Matrix([(5, 6), (7, 8)])\n",
+ " | False\n",
+ " | \n",
+ " | __float__(self)\n",
+ " | Cast a Matrix as a scalar.\n",
+ " | \n",
+ " | Returns:\n",
+ " | scalar (float)\n",
+ " | \n",
+ " | Raises:\n",
+ " | RuntimeError: if the Matrix has more than one entry\n",
+ " | \n",
+ " | __getitem__(self, index)\n",
+ " | Obtain an individual entry of a Matrix.\n",
+ " | \n",
+ " | Args:\n",
+ " | index (int / tuple of int's): if index is an integer,\n",
+ " | the Matrix is viewed as a sequence in row-major order;\n",
+ " | if index is a tuple of integers, the first one refers to\n",
+ " | the row and the second one to the column of the entry\n",
+ " | \n",
+ " | Returns:\n",
+ " | entry (Matrix.typing)\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> m = Matrix([(1, 2), (3, 4)])\n",
+ " | >>> m[0]\n",
+ " | 1.0\n",
+ " | >>> m[-1]\n",
+ " | 4.0\n",
+ " | >>> m[0, 1]\n",
+ " | 2.0\n",
+ " | \n",
+ " | __init__(self, data)\n",
+ " | Create a new matrix.\n",
+ " | \n",
+ " | Args:\n",
+ " | data (sequence of sequences): the matrix's entries;\n",
+ " | viewed as a sequence of the matrix's rows (i.e., row-major order);\n",
+ " | use the .from_columns() class method if the data come as a sequence\n",
+ " | of the matrix's columns (i.e., column-major order)\n",
+ " | \n",
+ " | Raises:\n",
+ " | ValueError:\n",
+ " | - if no entries are provided\n",
+ " | - if the number of columns is inconsistent across the rows\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((1.0, 2.0,), (3.0, 4.0,)))\n",
+ " | \n",
+ " | __iter__(self)\n",
+ " | Loop over a Matrix's entries.\n",
+ " | \n",
+ " | See .entries() for more customization options.\n",
+ " | \n",
+ " | __len__(self)\n",
+ " | Number of entries in a Matrix.\n",
+ " | \n",
+ " | __mul__(self, other)\n",
+ " | Handle `self * other` and `other * self`.\n",
+ " | \n",
+ " | This may be either scalar multiplication, matrix-vector multiplication,\n",
+ " | vector-matrix multiplication, or matrix-matrix multiplication.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2), (3, 4)]) * Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((7.0, 10.0,), (15.0, 22.0,)))\n",
+ " | \n",
+ " | >>> 2 * Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((2.0, 4.0,), (6.0, 8.0,)))\n",
+ " | \n",
+ " | >>> Matrix([(1, 2), (3, 4)]) * 3\n",
+ " | Matrix(((3.0, 6.0,), (9.0, 12.0,)))\n",
+ " | \n",
+ " | Matrix-vector and vector-matrix multiplication are not commutative.\n",
+ " | \n",
+ " | >>> from sample_package import Vector\n",
+ " | \n",
+ " | >>> Matrix([(1, 2), (3, 4)]) * Vector([5, 6])\n",
+ " | Vector((17.0, 39.0))\n",
+ " | \n",
+ " | >>> Vector([5, 6]) * Matrix([(1, 2), (3, 4)])\n",
+ " | Vector((23.0, 34.0))\n",
+ " | \n",
+ " | __neg__(self)\n",
+ " | Handle `-self`.\n",
+ " | \n",
+ " | Negate all entries of a Matrix.\n",
+ " | \n",
+ " | __pos__(self)\n",
+ " | Handle `+self`.\n",
+ " | \n",
+ " | This is simply an identity operator returning the Matrix itself.\n",
+ " | \n",
+ " | __radd__(self, other)\n",
+ " | See docstring for .__add__().\n",
+ " | \n",
+ " | __repr__(self)\n",
+ " | Text representation of a Matrix.\n",
+ " | \n",
+ " | __reversed__(self)\n",
+ " | Loop over a Matrix's entries in reverse order.\n",
+ " | \n",
+ " | See .entries() for more customization options.\n",
+ " | \n",
+ " | __rmul__(self, other)\n",
+ " | See docstring for .__mul__().\n",
+ " | \n",
+ " | __rsub__(self, other)\n",
+ " | See docstring for .__sub__().\n",
+ " | \n",
+ " | __str__(self)\n",
+ " | Human-readable text representation of a Matrix.\n",
+ " | \n",
+ " | __sub__(self, other)\n",
+ " | Handle `self - other` and `other - self`.\n",
+ " | \n",
+ " | This may be either matrix subtraction or broadcasting subtraction.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(2, 3), (4, 5)]) - Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((1.0, 1.0,), (1.0, 1.0,)))\n",
+ " | \n",
+ " | >>> Matrix([(1, 2), (3, 4)]) - 1\n",
+ " | Matrix(((0.0, 1.0,), (2.0, 3.0,)))\n",
+ " | \n",
+ " | >>> 10 - Matrix([(1, 2), (3, 4)])\n",
+ " | Matrix(((9.0, 8.0,), (7.0, 6.0,)))\n",
+ " | \n",
+ " | __truediv__(self, other)\n",
+ " | Handle `self / other`.\n",
+ " | \n",
+ " | Divide a Matrix by a scalar.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2), (3, 4)]) / 4\n",
+ " | Matrix(((0.25, 0.5,), (0.75, 1.0,)))\n",
+ " | \n",
+ " | as_vector(self)\n",
+ " | Get a Vector representation of a Matrix.\n",
+ " | \n",
+ " | Returns:\n",
+ " | vector (vector.Vector)\n",
+ " | \n",
+ " | Raises:\n",
+ " | RuntimeError: if one of the two dimensions, .n_rows or .n_cols, is not 1\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix([(1, 2, 3)]).as_vector()\n",
+ " | Vector((1.0, 2.0, 3.0))\n",
+ " | \n",
+ " | cols(self)\n",
+ " | Loop over a Matrix's columns.\n",
+ " | \n",
+ " | Returns:\n",
+ " | columns (generator): produces a Matrix's columns as Vectors\n",
+ " | \n",
+ " | entries(self, *, reverse=False, row_major=True)\n",
+ " | Loop over a Matrix's entries.\n",
+ " | \n",
+ " | Args:\n",
+ " | reverse (bool): flag to loop backwards; defaults to False\n",
+ " | row_major (bool): flag to loop in row-major order; defaults to True\n",
+ " | \n",
+ " | Returns:\n",
+ " | entries (generator): produces a Matrix's entries\n",
+ " | \n",
+ " | rows(self)\n",
+ " | Loop over a Matrix's rows.\n",
+ " | \n",
+ " | Returns:\n",
+ " | rows (generator): produces a Matrix's rows as Vectors\n",
+ " | \n",
+ " | transpose(self)\n",
+ " | Switch the rows and columns of a Matrix.\n",
+ " | \n",
+ " | Returns:\n",
+ " | matrix (Matrix)\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> m = Matrix([(1, 2), (3, 4)])\n",
+ " | >>> m\n",
+ " | Matrix(((1.0, 2.0,), (3.0, 4.0,)))\n",
+ " | >>> m.transpose()\n",
+ " | Matrix(((1.0, 3.0,), (2.0, 4.0,)))\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Class methods defined here:\n",
+ " | \n",
+ " | from_columns(data) from builtins.type\n",
+ " | Create a new matrix.\n",
+ " | \n",
+ " | This is an alternative constructor for data provided in column-major order.\n",
+ " | \n",
+ " | Args:\n",
+ " | data (sequence of sequences): the matrix's entries;\n",
+ " | viewed as a sequence of the matrix's columns (i.e., column-major order);\n",
+ " | use the normal constructor method if the data come as a sequence\n",
+ " | of the matrix's rows (i.e., row-major order)\n",
+ " | \n",
+ " | Raises:\n",
+ " | ValueError:\n",
+ " | - if no entries are provided\n",
+ " | - if the number of rows is inconsistent across the columns\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Matrix.from_columns([(1, 2), (3, 4)])\n",
+ " | Matrix(((1.0, 3.0,), (2.0, 4.0,)))\n",
+ " | \n",
+ " | from_rows(data) from builtins.type\n",
+ " | See docstring for .__init__().\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Readonly properties defined here:\n",
+ " | \n",
+ " | n_cols\n",
+ " | Number of columns in a Matrix.\n",
+ " | \n",
+ " | n_rows\n",
+ " | Number of rows in a Matrix.\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data descriptors defined here:\n",
+ " | \n",
+ " | __dict__\n",
+ " | dictionary for instance variables (if defined)\n",
+ " | \n",
+ " | __weakref__\n",
+ " | list of weak references to the object (if defined)\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data and other attributes defined here:\n",
+ " | \n",
+ " | __hash__ = None\n",
+ " | \n",
+ " | storage = \n",
+ " | Built-in immutable sequence.\n",
+ " | \n",
+ " | If no argument is given, the constructor returns an empty tuple.\n",
+ " | If iterable is specified the tuple is initialized from iterable's items.\n",
+ " | \n",
+ " | If the argument is a tuple, the return value is the same object.\n",
+ " | \n",
+ " | typing = \n",
+ " | Convert a string or number to a floating point number, if possible.\n",
+ " | \n",
+ " | vector_cls = \n",
+ " | A one-dimensional vector from linear algebra.\n",
+ " | \n",
+ " | All entries are converted to floats, or whatever is set in the typing attribute.\n",
+ " | \n",
+ " | Attributes:\n",
+ " | matrix_cls (matrix.Matrix): a reference to the Matrix class to work with\n",
+ " | storage (callable): data type used to store the entries internally;\n",
+ " | defaults to tuple\n",
+ " | typing (callable): type casting applied to all entries upon creation;\n",
+ " | defaults to float\n",
+ " | zero_threshold (float): max. tolerance when comparing an entry to zero;\n",
+ " | defaults to 1e-12\n",
+ " | \n",
+ " | zero_threshold = 1e-12\n",
+ " \n",
+ " class Vector(builtins.object)\n",
+ " | Vector(data)\n",
+ " | \n",
+ " | A one-dimensional vector from linear algebra.\n",
+ " | \n",
+ " | All entries are converted to floats, or whatever is set in the typing attribute.\n",
+ " | \n",
+ " | Attributes:\n",
+ " | matrix_cls (matrix.Matrix): a reference to the Matrix class to work with\n",
+ " | storage (callable): data type used to store the entries internally;\n",
+ " | defaults to tuple\n",
+ " | typing (callable): type casting applied to all entries upon creation;\n",
+ " | defaults to float\n",
+ " | zero_threshold (float): max. tolerance when comparing an entry to zero;\n",
+ " | defaults to 1e-12\n",
+ " | \n",
+ " | Methods defined here:\n",
+ " | \n",
+ " | __abs__(self)\n",
+ " | The Euclidean norm of a vector.\n",
+ " | \n",
+ " | __add__(self, other)\n",
+ " | Handle `self + other` and `other + self`.\n",
+ " | \n",
+ " | This may be either vector addition or broadcasting addition.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([1, 2, 3]) + Vector([2, 3, 4])\n",
+ " | Vector((3.0, 5.0, 7.0))\n",
+ " | \n",
+ " | >>> Vector([1, 2, 3]) + 4\n",
+ " | Vector((5.0, 6.0, 7.0))\n",
+ " | \n",
+ " | >>> 10 + Vector([1, 2, 3])\n",
+ " | Vector((11.0, 12.0, 13.0))\n",
+ " | \n",
+ " | __bool__(self)\n",
+ " | A Vector is truthy if its Euclidean norm is strictly positive.\n",
+ " | \n",
+ " | __eq__(self, other)\n",
+ " | Handle `self == other`.\n",
+ " | \n",
+ " | Compare two Vectors for equality.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([1, 2, 3]) == Vector([1, 2, 3])\n",
+ " | True\n",
+ " | \n",
+ " | >>> Vector([1, 2, 3]) == Vector([4, 5, 6])\n",
+ " | False\n",
+ " | \n",
+ " | __float__(self)\n",
+ " | Cast a Vector as a scalar.\n",
+ " | \n",
+ " | Returns:\n",
+ " | scalar (float)\n",
+ " | \n",
+ " | Raises:\n",
+ " | RuntimeError: if the Vector has more than one entry\n",
+ " | \n",
+ " | __getitem__(self, index)\n",
+ " | Obtain an individual entry of a Vector.\n",
+ " | \n",
+ " | __init__(self, data)\n",
+ " | Create a new vector.\n",
+ " | \n",
+ " | Args:\n",
+ " | data (sequence): the vector's entries\n",
+ " | \n",
+ " | Raises:\n",
+ " | ValueError: if no entries are provided\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([1, 2, 3])\n",
+ " | Vector((1.0, 2.0, 3.0))\n",
+ " | \n",
+ " | >>> Vector(range(3))\n",
+ " | Vector((0.0, 1.0, 2.0))\n",
+ " | \n",
+ " | __iter__(self)\n",
+ " | Loop over a Vector's entries.\n",
+ " | \n",
+ " | __len__(self)\n",
+ " | Number of entries in a Vector.\n",
+ " | \n",
+ " | __mul__(self, other)\n",
+ " | Handle `self * other` and `other * self`.\n",
+ " | \n",
+ " | This may be either the dot product of two vectors or scalar multiplication.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([1, 2, 3]) * Vector([2, 3, 4])\n",
+ " | 20.0\n",
+ " | \n",
+ " | >>> 2 * Vector([1, 2, 3])\n",
+ " | Vector((2.0, 4.0, 6.0))\n",
+ " | \n",
+ " | >>> Vector([1, 2, 3]) * 3\n",
+ " | Vector((3.0, 6.0, 9.0))\n",
+ " | \n",
+ " | __neg__(self)\n",
+ " | Handle `-self`.\n",
+ " | \n",
+ " | Negate all entries of a Vector.\n",
+ " | \n",
+ " | __pos__(self)\n",
+ " | Handle `+self`.\n",
+ " | \n",
+ " | This is simply an identity operator returning the Vector itself.\n",
+ " | \n",
+ " | __radd__(self, other)\n",
+ " | See docstring for .__add__().\n",
+ " | \n",
+ " | __repr__(self)\n",
+ " | Text representation of a Vector.\n",
+ " | \n",
+ " | __reversed__(self)\n",
+ " | Loop over a Vector's entries in reverse order.\n",
+ " | \n",
+ " | __rmul__(self, other)\n",
+ " | See docstring for .__mul__().\n",
+ " | \n",
+ " | __rsub__(self, other)\n",
+ " | See docstring for .__sub__().\n",
+ " | \n",
+ " | __str__(self)\n",
+ " | Human-readable text representation of a Vector.\n",
+ " | \n",
+ " | __sub__(self, other)\n",
+ " | Handle `self - other` and `other - self`.\n",
+ " | \n",
+ " | This may be either vector subtraction or broadcasting subtraction.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([7, 8, 9]) - Vector([1, 2, 3])\n",
+ " | Vector((6.0, 6.0, 6.0))\n",
+ " | \n",
+ " | >>> Vector([1, 2, 3]) - 1\n",
+ " | Vector((0.0, 1.0, 2.0))\n",
+ " | \n",
+ " | >>> 10 - Vector([1, 2, 3])\n",
+ " | Vector((9.0, 8.0, 7.0))\n",
+ " | \n",
+ " | __truediv__(self, other)\n",
+ " | Handle `self / other`.\n",
+ " | \n",
+ " | Divide a Vector by a scalar.\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> Vector([9, 6, 12]) / 3\n",
+ " | Vector((3.0, 2.0, 4.0))\n",
+ " | \n",
+ " | as_matrix(self, *, column=True)\n",
+ " | Get a Matrix representation of a Vector.\n",
+ " | \n",
+ " | Args:\n",
+ " | column (bool): if the vector is interpreted as a\n",
+ " | column vector or a row vector; defaults to True\n",
+ " | \n",
+ " | Returns:\n",
+ " | matrix (matrix.Matrix)\n",
+ " | \n",
+ " | Example Usage:\n",
+ " | >>> v = Vector([1, 2, 3])\n",
+ " | >>> v.as_matrix()\n",
+ " | Matrix(((1.0,), (2.0,), (3.0,)))\n",
+ " | >>> v.as_matrix(column=False)\n",
+ " | Matrix(((1.0, 2.0, 3.0,)))\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data descriptors defined here:\n",
+ " | \n",
+ " | __dict__\n",
+ " | dictionary for instance variables (if defined)\n",
+ " | \n",
+ " | __weakref__\n",
+ " | list of weak references to the object (if defined)\n",
+ " | \n",
+ " | ----------------------------------------------------------------------\n",
+ " | Data and other attributes defined here:\n",
+ " | \n",
+ " | __hash__ = None\n",
+ " | \n",
+ " | matrix_cls = \n",
+ " | An m-by-n-dimensional matrix from linear algebra.\n",
+ " | \n",
+ " | All entries are converted to floats, or whatever is set in the typing attribute.\n",
+ " | \n",
+ " | Attributes:\n",
+ " | storage (callable): data type used to store the entries internally;\n",
+ " | defaults to tuple\n",
+ " | typing (callable): type casting applied to all entries upon creation;\n",
+ " | defaults to float\n",
+ " | vector_cls (vector.Vector): a reference to the Vector class to work with\n",
+ " | zero_threshold (float): max. tolerance when comparing an entry to zero;\n",
+ " | defaults to 1e-12\n",
+ " | \n",
+ " | storage = \n",
+ " | Built-in immutable sequence.\n",
+ " | \n",
+ " | If no argument is given, the constructor returns an empty tuple.\n",
+ " | If iterable is specified the tuple is initialized from iterable's items.\n",
+ " | \n",
+ " | If the argument is a tuple, the return value is the same object.\n",
+ " | \n",
+ " | typing = \n",
+ " | Convert a string or number to a floating point number, if possible.\n",
+ " | \n",
+ " | zero_threshold = 1e-12\n",
+ "\n",
+ "DATA\n",
+ " __all__ = ['Matrix', 'Vector']\n",
+ "\n",
+ "VERSION\n",
+ " 0.1.0\n",
+ "\n",
+ "AUTHOR\n",
+ " Alexander Hess\n",
+ "\n",
+ "FILE\n",
+ " /home/webartifex/repos/intro-to-python/11_classes/sample_package/__init__.py\n",
+ "\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "help(pkg)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The meta information could also be accessed separately and individually."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'linear_algebra_tools'"
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.__name__"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'0.1.0'"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.__version__ # follows the semantic versioning format"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We create `Vector` and `Matrix` instances in the usual way by calling the `Vector` and `Matrix` classes from the package's top level."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "A common practice by package authors is to put all the objects on the package's top level that they want the package users to work with directly. That is achieved via the `import` statements in the [*\\_\\_init\\_\\_.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/__init__.py) file.\n",
+ "\n",
+ "However, users can always reach into a package and work with its internals.\n",
+ "\n",
+ "For example, the `Vector` and `Matrix` classes are also available via their **qualified name** (cf., [PEP 3155 ](https://www.python.org/dev/peps/pep-3155/)): First, we access the `vector` and `matrix` modules on `pkg`, and then the `Vector` and `Matrix` classes on the modules."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.vector.Vector"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.vector.Vector"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "pkg.matrix.Matrix"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Also, let's import the [*utils.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/utils.py) module with the `norm()` function into the global scope. As this function is integrated into the `Vector.__abs__()` and `Matrix.__abs__()` methods, there is actually no need to work with it explicitly."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from sample_package import utils"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Help on function norm in module sample_package.utils:\n",
+ "\n",
+ "norm(vec_or_mat)\n",
+ " Calculate the Frobenius or Euclidean norm of a matrix or vector.\n",
+ " \n",
+ " Find more infos here: https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm\n",
+ " \n",
+ " Args:\n",
+ " vec_or_mat (Vector / Matrix): object whose entries are squared and summed up\n",
+ " \n",
+ " Returns:\n",
+ " norm (float)\n",
+ " \n",
+ " Example Usage:\n",
+ " As Vector and Matrix objects are by design non-empty sequences,\n",
+ " norm() may be called, for example, with `[3, 4]` as the argument:\n",
+ " >>> norm([3, 4])\n",
+ " 5.0\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "help(utils.norm)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Many tutorials on the internet begin by importing \"everything\" from a package into the global scope with `from ... import *`.\n",
+ "\n",
+ "That is commonly considered a *bad* practice as it may overwrite already existing variables. However, if the package's [*\\_\\_init\\_\\_.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/__init__.py) file defines an `__all__` attribute, a `list` with all the names to be \"exported,\" the **star import** is safe to be used, in particular, in *interactive* sessions like Jupyter notebooks. We emphasize that the star import should *not* be used *within* packages and modules as then it is not directly evident from a name where the corresponding object is defined.\n",
+ "\n",
+ "For more best practices regarding importing we refer to, among others, [Google's Python Style Guide](https://google.github.io/styleguide/pyguide.html#22-imports).\n",
+ "\n",
+ "The following `import` statement makes the `Vector` and `Matrix` classes available in the global scope."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "from sample_package import *"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.vector.Vector"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Matrix"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For further information on modules and packages, we refer to the [official tutorial ](https://docs.python.org/3/tutorial/modules.html)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## The final `Vector` and `Matrix` Classes"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The final implementations of the `Vector` and `Matrix` classes are in the [*matrix.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/matrix.py) and [*vector.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/vector.py) files: They integrate all of the functionalities introduced in this chapter. In addition, the code is cleaned up and fully documented, including examples of common usages.\n",
+ "\n",
+ "We strongly suggest the eager student go over the files in the [*sample_package* ](https://github.com/webartifex/intro-to-python/tree/develop/11_classes/sample_package) in detail at some point to understand what well-written and (re-)usable code looks like."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector([1, 2, 3])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 21,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.vector.Vector"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(v)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 24,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 25,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Furthermore, the classes are designed for easier maintenence in the long-run.\n",
+ "\n",
+ "For example, the `Matrix/Vector.storage` and `Matrix/Vector.typing` class attributes replace the \"hard coded\" [tuple() ](https://docs.python.org/3/library/functions.html#func-tuple) and [float() ](https://docs.python.org/3/library/functions.html#float) built-ins in the `.__init__()` methods: As `self.storage` and `self.typing` are not defined on the *instances*, Python automatically looks them up on the *classes*."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tuple"
+ ]
+ },
+ "execution_count": 26,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector.storage"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "-"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "float"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector.typing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[0;31mSignature:\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mSource:\u001b[0m \n",
+ " \u001b[0;32mdef\u001b[0m \u001b[0m__init__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Create a new vector.\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Args:\u001b[0m\n",
+ "\u001b[0;34m data (sequence): the vector's entries\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Raises:\u001b[0m\n",
+ "\u001b[0;34m ValueError: if no entries are provided\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Example Usage:\u001b[0m\n",
+ "\u001b[0;34m >>> Vector([1, 2, 3])\u001b[0m\n",
+ "\u001b[0;34m Vector((1.0, 2.0, 3.0))\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m >>> Vector(range(3))\u001b[0m\n",
+ "\u001b[0;34m Vector((0.0, 1.0, 2.0))\u001b[0m\n",
+ "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstorage\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtyping\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"a vector must have at least one entry\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/11_classes/sample_package/vector.py\n",
+ "\u001b[0;31mType:\u001b[0m function\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Vector.__init__??"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Both `Matrix/Vector.storage` and `Matrix/Vector.typing` themselves reference the `DEFAULT_ENTRIES_STORAGE` and `DEFAULT_ENTRY_TYPE` constants in the [*utils.py* ](https://github.com/webartifex/intro-to-python/blob/develop/11_classes/sample_package/utils.py) module. This way, we could, for example, change only the constants and thereby also change how the `._entries` are stored internally in both classes. Also, this single **[single source of truth ](https://en.wikipedia.org/wiki/Single_source_of_truth)** ensures that both classes are consistent with each other at all times."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "tuple"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "utils.DEFAULT_ENTRIES_STORAGE"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "-"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "float"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "utils.DEFAULT_ENTRY_TYPE"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For the same reasons, we also replace the \"hard coded\" references to the `Vector` and `Matrix` classes within the various methods.\n",
+ "\n",
+ "Every instance object has an automatically set `.__class__` attribute referencing its class."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.__class__"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Of course, we could also use the [type() ](https://docs.python.org/3/library/functions.html#type) built-in instead."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "type(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "So, for example, the `Matrix.transpose()` method makes a `self.__class__(...)` instead of a `Matrix(...)` call."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[0;31mSignature:\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtranspose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mSource:\u001b[0m \n",
+ " \u001b[0;32mdef\u001b[0m \u001b[0mtranspose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Switch the rows and columns of a Matrix.\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Returns:\u001b[0m\n",
+ "\u001b[0;34m matrix (Matrix)\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Example Usage:\u001b[0m\n",
+ "\u001b[0;34m >>> m = Matrix([(1, 2), (3, 4)])\u001b[0m\n",
+ "\u001b[0;34m >>> m\u001b[0m\n",
+ "\u001b[0;34m Matrix(((1.0, 2.0,), (3.0, 4.0,)))\u001b[0m\n",
+ "\u001b[0;34m >>> m.transpose()\u001b[0m\n",
+ "\u001b[0;34m Matrix(((1.0, 3.0,), (2.0, 4.0,)))\u001b[0m\n",
+ "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/11_classes/sample_package/matrix.py\n",
+ "\u001b[0;31mType:\u001b[0m function\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Matrix.transpose??"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Whenever we need a `str` representation of a class's name, we use the `.__name__` attribute on the class, ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Matrix'"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Matrix.__name__"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... or access it via the `.__class__` attribute on an instance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'Matrix'"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m.__class__.__name__"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For example, the `.__repr__()` and `.__str__()` methods make use of that."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[0;31mSignature:\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mSource:\u001b[0m \n",
+ " \u001b[0;32mdef\u001b[0m \u001b[0m__repr__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Text representation of a Matrix.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0mname\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__name__\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0margs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\", \"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m\"(\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\", \"\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrepr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mc\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mc\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\",)\"\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mr\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0;34mf\"{name}(({args}))\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/11_classes/sample_package/matrix.py\n",
+ "\u001b[0;31mType:\u001b[0m function\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Matrix.__repr__??"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "In order to not have to \"hard code\" the name of *another* class (e.g., the `Vector.as_matrix()` method references the `Matrix` class), we apply the following \"hack:\" First, we store a reference to the other class as a class attribute (e.g., `Matrix.vector_cls` and `Vector.matrix_cls`), and then reference that attribute within the methods, just like `.storage` and `.typing` above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.vector.Vector"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Matrix.vector_cls"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "sample_package.matrix.Matrix"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Vector.matrix_cls"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "As an example, the `Vector.as_matrix()` method makes a `self.matrix_cls(...)` instead of a `Matrix(...)` call."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "\u001b[0;31mSignature:\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mas_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolumn\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mSource:\u001b[0m \n",
+ " \u001b[0;32mdef\u001b[0m \u001b[0mas_matrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolumn\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;34m\"\"\"Get a Matrix representation of a Vector.\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Args:\u001b[0m\n",
+ "\u001b[0;34m column (bool): if the vector is interpreted as a\u001b[0m\n",
+ "\u001b[0;34m column vector or a row vector; defaults to True\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Returns:\u001b[0m\n",
+ "\u001b[0;34m matrix (matrix.Matrix)\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m Example Usage:\u001b[0m\n",
+ "\u001b[0;34m >>> v = Vector([1, 2, 3])\u001b[0m\n",
+ "\u001b[0;34m >>> v.as_matrix()\u001b[0m\n",
+ "\u001b[0;34m Matrix(((1.0,), (2.0,), (3.0,)))\u001b[0m\n",
+ "\u001b[0;34m >>> v.as_matrix(column=False)\u001b[0m\n",
+ "\u001b[0;34m Matrix(((1.0, 2.0, 3.0,)))\u001b[0m\n",
+ "\u001b[0;34m \"\"\"\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mcolumn\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmatrix_cls\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\n",
+ "\u001b[0;34m\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmatrix_cls\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mFile:\u001b[0m ~/repos/intro-to-python/11_classes/sample_package/vector.py\n",
+ "\u001b[0;31mType:\u001b[0m function\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Vector.as_matrix??"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "For completeness sake, we mention that in the final `Vector` and `Matrix` classes, the `.__sub__()` and `.__rsub__()` methods use the negation operator implemented in `.__neg__()` and then dispatch to `.__add__()` instead of implementing the subtraction logic themselves."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## \"Real-life\" Experiment"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Let's do some math with bigger `Matrix` and `Vector` instances."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import random"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "random.seed(42)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We initialize `m` as a $100x50$ dimensional `Matrix` with random numbers in the range between `0` and `1_000`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "m = Matrix((1_000 * random.random() for _ in range(50)) for _ in range(100))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We quickly lose track with all the numbers in the `Matrix`, which is why we implemented the `__str__()` method as a summary representation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((639.4267984578837, 25.010755222666937, 275.02931836911927, 223.21073814882274, 736.4712141640124, 676.6994874229113, 892.1795677048455, 86.93883262941615, 421.9218196852704, 29.797219438070343, 218.63797480360336, 505.3552881033624, 26.535969683863627, 198.8376506866485, 649.8844377795232, 544.9414806032166, 220.4406220406967, 589.2656838759087, 809.4304566778267, 6.498759678061017, 805.8192518328079, 698.1393949882269, 340.25051651799185, 155.47949981178155, 957.2130722067812, 336.59454511262675, 92.7458433801479, 96.71637683346401, 847.4943663474597, 603.7260313668911, 807.1282732743801, 729.7317866938179, 536.2280914547007, 973.1157639793706, 378.5343772083535, 552.040631273227, 829.4046642529948, 618.5197523642461, 861.7069003107772, 577.352145256762, 704.5718362149235, 45.82438365566222, 227.89827565154687, 289.38796360210716, 79.79197692362749, 232.79088636103018, 101.00142940972911, 277.97360311009214, 635.6844442644002, 364.8321789700842,), (370.18096711688264, 209.50703077148768, 266.97782204911334, 936.6545877124939, 648.0353852465936, 609.1310056669882, 171.13864819809697, 729.1267979503492, 163.40249376192838, 379.4554417576478, 989.5233506365953, 639.9997598540929, 556.9497437746462, 684.6142509898746, 842.8519201898096, 775.9999115462448, 229.04807196410437, 32.10024390403776, 315.45304805908194, 267.74087597570275, 210.98284358632645, 942.9097143350544, 876.3676264726688, 314.67788079847793, 655.43866529488, 395.63190106066423, 914.5475897405435, 458.8518525873988, 264.88016649805246, 246.62750769398346, 561.3681341631508, 262.7416085229353, 584.5859902235405, 897.822883602477, 399.4005051403973, 219.32075915728333, 997.5376064951103, 509.5262936764645, 90.9094121737939, 47.11637542473457, 109.64913035065915, 627.44604170309, 792.0793643629642, 422.159966799684, 63.52770615195713, 381.61928650653675, 996.1213802400968, 529.114345099137, 971.0783776136182, 860.779702234498,), (11.481021942819636, 720.7218193601947, 681.7103690265748, 536.9703304087951, 266.82518995254276, 640.961798579808, 111.55217359587644, 434.765250669105, 453.72370632920644, 953.8159275210801, 875.852940378194, 263.38905075109074, 500.5861130502983, 178.65188053013136, 912.6278393448205, 870.5185698367669, 298.4447914486329, 638.9494948660051, 608.9702114381723, 152.83926854963482, 762.5108000751512, 539.3790301196258, 778.6264786305583, 530.3536721951775, 0.5718961279435053, 324.1560570046731, 19.476742385832303, 929.0986162646171, 878.7218778231842, 831.6655293611794, 307.5141254026614, 57.92516649418755, 878.0095992040406, 946.9494452979941, 85.65345206787877, 485.9904633166138, 69.2125184683836, 760.6021652572316, 765.8344293069878, 128.3914644997628, 475.28237809873133, 549.8035934949439, 265.0566289400591, 872.4330410852574, 423.13794020088693, 211.79820544208206, 539.2960887794584, 729.9310690899762, 201.1510633896959, 311.71629130089497,), (995.1493566608947, 649.8780576394535, 438.10008391450407, 517.5758410355907, 121.00419586826573, 224.69733703155737, 338.08556214745533, 588.3087184572333, 230.114732596577, 220.21738445155947, 70.99308600903254, 631.102957270099, 228.94178381115438, 905.420013006128, 859.6354002537465, 70.85734988865345, 238.00463436899523, 668.9777782962806, 214.2368073704386, 132.311848725025, 935.514240580671, 571.0430933252844, 472.67102631179415, 784.6194242907534, 807.4969977666434, 190.4099143618777, 96.93081422882332, 431.0511824063775, 423.57862301992077, 467.024668036675, 729.0758494598506, 673.3645472933015, 984.1652113659661, 98.41787115195888, 402.62128210226876, 339.30260539496317, 861.6725363527911, 248.65633392028565, 190.2089084408115, 448.6135478331319, 421.8816398344042, 278.5451446669405, 249.80644788210049, 923.2655992760128, 443.13074505345696, 861.3491047618306, 550.3253124498481, 50.58832952488124, 999.2824684127266, 836.0275850799519,), (968.9962572847512, 926.3669830081276, 848.6957344143054, 166.31111060391402, 485.64112545071845, 213.74729919918167, 401.0402925494526, 58.635399972178924, 378.9731189769161, 985.308843779726, 265.20305817215194, 784.0706019485693, 455.0083673391433, 423.00748599016293, 957.3176408596732, 995.4226894927139, 555.7683234056182, 718.408275296326, 154.79682527406413, 296.7078254945642, 968.7093649691589, 579.1802908162562, 542.1952013742742, 747.9755603790641, 57.16527290748308, 584.1775944589713, 502.85038291951355, 852.7198920482854, 157.43272793948327, 960.7789032744504, 80.11146524058688, 185.8249609807232, 595.0351064500277, 675.2125536040902, 235.2038950009312, 119.88661394712419, 890.2873141294375, 246.21534778862485, 594.5191535334412, 619.3815103321031, 419.2249153358725, 583.6722892912247, 522.7827155319588, 934.7062577364272, 204.25919942353644, 716.1918007894149, 238.68595261584602, 395.7858467912545, 671.6902229599713, 299.99707979876223,), (316.17719627185403, 751.8644924144021, 72.54311449315732, 458.2855226185861, 998.4544408544424, 996.0964478550944, 73.260721099633, 213.1543122670404, 265.20041475040136, 933.2593779937091, 880.8641736864396, 879.2702424845428, 369.52708873888395, 157.74683235723197, 833.744954639807, 703.539925087371, 611.6777657259502, 987.2330636315044, 653.9763177107326, 7.823107152157949, 817.1041351154616, 299.3787521999779, 663.3887149660774, 938.9300039271039, 134.2911143933677, 115.4286704191022, 107.03597770941764, 553.223640884816, 272.3482123148163, 604.8298270302239, 717.6121871387979, 203.59731232745293, 634.2379588850797, 263.98390163040943, 488.53185214937656, 905.3364910793232, 846.1037132948554, 92.29846771273343, 423.57577256372633, 276.68022397225167, 3.5456890877823, 771.119223019627, 637.1133773013796, 261.9552624343482, 741.2309083479308, 551.6804211263913, 427.68691898067937, 9.669699608339965, 75.24386007376704, 883.1063933001429,), (903.9285715598932, 545.5902892055224, 834.5950198860166, 582.509566489794, 148.09378556748266, 127.44551928213876, 308.2583499301337, 898.9814887425899, 796.1223048880418, 860.7025820009028, 898.9246365264746, 210.07653833975405, 249.52973922292443, 102.79362167178563, 780.1162418714426, 884.1347014510089, 406.3773898321168, 620.6615101507128, 154.55333833220465, 929.8810156936744, 864.605696219964, 976.2060329309629, 810.7717199403969, 881.4162046633244, 24.786361898188723, 736.5644717550821, 332.1854679464287, 930.8158860483255, 802.2351389371389, 864.0640283752793, 810.749316574389, 266.80570959447203, 787.3745091354712, 108.09562640295711, 872.1667829060897, 858.5932513377817, 222.43371754566442, 816.586605596929, 460.3032346789421, 305.1908673386006, 795.3454991528617, 227.59548740777035, 23.66443470145152, 193.12978832770867, 328.26195119770654, 864.3529420302864, 966.8891040483611, 279.1249927218714, 641.4817386076277, 399.6783843600609,), (981.1496871982602, 536.2157324787219, 939.2371403247157, 115.34175185142759, 970.400611022228, 178.56781617246364, 962.5343157615555, 265.4663625229686, 108.40254721471109, 434.56375856464433, 728.5450606527043, 313.67731419499125, 606.2088533061433, 511.4230596694781, 385.1954333447272, 576.5880434965995, 254.72250613858193, 708.7852838341706, 1.6912782186294661, 925.5751654990827, 538.4519970927919, 719.4299991448455, 741.9500778394765, 670.6285044329995, 364.2214717812642, 69.97381112631018, 664.2376849112724, 330.2000360425964, 313.91564505835964, 848.0152795063354, 719.7542630139502, 300.3222682112642, 309.28466220865323, 408.3929086192168, 402.40038705772463, 295.655202525947, 127.28779905915322, 420.4463337729083, 940.363670730183, 677.3179452727329, 902.8055457325827, 615.5149159513805, 300.9498745655653, 547.9372131356982, 0.4059396972875273, 286.91371686892717, 429.8881499898346, 579.984781195682, 654.7056237030716, 464.9881902470142,), (442.1597993048074, 213.70140098910028, 473.18618590932624, 901.1808258282542, 796.0247601267803, 169.69139619805475, 84.79553672512175, 515.4520099152164, 632.9408557657957, 335.1882554098009, 818.4234645366643, 751.1381375407323, 672.795670557167, 224.64066599728827, 199.12993272657664, 24.425387726826344, 244.84254407835016, 475.1363442188051, 849.737694624732, 72.82822918456911, 414.44101099771933, 629.7653807377137, 194.4352367397093, 696.3542504905049, 494.37716901043694, 243.98443957843884, 656.058011111784, 5.54481813803176, 750.9644766184729, 770.0461885740251, 106.58729656353894, 425.1461939427341, 175.88668170653165, 957.9660422795397, 517.9577504437408, 50.21838514064092, 249.19827965997166, 848.3363473516597, 456.4618254701725, 801.4166017222644, 667.5777325863531, 987.8924530664481, 595.4523184694197, 950.0396084431559, 891.425925810437, 612.6523227617629, 719.273961275967, 504.778164824402, 830.569169721415, 547.8719506108284,), (897.2081032332621, 743.6554421595849, 474.6744368230533, 259.19154846501937, 247.23973750965956, 637.6614367761563, 765.8136842971654, 521.2998128279821, 626.7484369817813, 274.59744691753826, 77.48335386473582, 285.7281508631525, 271.7151070821846, 319.7095684187623, 540.1522225184564, 138.37406151615573, 231.26147972818677, 693.9498122990523, 706.4191416945522, 64.22885071387807, 407.5993696665867, 542.6111405039153, 415.77423410315595, 206.83438951441025, 420.14351777342563, 904.838478340177, 584.0794142042251, 695.5229864979681, 856.7320323039343, 765.5945761180694, 380.38102892771167, 5.8960835839930725, 351.7588026718247, 753.4751250593858, 853.4479505691046, 953.4303384701062, 419.0212826298219, 747.5156689780508, 546.1323097338391, 603.2525889412414, 220.5386943238189, 219.42163462143617, 435.8359760466366, 29.02481994671524, 336.12954369838246, 679.1418850283497, 404.31666913763706, 165.04473120350883, 467.39014923231014, 127.6277972811607,), (622.2569609740647, 26.96645190513769, 394.0202563397047, 564.3919830247742, 27.102046340312434, 642.7496480093357, 135.69948723056424, 461.69844405151173, 50.28463348862755, 379.1038641881396, 211.66028421148152, 326.84580488130854, 761.2297078940271, 379.12621556413626, 752.0098235547848, 831.9242851552726, 252.2715317823806, 81.90623276164256, 19.38328705001069, 539.4190479225337, 999.9078285092093, 349.9603437201839, 650.144093249875, 781.2330496108949, 651.7546552438894, 754.2332040595262, 949.6117327159889, 199.3606823625329, 20.380017320332342, 152.38234578479037, 126.22097487420625, 669.4588446199107, 563.9695819300191, 217.96454090650363, 699.4649712461509, 766.8980983562408, 167.78914336780227, 607.2474938909317, 747.9256519552858, 114.53287137889767, 819.3011743110851, 964.7207730340876, 108.09874965760658, 25.678425497466016, 311.9572443946952, 677.3472868504088, 958.1728382058959, 396.65444151662734, 715.0147050494684, 75.99647784305996,), (690.6144159329804, 627.2423956010444, 101.90130544597653, 772.480884951224, 850.2932390887963, 600.4116148168441, 121.05506506731511, 983.8443515146713, 782.6353463610196, 347.20376530844896, 428.37801323474446, 370.5708762180562, 505.9607896770779, 341.2311748612832, 849.5756269995774, 822.330918090058, 105.53887064399858, 960.7875672145785, 635.585106101446, 828.7073110024583, 707.308643706077, 435.48714500767704, 733.7953040133918, 965.4737312380777, 270.0823963874008, 808.1992188067559, 538.1729064482557, 483.49750388319615, 435.5744930038943, 731.0262143051223, 268.3955380492253, 851.7131600193319, 830.7310188906034, 86.6628980556744, 881.631184002772, 243.863439190956, 464.7084666032317, 610.3317042305206, 378.98930412826405, 28.69999777008958, 850.9528363124591, 181.8398571576918, 212.119850179739, 797.8323568280965, 340.3388431642836, 880.3199797582254, 701.1837503322016, 276.26857575618493, 10.151114438677112, 948.0625777770312,), (85.61296195802126, 720.0746641041943, 488.5778468487874, 758.1646534824664, 690.609339446523, 645.9028997409523, 490.8213350785862, 792.9328681323175, 93.05335055467168, 221.59640047253015, 691.7871552952018, 306.2060301300884, 581.5555853323672, 473.2604887595248, 530.9219311457678, 425.5038127052148, 745.9354367136096, 330.79129719593016, 702.8549421857689, 270.91642697619636, 251.4036762009415, 120.65588475230582, 192.5842934515174, 119.55474125505283, 535.8639653837498, 762.189609484103, 185.14984243766196, 216.38463987860567, 484.1985872127087, 724.5850010930516, 976.6070228830567, 524.6368691086078, 282.998703274955, 100.52610809079077, 194.11757809107343, 227.48316348255472, 179.44154368540333, 14.148367256063832, 534.1350892676633, 274.3113267832673, 974.2949311027379, 553.3589667986083, 697.4173929101944, 126.2794929584089, 868.461197262944, 490.87869452377885, 872.7197349985346, 574.0642196263323, 469.3969449305102, 440.46879813601026,), (184.36367039008172, 51.37671718357784, 941.0635967681033, 477.7291872656877, 822.1156452994304, 400.70744225193977, 74.0821702556398, 629.4457069519983, 53.609074292533144, 149.19758447365516, 562.8395970845642, 303.8355118422277, 993.9181227102201, 118.45156221146158, 764.4434453261943, 606.3176512429628, 790.7408298434194, 225.68713706503462, 522.5725351302178, 450.5144648887542, 442.72100401932835, 860.1666658261801, 990.0312604667979, 305.3802443193604, 621.0273210721485, 609.6309122451303, 740.089305484545, 947.590200325378, 207.78790582355134, 211.025195305393, 660.4281371920366, 157.05709338087715, 173.81354832643447, 75.06486890116793, 2.675722602885733, 450.5037046177024, 593.8111951195633, 291.2592890310276, 231.47623455602584, 706.9558298790332, 702.9875580937172, 454.03132640704325, 687.3849200712427, 923.9110444825487, 787.8280266467314, 625.0580071642219, 661.1830428533679, 933.6684584455132, 425.13896610421165, 544.5623787105786,), (647.6347234024807, 908.411445212606, 826.6311596500597, 71.40983685581415, 165.92278922072467, 307.6118126142325, 748.9577220696233, 569.2070493190923, 288.61058830584176, 124.35365817470823, 688.6779912120937, 699.7336849757173, 942.6762407440408, 500.4721771179256, 493.7952193450843, 80.44185189930097, 39.86078418361305, 432.02866416077256, 322.32158335829996, 250.3679024120039, 91.3268866309852, 961.9111021928367, 835.9586139061813, 575.1991092199305, 950.7862778063524, 999.5724168774514, 672.2815843032392, 269.5110259670539, 40.231673144616174, 756.2688304125571, 470.50082325163356, 651.5094894336863, 916.0727879267675, 181.4891472231259, 585.3296252778543, 634.7847194540868, 491.7258021910134, 91.24240629382719, 347.96105629465046, 333.308393665092, 670.1335095211855, 857.7330944003526, 329.8036635789654, 693.6736739834325, 288.2177953614557, 945.1935395632106, 813.5660347379726, 550.0966089717829, 454.82590860172945, 314.51715717016793,), (323.27378627599467, 970.1847268085771, 404.17505700074565, 514.5962523291051, 988.1192147817542, 657.660386483129, 542.593594357195, 413.2475707993215, 187.5825413945271, 361.77935915167524, 756.4431540555737, 625.408742145009, 759.990536061017, 203.55823835027388, 549.2196390853317, 927.6727608444477, 438.11609507237824, 698.2500291221722, 121.42608336261983, 973.1468158216944, 608.8716670819649, 239.29746232765038, 158.3781638052324, 550.83900700267, 552.251409053733, 93.20920128152255, 992.2571393952196, 912.929878484168, 461.44789417878343, 117.46614908955755, 832.1431737689012, 498.37550470719526, 716.603325991791, 508.87201506951175, 273.42489671327417, 834.7239455766944, 980.2446326033755, 243.73090607513836, 551.2650768804697, 383.58601324714994, 921.8681499315619, 508.240891592894, 879.3262551464761, 864.0269344285881, 276.24740282204095, 790.0061820031135, 414.94242355134514, 934.2483936825201, 507.7376758096565, 820.5494731855783,), (282.83898328282663, 298.5558497717672, 586.937722414161, 998.9023332293388, 489.640346655701, 148.59541838278912, 538.5805777038737, 345.1239416930075, 551.917417070811, 543.430062958673, 455.3446168665011, 321.77735022903744, 188.65237370710543, 697.4984276205713, 571.7976419849011, 233.5624460578931, 775.5444750992589, 43.64729909730192, 744.7051515651959, 705.2278810250026, 811.4089025648768, 386.0787524909823, 663.6888294845347, 820.7475517095551, 980.8181386597851, 495.32864961642355, 37.01961134557619, 502.29115013296587, 590.180429309567, 869.7003133622154, 874.1903740543083, 440.3062097706771, 525.9510868075614, 456.92807447424224, 722.4438275706426, 409.9786197463644, 654.7813264277338, 154.36121877154883, 469.49060098473194, 969.2036305742172, 338.56123405143165, 692.7045985868821, 649.8366525792726, 851.7652923506914, 852.341336565893, 859.3421841682666, 380.00939752265504, 316.66115393339965, 718.717425222983, 759.4018093343636,), (872.3830173985363, 35.89909983768658, 68.42074718155145, 631.1610168546406, 920.9290987802967, 997.4259229562153, 746.766366737927, 433.9714719231257, 98.44312638319796, 633.7478287807912, 872.5792326070907, 443.6785516617058, 694.0011627932346, 903.424062050486, 45.99096867721275, 796.1434651622118, 293.3677759652088, 374.84108977531486, 145.56979569939844, 531.1663181487601, 565.9280619159368, 792.5194738809532, 169.98364906809738, 78.96835065999464, 870.8395986448548, 619.7103685374153, 240.82979185631302, 912.8290160235548, 143.11772012850966, 461.1499133549976, 253.97733941354227, 255.32670815915404, 9.397431454871041, 804.6330769751463, 901.2094235988831, 677.6108856990794, 157.97562207178694, 441.72978360381444, 345.5656244430817, 587.5717051264214, 638.9387023600121, 424.30893846089765, 250.09822440770125, 845.3039251425987, 199.21699910889234, 384.6932489673455, 483.2080610592161, 237.2057019275746, 571.9226923507389, 574.8119301786451,), (992.6920436268975, 295.23075388326936, 977.9444845768627, 658.2298159286806, 274.48038017822773, 565.929016955699, 685.7994927340121, 744.6688411653251, 49.04425077599994, 606.4064930764746, 496.7272865238703, 904.1552908937254, 286.1941514591945, 798.8601195075987, 607.064998164266, 352.3209558353165, 636.6178780058918, 620.8911631303041, 677.7644586233595, 720.928376670709, 659.1815403167537, 838.3371169625166, 628.2481036868983, 903.4037040729679, 646.3406088905228, 308.9328839526361, 440.82319016281156, 579.5738053684028, 732.3597679392383, 90.13337574621893, 295.1104516291553, 747.4808649383715, 175.64007044430662, 132.15979774678354, 539.4077589844211, 971.4895812113399, 530.852373702785, 913.486974481697, 830.4726195673974, 256.97008456326233, 824.6898125424073, 481.8478298737412, 806.4884937937666, 746.559350717045, 338.71525380188626, 115.1697074497594, 962.8932928688776, 140.75701500588266, 966.5002094627521, 860.1405968988219,), (724.2167120755182, 979.9422427819035, 967.2697473000323, 804.5876440205619, 365.7750494055664, 790.6819685889375, 13.918655100901178, 536.5723082690591, 454.7860277338747, 672.8283818737536, 672.3407973510132, 584.5600916521661, 822.4173012267743, 940.2918917795541, 108.34610219923069, 233.82190221158316, 25.024649646482324, 884.2348452148522, 561.4073822498789, 915.2559087431595, 221.36720007399003, 63.21704116019544, 823.8553513904476, 909.387638427892, 302.1901745292502, 408.2958557954127, 139.7770125072211, 946.2615328819462, 304.3645843560052, 492.6246189782059, 97.1919986218216, 887.2593085285023, 135.66404870633653, 453.6437568888926, 670.4862188501712, 743.1401215231715, 945.9740857794321, 419.1267534147228, 742.2690147653158, 154.522902409835, 414.88452743680693, 99.02163471052849, 489.34703778961455, 408.1158856977005, 951.5215253810595, 32.7162868550469, 370.5299587344354, 443.38308606070166, 950.555169851427, 855.4501933059546,), (99.35462460613032, 685.6802654812853, 544.4658614821449, 977.8425294520467, 358.67384121231794, 398.1396427443731, 189.80856216107955, 122.15971908726375, 848.0331884636811, 454.7173685705171, 662.768738061978, 641.7044672332177, 597.145959519545, 21.357454736370627, 786.7945904546167, 243.56889716402364, 125.92388530804288, 564.5779759079634, 68.6101528243559, 765.1573758885845, 207.1573703466585, 215.95135191867476, 869.6954267695447, 328.5595534322304, 147.55417994144372, 900.5310356317582, 2.8355514800163517, 858.4061263801764, 144.687980320508, 129.9921314434368, 250.65419672812382, 174.49712090139346, 661.0576425973168, 25.780149786198358, 14.860327230708847, 789.9846642347538, 237.93160609046305, 323.77146196202244, 174.2462014061772, 52.39901786117651, 741.7180569541495, 526.0855265978671, 745.6652750339957, 476.24596542325025, 778.0170393142027, 513.2379576091917, 109.05401000384552, 503.8386897858214, 945.4156429701063, 43.365036899171706,), (783.2269959803796, 866.9809077598383, 521.4512147130841, 458.042522097682, 964.0261831220287, 60.825407494505825, 478.9819109983633, 401.61725451256046, 686.0974960622327, 490.26885414422526, 909.7008291152293, 73.49071576645049, 80.79047741079192, 608.29742363344, 65.68223332011391, 275.0159995579532, 633.0767243010155, 548.3564340483606, 325.1854433186679, 994.6277558609236, 530.5568374313245, 453.71541757547163, 605.4267915353129, 99.17846167904199, 701.7794185460662, 852.7927372955751, 650.9166648813055, 768.9627301047386, 720.8399166575991, 215.0230663274969, 451.5549159652815, 228.4935743645844, 338.9316188351752, 453.4989029074473, 415.9896502614953, 95.08583927563086, 426.764006260958, 665.1078630603089, 374.3010234355368, 152.63892476872377, 922.9850357343844, 67.13330813664776, 831.7718884748544, 93.2301017036774, 96.56443256578562, 738.7959984887157, 811.7692852773923, 556.3707356153501, 586.465082739477, 561.5864139920724,), (329.6459814162051, 122.23128535455929, 353.59807963376767, 665.3405200029155, 750.2842502514783, 868.0921488690649, 721.0606787461494, 968.3986253114745, 600.410091224677, 351.646185693149, 577.9185183898549, 212.7388056720061, 656.7363029881521, 224.2448691075656, 108.21838192726662, 845.3734186013451, 367.56105061535015, 762.6056319368497, 574.1000043314627, 807.2213711523444, 845.1551613283581, 974.5466021257082, 818.4268595406696, 613.5732805354647, 642.6991638298314, 26.2538314535834, 929.0842909949364, 829.460789959663, 267.4477251541106, 180.4160719608544, 702.6987728656047, 308.98468882991017, 339.8246567772584, 6.10578940365869, 869.8627065364382, 566.3210947613762, 400.78434399951556, 141.87465415126866, 633.1720126555139, 30.65709838090591, 746.1117620057067, 215.13288003351093, 419.83249376450317, 340.89598176933276, 370.05309247700393, 721.5959677426732, 776.835619966741, 567.5935566143972, 84.95703997717929, 52.608826425521784,), (157.40989710715314, 617.8381819260305, 673.968710613113, 272.10284354621893, 661.9386928082612, 485.66170489099625, 442.04418669777914, 273.1668443647695, 754.9431436683707, 113.81750811020174, 429.91363339789035, 283.24647008044934, 678.4862547601331, 486.63275336425085, 667.1325587363496, 45.417362604427524, 395.26339608762896, 599.3249569444504, 7.687085899882873, 301.4193620184368, 211.23397922031228, 137.234805257327, 255.51950402145928, 328.1223559378411, 7.7299065693119395, 747.0141234297678, 175.6948018758423, 380.20744571523625, 703.6712633826636, 500.2623465562132, 833.3542024198782, 806.2001865667638, 72.07549659215783, 861.7643620225886, 42.30226156279138, 18.7415365856457, 921.1624345024125, 862.1100136120664, 575.7591607368311, 573.399680885843, 709.4989615689342, 417.69395984348057, 115.17337266379823, 20.85655902474881, 324.76817944551385, 801.3221543104522, 618.1252633042402, 832.0259130717071, 919.7697517413847, 88.12988129788468,), (844.484359814697, 243.31647482273365, 588.8712883029119, 523.9625430006316, 395.76669685933297, 310.27456183586133, 339.51328114846393, 333.06862249313195, 168.1327080456866, 510.4832845421483, 114.02663983855254, 509.952062322972, 905.9227315800505, 349.3752654723803, 727.3791056739043, 818.9486015248519, 815.0370057500141, 236.2688489467243, 146.44421827769438, 197.27180282398328, 602.3989852731659, 760.2152955468921, 655.5090105187392, 177.14612894722913, 772.8480892475602, 494.11702501738864, 754.4458252469858, 759.8771496077485, 448.905256995063, 924.1542583856593, 564.4917834027992, 635.2983190605934, 624.5217794418948, 864.2468748314161, 627.2174068997718, 150.9574013930769, 68.28625849575609, 442.20806383628485, 302.8204351390129, 274.67366748615916, 56.172120213078045, 507.33688528977814, 310.40785060631094, 451.9138637006822, 56.89005083395238, 831.6966316631215, 76.731001173455, 864.2500339252391, 855.2933714560903, 615.0083884155736,), (507.06781733395513, 462.7116589272723, 554.316371338304, 791.8177972653585, 895.8767655568026, 449.73370365090625, 809.8159176979711, 651.8374546486099, 321.52676288042835, 475.6290279430695, 150.86107669563853, 61.87370010110072, 103.50187727039162, 899.1268339557736, 343.4377758382676, 714.3155491674627, 504.5490015009574, 172.55891143636492, 247.74372359828422, 437.758274309233, 439.4217917626243, 522.7480352655456, 158.74620798154137, 372.8519821013271, 282.8935786144179, 408.7693972473203, 338.3671468414195, 597.8858623829894, 789.2269315636647, 647.3053569693069, 65.91185427971746, 94.50594751509433, 678.379344840081, 284.1469738781137, 723.7336550024618, 656.5640864104779, 906.3426971636773, 873.2796620605388, 333.3620360605566, 582.7395145864195, 141.42838058431784, 349.8207875358442, 967.6965076927446, 698.4799628118809, 391.9579843353603, 595.0412281515748, 938.0021995657609, 309.5818874159609, 376.67930605630016, 791.6619578635435,), (813.184783814801, 670.1163999947225, 828.9589728944645, 738.7746721294827, 685.4144402775762, 526.393339734174, 646.0248207334879, 423.40636632210374, 361.8280963467846, 362.59766898204435, 180.26292287684464, 214.19266120890035, 947.6682675343468, 486.2709208727273, 226.54304653388724, 137.5653531770793, 77.16508430087632, 844.4283886858833, 101.14076366789415, 770.874720363101, 835.1198266345559, 883.6821654925616, 37.74749235756125, 336.76437196428157, 766.3076044472416, 131.049041429508, 376.7198708225247, 162.2472120884505, 831.3450566891255, 771.0978137309884, 809.0437196393011, 165.5391657440587, 437.67340513834677, 410.85861149653346, 676.3629221885329, 237.53020144692505, 444.19870980527566, 284.92793256335824, 748.5365180954363, 448.92796303334035, 534.0111496982611, 309.46789656278963, 808.6238710907863, 469.0156050322527, 835.1133927257074, 367.8409582250328, 947.130170244123, 984.4397935315773, 461.6799784409892, 281.7717327037754,), (381.87243419071046, 527.4597884614826, 966.2681532059473, 816.8912395812401, 801.2592241515476, 138.39853460343122, 250.00321158900707, 641.1790362044471, 874.116945052237, 554.5407454244312, 102.58973174840935, 845.8922767334385, 851.1660480847788, 285.06301405932845, 763.1168302916909, 272.7912995913566, 905.3062089782412, 147.3486559916043, 437.4725601949808, 946.4132630117607, 222.0380069069806, 451.1279902207089, 349.5850781386489, 26.67019080293853, 53.25688717107446, 502.00711469323545, 235.77807386343264, 994.5253512382913, 374.91267341776756, 28.18754552815006, 930.8259047499531, 839.1762876116056, 649.9606842941816, 791.380637482176, 137.59958772587166, 286.87939731326816, 829.7615831528226, 696.0719885759836, 138.7926918181862, 705.5361752890805, 448.6014739822466, 5.251198305617932, 79.22577127072128, 255.9239284370447, 834.963099282381, 548.8042454438354, 727.2347853249317, 527.7715058867243, 111.18686032423841, 288.10157803923056,), (301.15119458621996, 47.74944661885128, 419.82554375344307, 793.8991086394382, 457.1136166364487, 110.857895290015, 905.1468856619986, 596.7390428189469, 16.435352205894425, 515.3757302094061, 241.93813442099332, 143.57684024626005, 429.23889310333374, 614.8095827759506, 240.56423880654353, 416.5675952085398, 664.3713017420091, 85.61395498726176, 974.6544909522216, 67.67932290884605, 526.0594453221705, 507.3276965797866, 988.3314855964675, 554.1519524181955, 390.4537325600641, 470.135078158486, 635.67079146863, 981.0394225515603, 253.65026106921275, 16.242231108947625, 788.5200162795153, 344.80249316339126, 732.9410214506416, 628.2569624756565, 771.5013741098591, 735.1869848123113, 332.5186083719849, 44.33568829520817, 546.0137452076915, 813.5088655560882, 175.08912705170843, 779.1425934782815, 464.62289974703776, 695.389251996064, 631.7358477583379, 811.4976818476064, 63.10053703222462, 776.1903997034217, 457.6795774473532, 293.4425711750313,), (43.806275659123095, 199.46983371518834, 41.905941930382, 933.3709799503973, 515.3835892544988, 989.1227022961234, 543.0306976541859, 253.3137652026174, 753.290918818865, 191.1034307339109, 356.9741760353634, 780.841566978425, 865.7982770780576, 331.92468638134454, 124.4750082443834, 368.019174314673, 889.4865170122814, 743.3077055196212, 894.6374949550533, 386.64476826069216, 973.723584315309, 496.20322653702266, 497.52339249362734, 924.3104666269636, 519.275853534942, 801.1480874017738, 727.0813243426358, 78.92700605546787, 602.4532988302273, 822.3412795398867, 545.4743973446369, 321.21142821459404, 80.06891107499526, 660.9192214581367, 306.49585609075245, 602.6216277305998, 426.11607288304543, 689.7648084454862, 351.54698377199213, 42.355162850129524, 870.0371750563921, 352.5593103084823, 998.1505977730491, 274.5553600748576, 980.0272791942791, 947.9043786030863, 75.04116498815927, 637.5125378832984, 363.3111306509823, 801.0959755621699,), (679.4106078146899, 952.7893962796078, 142.77946836254972, 607.5729033208553, 781.3119697434665, 34.79896579854203, 67.23336306210881, 778.5153735068654, 366.32847310714857, 382.8544016887777, 567.2446417241194, 605.0948288547855, 679.0620569133949, 948.8235292655564, 372.01337847068373, 763.0844717985796, 573.9217783338956, 529.4598815362897, 398.03404595244996, 649.5607367060315, 249.61165309339793, 113.44861258501804, 735.6748594794277, 499.0439602564668, 386.9873800392635, 561.6727107596471, 261.77667658749584, 260.2897711399034, 446.2731124056162, 996.3651121547607, 285.576877698158, 916.4789095418814, 491.20019525422407, 122.63742190397086, 852.8262903843226, 452.04268524539737, 898.6790307862303, 445.11119274245505, 87.79074110473107, 681.9292602506372, 845.5212189746994, 319.58777209997015, 347.42529473856456, 64.93907831607115, 542.1713612255624, 891.3316823538889, 851.3620507531259, 711.8091040072127, 927.3244567231777, 637.7000225640163,), (793.6963838450027, 508.7557451743008, 121.36245507845689, 200.98037117768575, 138.87687203836575, 790.3730608077492, 26.284026808265693, 554.021437259595, 368.9111655012207, 803.6617262885865, 551.6469339264276, 611.9483626205209, 86.21548165193272, 309.29071752846215, 999.5950439343869, 718.8696598907195, 525.6956548303001, 769.164550374698, 823.3393995083907, 73.75071291123902, 972.3797315137659, 642.3385893863814, 449.9744956629961, 680.108990649581, 344.5147807026997, 877.9601516504117, 780.2629288383084, 639.7939293666631, 181.96313655213737, 966.2646139341068, 432.61828648197485, 910.7122709468873, 55.412850017777075, 124.1611206390122, 153.01546681020062, 164.65707989012412, 322.6607511545556, 709.3321325292459, 346.0230823418731, 940.9040549315347, 894.9259168211245, 845.9337061558656, 250.60530898199906, 635.0570913877459, 550.8414154831397, 125.1702951269027, 302.8246061088761, 533.4780297683806, 502.5731447412627, 168.6359017477068,), (941.6069878685959, 154.19426687956317, 658.7328733837543, 720.632768437273, 605.1389078618554, 842.5300041133889, 563.6180344312958, 825.2362673266321, 28.373487898877613, 45.46180329134197, 641.4537338243601, 576.771231477379, 651.1299774855999, 766.9590009163111, 416.5867836632462, 638.9911922088704, 498.0380601361161, 627.1640102980843, 289.67165680160576, 956.650169954405, 482.94481817541947, 804.688154215733, 684.9908411956767, 297.43387142974234, 72.97302550534579, 59.91303293466898, 439.605499920411, 484.2511301073592, 204.0230135633987, 606.6602628206364, 312.582499246933, 718.3628851124375, 734.1997548045468, 860.7773657543253, 975.3741279148802, 130.76615155238648, 370.5401980502913, 561.6512157483692, 319.1158864031572, 466.4725670406364, 267.4716744541824, 247.91888252554563, 96.81165256057145, 290.2120049699015, 384.14983350160605, 615.3774441639687, 248.27028038118658, 865.3075017207177, 159.69966213016096, 327.43582080119495,), (577.6870378935964, 312.71492086227306, 763.1213751386285, 498.2655420599088, 514.7248392314845, 498.75970839765415, 308.5404820544293, 23.176298216351032, 945.2328040692373, 505.44446302377156, 966.6866305524754, 215.1444225262411, 352.89508885635536, 50.54040462789189, 494.89420352517635, 882.3394763682011, 654.2600368896171, 470.5868533681957, 536.690749288927, 847.1723653934679, 430.9277693475405, 882.4557309185928, 727.5080633593919, 763.8567641619895, 365.937352517563, 400.5816210147566, 570.2816438810293, 194.65530188771908, 553.2229266211517, 73.53174974284182, 504.2555291232047, 764.4041147072396, 279.720677623982, 989.0907006207226, 680.3986401188607, 118.81101972358954, 975.082815413784, 393.90371272860716, 794.8972283809458, 339.0852999525653, 938.9485669553754, 754.9651722927939, 199.05788155244997, 509.1225162251821, 500.07790357064385, 45.30335246707473, 137.0363735675204, 333.0407053527672, 473.74415039519005, 456.98855928287395,), (606.2605224038041, 515.505732147254, 327.96584763652623, 613.068121064678, 162.5020458769394, 990.6157375611401, 739.3193605736042, 299.2343425283183, 336.37345215167835, 828.2893859573759, 532.3398298758764, 708.7398064354379, 299.7905647374137, 815.7488332438242, 368.3578098657696, 673.8063924730166, 979.8980311791619, 583.7021418487864, 796.7548139301472, 725.3242125518553, 688.0436512027717, 26.647154963833188, 474.59021408506055, 967.0706961720509, 782.9039914314214, 776.1620251720722, 577.6343958641828, 721.4001122963067, 583.5232770476399, 170.51206174665933, 629.0252411453637, 619.7358055010894, 841.1671249582643, 147.77570830033181, 680.7268950506789, 31.57051334209937, 948.2051707843013, 109.89552133369685, 18.937367506612567, 313.6924834458552, 151.43125811934544, 690.5002609185254, 410.37740932270503, 774.972301807091, 920.5209498972107, 872.8177089123203, 735.8372712686813, 62.28128601443195, 138.0824857852102, 207.34170497124472,), (325.0495344260548, 662.2267997143491, 525.4771514000354, 313.75259873781715, 173.18242385732773, 912.1241609240104, 342.32701768184614, 354.28698864481277, 771.9897841487408, 720.9245613272926, 643.3090999994615, 693.3133100298209, 610.0765800787516, 192.2641691367134, 246.5191355273604, 558.0866508074041, 224.8670379928167, 972.910627590569, 297.6145652769079, 289.0041374035249, 207.27779485464026, 704.9882597401968, 317.04074501844804, 348.8031742013066, 933.7003747708006, 795.4053560023335, 273.45753675542306, 121.87410573271507, 676.6222457825457, 379.69418537438247, 980.1605373213622, 818.3774601305623, 954.6088633914613, 804.6158339565703, 290.45265199293556, 287.6303416141399, 714.1412874983953, 346.3635140956165, 442.37611186483537, 256.44397547348206, 479.0792630164832, 202.06800720059647, 538.5779237086527, 933.0239337833077, 696.1713006468226, 137.27295480092295, 615.6770344144923, 586.8304985708152, 242.4580384102868, 669.8339648931039,), (531.0414897327992, 637.9445734086377, 52.49110683946279, 413.3013660482586, 717.3580562502436, 100.5449047773479, 770.7660580982791, 5.18144640713003, 550.3525657796166, 929.0996801699778, 406.90745154688346, 935.0320985961515, 878.3996215142282, 477.44852044089646, 199.45597466760168, 963.9140388909358, 321.1677021191667, 645.8979178902698, 907.9369587356224, 89.46072051151633, 574.1333531753733, 535.1522768936047, 723.1176782424234, 936.669379750626, 913.2297256698525, 175.0647754809631, 882.2449731648331, 175.78870753886255, 919.6348118509339, 997.1718030885099, 396.99457427829964, 495.3838973217578, 936.6087447777628, 962.1313800833424, 926.039697896443, 876.7431679179234, 9.267168480099786, 567.9618686644953, 107.300690773538, 982.9938883710064, 284.56165235004624, 989.0994700887234, 543.3004835569749, 493.91242034181914, 938.5605017408724, 851.0597385779967, 468.0207690062901, 192.81139827380977, 112.64676102015447, 162.49426253973866,), (458.9144710323612, 257.2648802295191, 186.19906912806772, 736.6178954922569, 790.7676644252557, 567.7812224209393, 757.2827502575612, 175.49491383846473, 856.1465042734577, 897.0427617839752, 826.9898252763096, 515.2806589597959, 86.73776984835591, 669.2558534099542, 184.78120104733998, 140.61187563429846, 323.6016700850828, 248.04708366714502, 260.78527706544384, 235.5212528015339, 753.756651781457, 954.0348226549125, 301.9458396916147, 722.8825284009974, 11.43573419416799, 653.6833670701986, 692.7685904227388, 62.12433180053611, 118.22484780397258, 306.80634200361976, 405.41661296839726, 502.52047123838963, 895.1183698162541, 703.557035102256, 310.9779483846603, 117.41574584477632, 916.1303858506353, 295.0376001562468, 614.625448143849, 219.1286009353439, 133.56878219680414, 153.18556468917922, 747.7348371508565, 605.7389480890694, 415.84561543122464, 549.2345088183648, 470.8280768793115, 537.5176876932009, 664.0944393396638, 218.41162265732595,), (247.46542930857774, 754.7395497248654, 873.1350584877333, 81.87030647327487, 446.7479774964833, 703.7661251558726, 78.10272214744019, 564.1687222734748, 61.75804755668379, 547.6492487109315, 505.4870560925958, 572.7016742278647, 149.8523813824143, 328.11763774220645, 520.341541184787, 116.24002218466423, 205.40148553936154, 583.1476784410175, 90.94164445168784, 510.37535403283454, 808.6920831772371, 453.432300179782, 513.2478432016416, 456.79847571187605, 57.736780932524766, 462.3783057389237, 806.9153525543925, 723.2800798250023, 395.9487099532575, 816.4532259331305, 745.8044828318157, 578.3112650590353, 45.289802727828786, 344.52886656213457, 63.75991211208321, 994.1236604769201, 934.5827988849464, 69.0191461603138, 933.7755625849306, 31.734871023079037, 408.8669358192483, 768.9720625834827, 765.8276829237117, 978.333284924101, 645.8808180971635, 420.3619388823279, 992.8565985808789, 382.47961885137204, 869.6202853107085, 906.7673115245726,), (375.6455338019876, 682.7303541015414, 661.7925381254681, 539.3002639188602, 653.534098418702, 347.7698871391502, 178.47362900411167, 537.2584863980012, 528.8425395440092, 727.8581409061281, 222.6902159662665, 3.473294944907779, 22.735327321966594, 298.36298870298185, 673.4998577765671, 544.4453390250208, 531.9336084967588, 823.3604373757844, 247.51203850377556, 346.15973498762673, 275.6497277951273, 937.4103611350669, 725.0239459089183, 112.84463876647732, 809.4781835391944, 419.2405984917521, 766.0534675139033, 883.7566218453745, 15.645796363549568, 206.08162185021416, 100.89671309250703, 33.57627575428079, 597.7848967315526, 703.2862668649286, 48.6763212437491, 740.5410784798617, 402.2653508109063, 234.33927848756164, 217.26921017755342, 863.7302426700563, 56.44403502446094, 503.8958489409345, 289.26345135391887, 815.7862567633168, 731.5174831160183, 318.903696369192, 597.9176742772623, 672.5319014369137, 320.6651153929831, 301.7644351442033,), (143.26043416332868, 660.2124238107432, 221.04274044603255, 300.5009537574196, 60.957637106762164, 948.5202550267213, 879.7138909638829, 911.5776656205444, 625.9931376483299, 427.2005822945435, 495.62078749302094, 972.2902353436858, 941.5864098319117, 671.3425247457224, 785.804595980518, 318.7344573163122, 416.3246334214447, 149.2176078251637, 376.46018850713205, 754.4160972381253, 473.51882041221995, 849.3409322600405, 300.7364187951478, 707.5767974879722, 805.7761599348869, 914.7411738159054, 562.3859495869091, 967.7861885466267, 557.2867581992731, 134.09275105907804, 242.85851608958575, 203.3367330600544, 646.7058515479447, 922.2261045112224, 847.1333859196119, 92.46399652686365, 724.5847123072019, 190.4816184307383, 268.4615878549812, 673.6719206345784, 602.9220449890759, 873.6204584895396, 188.16329393275532, 761.69641753721, 724.3052398521492, 558.8504762725273, 479.3942064709148, 869.473851524538, 332.9643108188213, 957.0197605266732,), (15.333706228492838, 937.1597632022477, 962.077556114459, 117.31619944333448, 999.5720070178116, 478.9208763423658, 242.59318184574153, 604.4015340787812, 204.5131429778937, 915.1264595935564, 552.0792925478145, 775.5138820637702, 380.66174437292256, 533.6501274162631, 359.2595555515757, 261.5616273853526, 512.8165400237735, 497.27729310098334, 98.60823156548004, 981.3184568465767, 469.4904220225584, 839.7311815636849, 914.3304988974891, 370.7049214312584, 413.9301705786246, 562.5247274643543, 221.27409831622523, 145.92271310254856, 260.77410990976955, 934.7582502963535, 579.1429260374005, 417.5780735551736, 152.41141018414328, 329.8652859778599, 379.83977474115414, 833.3627152869851, 499.30148236932246, 654.607966121022, 684.8466123459061, 257.32675763488277, 821.5919396923563, 966.5082672503437, 641.6944543987072, 490.5955825807824, 168.233645951541, 794.9755143536341, 169.2657108601817, 720.3135307620937, 488.3163212541324, 916.8993896943668,), (542.1368553513552, 641.8094631823666, 58.732051580730136, 33.82375716469055, 846.6973831827223, 945.1881112008982, 668.2155433931528, 764.3388435720192, 412.3922224155927, 842.5447168253485, 231.43339207744552, 707.1695637034599, 9.141461690661767, 505.7329196930651, 373.20069268681186, 617.835235876803, 666.7547295533396, 616.5193610843379, 483.2041536218251, 487.85438400307123, 6.6123569921588965, 551.6435609112568, 11.850968127892436, 529.4176468416506, 274.740737197832, 977.4793482325117, 17.1425976024272, 813.1572209139099, 674.0329521192103, 806.1676989474289, 909.7733659987662, 107.0164286944929, 96.31389025140847, 148.89748574025052, 191.9320569916132, 526.4559854002163, 815.2143907132826, 267.32473667663584, 396.89642249807144, 373.05158346368216, 406.027409873886, 565.0021824479691, 990.2330316397273, 225.85725262742218, 684.0416254703855, 847.8671020315358, 653.7357195591532, 858.2191590211871, 759.5858501768448, 93.50050542710942,), (379.2640222398159, 552.7014395296953, 56.1149391289657, 9.450172654130618, 171.38357522104764, 499.858393112792, 433.9096519716623, 784.3763107901111, 565.856627951035, 857.9603133636695, 95.36183547073007, 528.159185641648, 42.551757617045105, 211.41705588454718, 868.1168905816056, 887.5543070344936, 475.5002876452733, 46.56197074174329, 74.34805992565107, 925.5848100809231, 899.3116508650087, 563.5098640479836, 32.90178015347189, 928.7663612546593, 314.48469322665096, 961.4691898760058, 587.0361040844883, 752.254469846865, 712.7113999493597, 398.296020395122, 76.93749144134587, 162.45025071470587, 240.4721943042868, 834.650560091752, 389.1566073673588, 896.5257670027198, 331.72983962182855, 755.6092645208922, 139.9505942351973, 988.4779579141031, 724.1635700943149, 500.7928516377251, 974.3233274359964, 53.6964319473936, 437.08825284349916, 838.674657613175, 340.59274647510597, 769.0056533654423, 954.8583969146658, 396.7030493089595,), (773.5549161313224, 29.625658945165114, 273.32702862879734, 992.5858784507974, 490.6034561107933, 355.8111977058479, 941.1428449707254, 431.8479462395447, 679.6948580881589, 660.6719075148754, 85.69411763673574, 618.6158902598878, 798.0551738027466, 713.1085326783857, 82.03800967314857, 154.22096386551843, 711.6771547786167, 633.9008819630509, 739.6552889765946, 316.67822868153615, 106.55091771126112, 5.195222370030117, 308.26745513629805, 359.9174973468966, 269.7664432393865, 132.50700449419938, 187.3917835278772, 448.84367702145147, 554.7400006088677, 408.04417508313753, 26.261904013297197, 353.91428734525886, 93.06426045987415, 598.0437977942729, 324.4303305724127, 385.2379173959837, 291.847351296321, 387.7995593783937, 84.69951692852251, 901.136044856433, 905.2075743133588, 978.1730640138873, 571.9604307495395, 169.5829233923255, 380.73202348874315, 138.84005895941675, 301.1312653750687, 493.1239422107847, 63.267150276954396, 434.67626926274494,), (421.1023397725243, 484.2313139339285, 76.92136139515715, 251.69973778890287, 246.59006834700293, 625.0336877688947, 593.8063980390787, 195.54822488026048, 106.972367580902, 304.657995751257, 948.8234623945651, 332.21721531162694, 620.1921878746894, 804.0764619573824, 329.5417162792602, 334.736223746957, 815.4754700030031, 859.5084671008352, 974.2253765046069, 136.1244715782648, 320.66515537508764, 947.2789220161162, 200.8514887020717, 314.18328119544134, 964.5746230947939, 968.7252217466955, 291.4481515800532, 694.9577676721101, 491.00731289210233, 575.8792816249606, 242.4242967805731, 376.0553023241471, 816.4945154329131, 392.93512973945985, 113.88782361199812, 563.850508628786, 592.2270342503855, 545.6290854508221, 681.7126331300876, 550.0991569974728, 953.004611486296, 461.62222283314844, 708.3670512560635, 438.45495430890855, 291.33120798529535, 692.8352793243428, 818.9655680044585, 795.6568359959543, 409.14159024490147, 499.30321528896025,), (633.3360396536635, 242.02116767079883, 658.6629685323181, 715.2363912994261, 789.076763220063, 73.96513558228845, 990.700614436968, 479.23469977262624, 400.80509732937367, 506.61264302950906, 920.3921844782957, 691.7088658981077, 543.6452033874776, 790.7209170492591, 359.5294930162599, 895.501515360899, 536.9059860245878, 638.1803670108411, 84.98193405098775, 768.9540479200405, 657.6016445807657, 355.0088194484896, 646.9998479834043, 44.2966935278496, 983.6082059519828, 677.4718958641856, 399.61774622508227, 752.6827777296058, 965.7167777138283, 430.45554389018224, 10.547772784579967, 258.73837040365544, 510.6762405250447, 518.7977668493495, 580.5182955240041, 575.2353982704261, 445.7785515049068, 391.1341686775028, 772.3422324624321, 588.5899565401472, 500.4657816197345, 344.9673875995183, 24.562653025769322, 104.54935800606168, 415.9754285257126, 961.7278656799704, 116.06933795897467, 940.676158146193, 141.6751768315483, 311.89034389754147,), (455.3326352258771, 206.8673262083145, 482.92599877938846, 476.16253120916355, 438.16593827302705, 696.7632655236661, 318.90947421770045, 300.2640831819403, 810.1859369186981, 115.08526669878594, 849.1800080469097, 647.9699172777136, 677.139332890884, 164.35409285070324, 983.9004705882779, 243.9129465519927, 174.45323282413395, 160.1357112153593, 559.8489524898631, 958.4626217339497, 231.85554741141036, 405.04743628025284, 184.45177515139366, 640.4788766600781, 432.1344524292825, 29.19227434239058, 614.1069373719198, 197.32443578224635, 592.2031583603683, 388.8357803557071, 704.7356159597344, 205.78447936732192, 752.3254953604917, 808.7297886312608, 62.56375146916437, 101.75204872714238, 871.9793300098852, 186.9598356320934, 325.9849115988185, 457.5504222061855, 262.3533954523609, 862.6365474573073, 527.7150196277827, 639.1085856661506, 596.9708292829935, 611.3084211390019, 587.0047145652179, 347.9246374367544, 845.5178026695593, 617.362679336425,), (813.7382542609338, 705.98836094598, 297.4448346993519, 614.4845157129195, 84.7519686230278, 133.94776965071065, 117.8616526616567, 305.38000253549893, 183.0445183314835, 693.4365417164455, 510.82486948716087, 418.2391062112586, 137.86729853455748, 383.709962837687, 185.75369929276565, 635.5016420586353, 693.4329265293553, 645.2600950799504, 999.899552563092, 554.9125758324974, 489.64202619220885, 140.29653509779706, 314.5800146608443, 451.00097074665115, 53.61126263083682, 359.0391721713688, 9.583439563281226, 136.5347146624959, 815.2159406538618, 963.8290894149399, 505.43801973067036, 494.96984786640365, 684.6966705599865, 415.6304352741119, 839.8918021012228, 488.69951193968564, 82.67062578646689, 30.860705643004806, 761.0566185454242, 292.0899095587066, 274.852918273534, 537.6086182048194, 168.2089774365737, 457.3213872734491, 742.5182519297871, 765.9195549436906, 549.7261845380514, 113.21099529202317, 114.2066513589688, 775.1130278639955,), (823.2828079975666, 366.8617721054209, 822.6109277962613, 41.61052227023332, 718.9802411300433, 546.3532747219646, 989.7757278766833, 102.4164388774983, 830.0707165425397, 751.3454947436721, 297.70893510289363, 999.3126692789077, 449.73234283041376, 348.57697682231384, 816.7285851164386, 439.069903383333, 993.9576843186171, 775.6316498807736, 236.94605536668124, 810.7027168394102, 587.9238969768106, 350.6308411139897, 710.7539594937995, 632.7706309271384, 165.9816176902592, 139.23496592677608, 206.61965618677337, 206.94272075176602, 59.35783363934111, 350.8154789528309, 281.085018790198, 538.768546047638, 323.6536158546817, 704.0537617551885, 289.3332434649436, 267.34306627808013, 858.0168449462576, 985.4883022617942, 679.29931592331, 95.22516381434276, 962.771994993792, 785.6910482912973, 918.7687118298253, 992.4862256446744, 867.0475904337784, 126.88816861381025, 866.0787949911568, 249.6772419381398, 711.3948488391691, 828.4818026986326,), (761.473587479857, 676.234553699946, 489.4587259156378, 577.4255293041615, 268.71715208748594, 414.22508936503766, 451.99172255036433, 633.6277633502976, 880.1250813073235, 93.09478404261739, 515.613472087699, 278.2256878517837, 936.3361140885752, 369.071174075422, 950.2540788653826, 327.2892801609303, 2.4730851419847433, 774.1352904376932, 732.724026539487, 730.931937487053, 458.4492566797177, 664.1438208318425, 358.2227293409872, 63.33068606017467, 534.4244643875649, 217.82993501520588, 429.64310068523616, 211.85146640773823, 268.53683831442254, 828.343617048808, 337.75515517078736, 577.9336402609515, 566.1421109171403, 485.33790400850506, 343.7396205526192, 682.5519260932059, 48.40926115172295, 99.57474191620585, 783.8897618405682, 459.58176267356686, 124.23717923039845, 857.6515999286138, 441.2859488764266, 0.6759315121042109, 958.0317693039723, 202.31820639739973, 688.5918819115103, 131.91308738401352, 649.9971993406527, 158.97746290581938,), (932.7255627259242, 274.0194580952822, 654.5879644187941, 250.38927854910887, 371.84376764676574, 903.8002688356579, 165.5250791593438, 396.3415669322332, 305.5092448442307, 699.4413715245736, 234.14384148441945, 655.485228383535, 703.6980397640442, 1.086303691723689, 476.8067082609141, 132.69979203998662, 226.19086145940602, 679.982725121579, 9.28694760846016, 695.5971072880487, 817.1090269132984, 988.154909464272, 422.31393377505987, 132.17515109256084, 70.82830540051211, 383.0699256757727, 730.7633817639711, 102.42717044950666, 313.3514774409062, 880.9889949802706, 137.1292947435456, 773.4604836506242, 753.157800991068, 133.14623118621216, 992.940155246385, 142.85306683489384, 530.508276546681, 8.474741953009568, 650.0202131578069, 440.09942077985187, 722.4320263643224, 628.0800383409025, 151.37413084428098, 411.70989435967806, 686.5661698757399, 859.96252460215, 86.68803346598852, 100.46511247763878, 752.4456465479524, 589.5739177615131,), (384.03190934312073, 963.2487105532641, 314.50366818216935, 139.8301888092337, 276.9676569448454, 84.24871599798666, 553.3966317847301, 600.0078672258529, 607.5930989155232, 778.9696525132599, 690.4760750146538, 847.892104439959, 658.4053700851439, 301.6493335771986, 517.7491277972375, 509.52256611471256, 747.8436409812496, 295.5420512420667, 54.56913101525296, 897.9125603569896, 954.6715113471528, 494.88771994326686, 112.74366260380853, 499.5825412726576, 593.9297681377232, 528.2865009361842, 977.6969478433356, 986.882532843189, 933.9244016742159, 131.98288073270203, 860.8140039367474, 568.3803887729609, 365.4124628480987, 682.9420490024262, 762.7259378955857, 954.4529839408864, 770.3670492246139, 16.689401770856204, 67.53256927276719, 262.18527716206705, 39.82686193718976, 60.46885958820569, 789.2899989930913, 506.61060427492697, 628.570696060576, 501.04896550707514, 415.4319943754019, 701.8106386235163, 82.42781192464588, 536.5648160700154,), (616.047055666748, 277.46773573456005, 309.90688561664194, 511.30469829533143, 203.19749395782782, 808.0600809706533, 536.3901728894583, 390.7315029683497, 634.2942473532567, 834.5264691847527, 681.0556847092104, 66.11508362121499, 698.6758816054913, 729.9583774315414, 846.4681151407763, 57.90594402313043, 86.21279839148932, 434.4835886060191, 453.3718422971427, 608.8324044274435, 309.2900638735262, 741.6935090575543, 740.6581802341782, 119.44300690788745, 707.9006071180984, 701.499611223866, 163.83263093540756, 953.0162017546976, 523.0053627284083, 782.9871015437459, 720.765518093459, 166.96295419238083, 126.93060748112484, 781.1239799369876, 268.76448640924565, 886.414759740656, 771.4302904896247, 29.356251172803493, 807.1078466573698, 271.98059502135374, 63.87198912314807, 712.3232482004954, 576.651674714184, 77.07003128638534, 455.1977776794275, 360.1238955217533, 499.61000576138326, 566.8861282384498, 367.68777785681175, 255.11608103907813,), (102.9049150218696, 573.9123051920565, 722.7783733258665, 228.40442315325106, 508.8248849106468, 44.03790726280954, 862.9284949026113, 244.55145035670344, 471.76458957773815, 382.9794743673991, 150.08466824115007, 931.1600771571855, 857.4851977479724, 552.8647884958646, 913.947669521129, 740.6658281065769, 419.3693043627985, 321.8020144720613, 416.25659850131535, 720.2879214281971, 271.25795800981666, 77.88706996691197, 372.8081667378613, 502.04094255958273, 901.9410092582714, 179.34435027230566, 804.3380194587567, 981.413863941495, 954.0794074604277, 68.9264377727451, 465.0941779168861, 282.30687062090243, 844.846504261328, 327.30092172611893, 553.0913953240675, 7.969173557455522, 200.67115345295582, 563.8067408241869, 303.90930726808296, 622.7175238921045, 463.92669058049927, 591.6909349848162, 493.36067585488485, 772.6132629505244, 195.4224238728235, 900.4432972264233, 760.4822203216966, 245.12687437658286, 6.377882751804065, 410.0361095660199,), (232.99774185974297, 346.4236977394062, 839.5740068961738, 877.198614251575, 950.9902621434433, 1.4620405355777466, 657.3038501759978, 849.0059877789217, 727.2150390409726, 103.94901935155964, 529.8144890600125, 238.16759452086566, 492.0288768498976, 59.895075653189636, 996.9592701075485, 711.6524747001027, 93.02663835808822, 921.2744029721064, 897.2874719314592, 519.759218492037, 700.8469096986796, 372.49197407408076, 974.5547901682511, 84.90241751648952, 95.57700533738556, 133.51385025095698, 819.962891894216, 74.83048432987749, 567.8207282892083, 434.9828784349853, 964.2182747327408, 236.7717347418965, 260.99093892361003, 315.0089154097583, 800.8168219994952, 700.7256063215422, 735.3528687823681, 318.0577192661006, 271.9558718324544, 74.68743580247971, 202.7126262792337, 779.9369824688629, 584.7080358041289, 155.4105513219367, 164.3749806997682, 466.0551124366618, 406.51255190921376, 535.9245490900237, 964.6347222502287, 207.63622561035356,), (308.3077182125046, 265.0079269147082, 119.91355635539358, 157.6153770655162, 686.0548429505152, 826.3866879396497, 696.8779832370764, 40.32980810866338, 835.9247509879863, 327.79365888479197, 91.21290379134861, 248.21878946125508, 355.7427555042837, 513.4596753787295, 677.1833769890718, 260.1667927212239, 990.6766204079072, 31.082093358759554, 404.39222644717466, 452.2011694010627, 748.0784472087092, 249.85262822155107, 462.0432488762389, 803.8967534438501, 139.79303927312236, 11.956923169412992, 830.372331894636, 982.5672374836216, 130.71523754397796, 823.6734151883558, 372.23974381599766, 630.2975486038245, 644.6850157085017, 582.3238014140632, 258.82065234652583, 812.7470916136143, 21.800182738612726, 64.47203604690976, 902.4960819332814, 443.43132383598885, 128.79154899700453, 905.0779703801529, 829.3561772174179, 331.5498912522767, 42.69631909043081, 460.9949220019407, 167.98920872905643, 573.8841998067228, 821.6854015322838, 394.9986882219194,), (29.48229300029148, 683.2129167516829, 172.80721183078919, 214.71610597002666, 187.15111371116166, 279.8545542807238, 883.4237988936261, 34.64944373150591, 619.1765488458958, 245.78477924524543, 295.10516299971647, 411.9922062961651, 550.6886652039806, 60.97906893284277, 279.77598743897136, 137.2360171320648, 199.45780666658663, 884.652516224965, 525.8140582527797, 630.7543888247554, 802.1680827711275, 794.8462443018702, 989.403615761534, 781.915766845425, 359.112109837565, 544.5178060340517, 484.6794854405083, 912.677016598367, 502.3932555437417, 388.3812745693004, 179.81587369786922, 318.87756732937953, 219.02018807309454, 895.7647816143137, 778.5382813284745, 58.59123027811852, 991.5313234305709, 529.4322919654317, 766.8421684502729, 999.6057468113461, 973.9798617335022, 100.13433730495758, 656.8644213762342, 266.52700106568307, 816.2851616087347, 917.2594825704406, 55.908655852449755, 996.3920485064572, 219.41158604392484, 846.5050904651331,), (797.3906873794087, 354.80463456515986, 839.2222523059093, 845.2209023662808, 176.10036757999003, 592.5195091814127, 806.2102253593883, 697.62656359958, 913.9800041285343, 28.20656972715907, 700.5608033514183, 947.5587350700152, 563.6064867831936, 563.1088908640183, 188.2336649052563, 988.0062104046431, 881.6263598977603, 492.2267238954052, 309.05307215121957, 490.4364201274792, 90.25737685090284, 232.62335808323908, 218.80894169157727, 526.4485141485109, 0.6834963838308061, 917.8961859206465, 201.4643833521411, 130.4895456191826, 716.9376326908161, 918.7807873072061, 844.2840864376508, 323.5888113048372, 21.912896243572288, 586.6091750912095, 917.2241474811186, 774.366498819892, 846.4808875791347, 860.6694665915508, 960.5587502757579, 373.5907800833732, 941.9232134680417, 395.5955623538543, 101.03217018068634, 301.7651537254691, 136.45166885749006, 157.50388601684585, 948.6935320263234, 791.8425249998718, 960.6621003687383, 649.180265644814,), (174.20267095553066, 968.7428963331677, 693.560398292543, 928.8452089854253, 786.9917925955768, 223.23721398064245, 588.9501557831036, 175.3516299902208, 306.82322929795805, 688.4984395989571, 127.3483647235355, 728.830955935999, 948.7881042212877, 948.6981598499029, 391.60142727760194, 994.2831305476307, 965.183940287551, 32.38274292708743, 602.3886674509712, 921.0382164180794, 967.5316856159355, 220.89525043701207, 565.5502461203205, 936.6877935542542, 140.64336814856836, 745.3433281019825, 237.99592514939692, 982.3796549403569, 167.88741675158414, 885.3202533951373, 88.72601257012636, 708.9658131452244, 639.102687904879, 886.6535152178218, 446.6299340977938, 265.21244056287617, 249.54086313527878, 67.79478006423378, 256.6579415427148, 108.03460121462926, 1.281157494689933, 385.93709795340993, 732.5844944987687, 969.1018251056852, 884.5519975872274, 493.0819934738476, 378.712574774259, 546.0239676784543, 101.42855963712161, 479.4892503971605,), (864.0507204685769, 650.9695631134773, 687.1604042171144, 162.65651174848838, 73.70938956137418, 845.7624840245603, 294.90861311613014, 318.7169079566575, 951.6621314341206, 74.20040176665933, 170.10520315119072, 375.45788407999635, 731.9850852713168, 547.0462586122314, 898.1241731199735, 93.10468538068662, 594.0305440520949, 613.6447257788939, 482.7367864884782, 30.99320405217498, 942.4422581019638, 164.9971947619141, 889.7532704040436, 157.10377733078406, 101.25781097333564, 205.55047671793713, 189.98425758838545, 697.1967038073572, 722.0381045756757, 729.9740056702899, 265.2647693499766, 281.52307344526525, 237.54828494724566, 49.63544303850986, 571.7698078577415, 839.854504845651, 153.5899345167342, 360.82892122727304, 427.6059574457135, 294.5494489662148, 662.0202260098677, 600.2155280137688, 199.67480076094634, 25.735692731685745, 170.8321916922231, 291.8114660461075, 81.93609471299234, 843.7690743469366, 308.3534001177294, 397.46734850812294,), (489.09681212065726, 661.0400547460049, 91.13687755067923, 544.1260292196804, 184.88122054204726, 885.492692749539, 369.40166493194505, 445.7752707944581, 263.2959666828842, 465.0579561546505, 226.2422802826669, 268.5619932729496, 61.639369660099305, 752.2351421152871, 667.4684591345684, 85.70731183991708, 343.7913865460467, 541.449006006022, 970.7055371735757, 589.7264423036034, 553.6019061579088, 840.7971664302505, 818.4052148653735, 418.63207493445054, 535.5144119789297, 866.8940137055623, 474.82438812192, 881.6538694176228, 476.26228108146864, 78.9550497740832, 902.7772013391541, 714.3056991303599, 502.10147031560484, 900.4550614715919, 800.4503367206856, 677.5348855760302, 620.273114349303, 120.28187475543328, 757.1887556391935, 172.87897280649446, 984.367990249133, 972.1648783377569, 808.4607213193631, 126.18599863853974, 423.37489931559946, 988.2799363369157, 435.39324553250725, 997.2692267787756, 627.2262122323979, 834.1081186760042,), (257.88635998777653, 910.8044857315942, 914.1739368985866, 67.01195304861241, 388.09915301475075, 397.35499435703593, 326.126805369736, 276.2008199072493, 458.15718112261516, 873.466253372757, 786.5506388277718, 623.0579285888082, 522.5620104498092, 419.59383422522865, 414.4423987689787, 148.1997412934446, 587.934133281388, 758.3773878576729, 939.6499849871099, 924.9281667592735, 562.6837594773225, 100.99044282544867, 286.0928540488724, 535.6334811459167, 343.8691785774645, 410.89003729922047, 383.04347287705286, 485.5847922863307, 608.9621519524352, 37.46567049771343, 275.4141420347538, 143.85267062667373, 608.655264085627, 693.661253167092, 38.78253248179509, 889.5742139142575, 331.4940757342707, 237.57929391630717, 745.7498198391361, 920.8349829061652, 897.0313621494225, 20.23288859327188, 817.3859253052775, 303.0549534480966, 280.3466531846416, 491.61921335931703, 696.1828446749748, 98.21739027274123, 868.9000250991986, 134.3855209684405,), (974.2194246033105, 443.11061333142885, 825.8233317797506, 269.4199810087636, 416.77272770538275, 645.5548114276443, 187.9356908926114, 211.3905021058976, 823.9694342551406, 740.944798920008, 759.4932952262567, 867.1889705502257, 821.0173923984603, 515.2697026095568, 158.97244263900046, 311.1230932889089, 506.7952752365554, 135.6498210170598, 851.2953409871922, 879.332591359856, 28.948159320185685, 192.7634468315582, 832.9299330765513, 836.976677608695, 249.49026132427932, 456.4486671280806, 918.0310693411162, 704.6339452200365, 273.9774984743589, 823.5062916891162, 505.12637493716386, 635.409809075844, 123.87285889069999, 30.563192381993566, 372.4667615353072, 594.0435934521513, 177.58369416386165, 870.4807719184629, 586.8802484224268, 349.7599016446545, 163.51361451785084, 894.4919203121954, 748.9611344849333, 688.8505376159543, 285.0693764347619, 386.5656707409847, 162.9074055986205, 572.2580610203007, 964.9176092022551, 857.1111874383023,), (647.379891761482, 677.6952460909707, 269.0835792689571, 409.50719649554213, 20.052357499524387, 780.3036642606434, 767.5727284118514, 8.897986070045771, 911.5154380523718, 647.3715541750241, 601.1419504945338, 8.463727114871311, 252.39044136065147, 805.0855691954863, 305.45969838455676, 967.0244433683766, 642.740311290164, 423.80680065961906, 376.4754522710008, 348.7091992720813, 251.99870927787603, 466.6980154972157, 677.1961563873133, 824.3108214706635, 397.15253997212017, 102.31210296860016, 511.51371746830176, 662.3554525262523, 843.2839500857772, 374.2297631610715, 647.3421200645904, 609.0501046380168, 298.4759262532974, 108.10453058584346, 63.83705389710992, 988.3609658217671, 640.5947456146558, 861.4916002518306, 261.14690792649685, 711.0937755717918, 892.3737976809327, 298.7911119189864, 149.92929635415652, 765.4743883036232, 899.6870447454949, 805.4298226335299, 802.3637444528116, 600.0333011455716, 660.530323681129, 680.7557503873818,), (721.2831975926106, 655.4005704624416, 997.4690995782959, 259.42627271198893, 418.5656860813416, 388.27492100545834, 35.31900482642425, 708.0689593758711, 572.0424340368412, 189.9151604113507, 726.5498765809035, 222.36319397626892, 534.6351015442132, 784.897055296117, 906.5265055050547, 671.8684759624089, 507.31485560681534, 845.4192442445952, 840.6386945964, 876.4947843421105, 181.13585780400078, 97.60306381270934, 127.94413969605334, 258.6518505602079, 808.3438691424701, 762.9182183193025, 183.06835701259504, 679.7123692899282, 335.63246320511877, 89.2998220194392, 355.2833961198324, 744.2099293428081, 307.08527133220855, 788.0904368075993, 331.31713697077646, 260.5592354057036, 294.05112165034274, 851.2138882114102, 470.5365676706802, 866.3933696925396, 583.5746197425805, 944.300984000077, 71.21573740407582, 889.4260614703878, 500.47737449270437, 867.49775469823, 381.6692198055871, 298.35575381746327, 54.06197064257512, 854.2397704033438,), (137.36525917312392, 200.29659079514352, 409.1918467521041, 569.4035879734539, 906.6209345061276, 457.57143306154126, 316.3834576487089, 715.668456110036, 778.9411774338146, 487.58084574810744, 631.0327722626952, 176.82426656346072, 634.4989691526711, 4.713501423993849, 273.5278622014996, 761.1932812149992, 168.60575658290767, 764.4837361457579, 489.5770906300164, 763.5691671054992, 88.0413437590446, 614.4797387250618, 633.4875407025942, 403.39348039795044, 965.3278401300331, 383.31642005346066, 37.725318474937986, 199.41425122811697, 373.10140542737105, 14.077118724632133, 322.21313384806905, 833.228666566564, 190.57670994040456, 676.7481416771101, 626.6869286716226, 248.82312287588192, 693.5247471703972, 344.3497740633209, 128.93080042830275, 383.55047630769366, 588.6706881680719, 167.02018726830937, 823.8438296744104, 298.20225349413874, 290.8277918094866, 727.8319114468128, 596.3699186155023, 337.83515120793294, 887.9740438370417, 995.4724098279283,), (342.73283925755993, 901.3837761445245, 359.25088636663236, 188.42603389086943, 948.0843537069427, 918.2054088414334, 403.3916445011253, 228.4182848358255, 727.1688281363213, 131.20577641718256, 734.0766193115209, 589.6928347992416, 168.98474189014712, 366.5914513998485, 650.5403801212248, 37.363427692595266, 876.5441375994762, 255.78917392529289, 534.7334341938405, 48.586961581737256, 994.6802336570648, 661.9699158378946, 653.5676184007526, 19.741766514145166, 689.749748377426, 416.77556517954383, 380.2537629276146, 547.2823847289255, 474.3963383277372, 153.12709812443637, 695.192029220909, 630.3113401629369, 301.1164547567923, 661.664576090722, 662.4829658888967, 269.9771495371891, 605.6343369240042, 137.16352297980893, 830.6527204708898, 104.91423690009694, 718.7662245161679, 117.73456843723929, 114.01255268424792, 106.26370830639497, 198.6466617062822, 199.74938614635852, 262.9939156137319, 523.1461075925806, 201.67318998218943, 703.4367502462583,), (295.35120100061295, 39.405707628568656, 496.3547133680927, 207.693984271615, 933.1243446518774, 330.6036161967014, 2.7379990573926927, 671.6331324652049, 906.880044269792, 835.2321652632412, 669.0219305016401, 149.1444926667661, 90.08267357592447, 511.7053469646589, 723.5636168984789, 101.29047859295737, 255.89210275066486, 231.16028649174592, 988.6659307576041, 296.0221787104385, 464.27735723614836, 99.80891505761747, 174.7018089453909, 39.444519968693584, 290.56717723767946, 801.5961026904473, 312.70710696276427, 738.5401957921762, 94.99847033058029, 758.2040048412383, 45.88908709341521, 851.9987250744881, 663.355027490844, 170.51227635637534, 357.5274042377443, 437.7145167819347, 621.8070333325072, 878.4755373974737, 92.9513154374223, 814.9642895364964, 182.86942030364273, 400.77751428075294, 962.309930149754, 271.83309075019014, 385.71548975931427, 850.671578036584, 799.8990969489502, 648.8464838722638, 796.9095570354069, 113.05655637764612,), (696.1698735237949, 58.64639566517737, 942.4669879999001, 159.39539748216492, 416.0277166308965, 590.7503029903385, 802.2647840450959, 678.3931362492056, 181.2605605373021, 379.7507791158876, 358.5989148270966, 28.81622957122154, 684.4641333814728, 838.5364765471118, 973.4445802253692, 130.65532954841586, 920.3979856381027, 112.93747937376742, 411.28426373517425, 45.972325379669996, 261.61286722848985, 314.2379448738709, 704.568176480623, 677.9289210201644, 767.5287182899103, 576.6490081233795, 565.0122256873914, 977.8955575507985, 669.8443582143154, 338.30064176842325, 523.103175224674, 700.5801842975543, 95.24213845169471, 661.713262845963, 248.57699485070762, 345.7494874575037, 676.2955683574412, 384.87661740671433, 839.0330212297873, 558.3442367021879, 987.7916247614772, 54.56611522767585, 643.3987165568129, 156.92737330174123, 848.8455747760007, 851.8711514460662, 869.4156346127679, 74.86524247698479, 491.6479611444012, 240.89181669723115,), (970.1450487834002, 50.35071452654394, 222.706523168799, 643.3173148232519, 403.27699346172284, 234.99999999128718, 459.09459474823524, 801.2612877267221, 448.0985856653739, 856.5892241780172, 447.068119913798, 118.70685205363284, 497.373348066501, 653.3730031383718, 102.64332148015242, 412.3385476755494, 557.1310114048331, 0.16971547833644074, 90.89554691153224, 604.2032760617975, 619.0661537463823, 304.5979581036485, 508.3377403194649, 206.85124884989835, 671.4743707791561, 950.3551299933657, 363.34247756981097, 54.27521330556784, 222.9181143963146, 454.4599826035843, 560.1508451060681, 619.7579696770842, 473.1336810935889, 657.1672064311396, 715.9135002831553, 114.0233583557383, 759.3199510791171, 221.74710782019002, 341.3571145768387, 829.8838856344012, 964.43368345434, 291.9835638082815, 521.9850866041359, 702.0960532599854, 45.77810985503427, 164.15536770539086, 140.44918732224886, 716.8561126478827, 721.5590954513159, 106.961505519519,), (610.8517661386837, 187.54340053160334, 930.2162591531453, 392.89648991517345, 457.0460082929889, 781.4204036487547, 716.7879144693429, 107.76574728749199, 414.4703451283303, 926.6265305373107, 837.4662374864649, 588.8111078979014, 772.1451736372288, 450.50459695896404, 658.4566950002027, 956.190321625751, 134.63601443063277, 498.82864210261124, 530.3643475190843, 48.571605631905214, 935.3082808316526, 838.8161106789165, 482.9319635359263, 508.3328990718833, 921.1623140438011, 177.20243850214555, 578.5543857041766, 730.4946617427778, 128.3817820515778, 387.40557922066597, 600.5460218054113, 882.6958635586781, 504.1101193531644, 384.6626387437041, 979.500760205043, 915.9102416970992, 762.3746562617927, 273.5958799561112, 963.5935376797527, 970.4916499121006, 452.84570608136676, 133.37183101609352, 412.7454728076193, 700.0142848971251, 748.426865877916, 298.91313495542425, 701.494310407385, 860.707546129158, 711.874356395145, 935.5120774625358,), (632.5832845072639, 200.88908232534442, 624.1675892053881, 290.5794954792507, 345.29358089858107, 672.3737130513169, 981.3354962348673, 650.0863878069779, 958.3264528051965, 503.89343904324966, 694.3119737273263, 322.3813974138552, 115.40525228362542, 352.23780246287737, 480.3699223906268, 570.6381942530993, 667.1572380522164, 417.4779331255218, 747.8687233765869, 841.3893024169117, 285.92798022536357, 847.3010914205489, 808.304779326696, 522.7299862092023, 25.272141254472082, 145.32671163279554, 670.2541787669418, 199.9024172647168, 750.1850186700538, 160.93539434323, 286.61554815910995, 250.56100745818298, 839.3818144651713, 690.59485036401, 295.140565961606, 753.593948045165, 32.30638324232715, 814.0262175544686, 102.4800063379766, 868.0353736544103, 739.4924801728499, 864.8414852046026, 742.4127358728605, 561.2705527908106, 237.5954725662045, 784.3071728708866, 798.8217524895705, 287.6386545072203, 664.4935685916236, 926.4848111687795,), (387.5546486419903, 956.7019700118268, 975.8227220561263, 312.6539808946341, 552.1245975330635, 12.965237674638308, 251.34112837230572, 620.561695611849, 780.9248928758853, 870.1180923213351, 829.9834819551445, 911.4863770737386, 704.6290688927878, 647.6323855500005, 755.1077052085499, 546.6151367012413, 603.3869772886416, 776.1467451223322, 964.2898231260307, 294.1777218044915, 177.4258984882632, 682.6588517015351, 186.97658153232365, 173.79513185530516, 513.8468527745739, 377.1982270526456, 428.5078144007343, 556.5986205953329, 130.06653011537227, 583.9860576839312, 255.01614813242202, 330.56278874226905, 709.6892963163141, 154.80569779828625, 153.70338356962975, 322.63002989352, 50.85544119522678, 932.3818655711908, 615.60867609933, 661.890210964939, 490.5509411536766, 572.6769905549301, 358.0855881637394, 784.0314485705014, 317.8590053721895, 219.96201542755745, 183.86909753535895, 68.17646910442588, 505.1403504563159, 415.9312437704217,), (537.0359525339087, 91.94555393906644, 221.19808110145277, 213.35381018619591, 331.8835149039815, 360.7978246627327, 218.4452857845147, 752.6556895545169, 530.4988956068246, 996.6310826043388, 823.6884009252022, 981.1331976152965, 8.803696295037412, 668.9384679943867, 445.66724628469245, 904.4964584876922, 613.7010970607944, 621.131697623634, 958.8968160739336, 682.551360768528, 320.9370105070815, 915.9324302986754, 944.9876704318376, 385.8759347840439, 540.2373129425615, 282.8289942793975, 911.335926628618, 822.1090642399662, 374.95804216197917, 802.8167375082938, 445.556897571718, 43.85166091050174, 898.2816089058533, 192.51238793367708, 513.8947001274336, 948.2118196484181, 167.3948092134949, 956.276206473399, 537.974830964251, 7.330586216845125, 65.47182210371993, 670.3351261430566, 773.5822663692767, 864.9508557126312, 424.20945194114057, 103.92680917367014, 537.7542140241612, 702.7428662675412, 976.2239213390278, 775.2067404630612,), (646.5611593183453, 939.7511115083134, 746.8908256742621, 153.7545772176455, 459.22523526004164, 331.7153754672878, 87.52089793419859, 54.268070891441035, 794.2147869758903, 558.0842053178108, 574.9622942627916, 227.64563494165668, 258.64795899117564, 388.03260364477075, 630.2172646257878, 433.02503463927087, 16.890968304141275, 673.2987323277127, 534.8343700610021, 641.4227464816397, 618.3493589106022, 755.9605966951774, 585.6080133133892, 700.8820313830021, 72.58944479897711, 928.0477501663338, 106.13554186997254, 786.8918976625699, 301.3507098448711, 86.50522143779271, 765.2742410204625, 437.676149468225, 395.11325822861767, 660.7231649072405, 473.98767736795145, 533.4677638103494, 136.3814603295468, 390.9925654289903, 797.4846128334658, 545.7244686408226, 960.1342650804345, 144.07553745848313, 677.2890455050625, 914.1689163846179, 795.0171222324592, 729.3245766158448, 373.130514900324, 949.4651035305127, 553.524187764279, 555.0697456402619,), (123.23515893349901, 5.029839074005116, 596.6548874533303, 537.152048554461, 946.9793892288718, 304.6911523495084, 748.2965867327935, 903.5463759745459, 343.8516498486469, 412.65449602624795, 645.9315035600874, 512.552694547587, 160.95435756439502, 220.78796935740797, 834.7483668680621, 194.32621534632543, 181.35798477365006, 799.6600536055314, 852.2511277674757, 851.3877224254742, 932.3698654031485, 994.3130504254334, 461.2652036895939, 549.6646096791668, 290.65266752008534, 67.36275740480136, 98.20240138037195, 725.5892573341403, 487.1904101337485, 330.8509316827234, 128.21514659595158, 655.4729806969527, 100.0450572754229, 619.5157771543329, 900.1185908429218, 317.7859372337254, 450.64597821493504, 616.3424302594531, 305.6754894067207, 584.1699328451972, 565.5520079051098, 364.4934356786292, 316.33315949632913, 428.3073929595911, 4.831882664671383, 246.17513854548412, 221.52570420651975, 739.8162159386168, 436.1240660896885, 840.1100587806552,), (134.30583238288884, 732.9727957514148, 877.8858295079616, 462.84837607561025, 358.74160211359674, 305.47179644533526, 551.6718261601972, 175.77016089522536, 606.6276660708613, 841.7929953382605, 858.7136553707992, 140.0016687161999, 538.6180370780223, 263.2346505935833, 886.3358320788956, 76.46237855721539, 75.39995679337663, 18.630294871235353, 507.1783112855739, 31.194231008309092, 581.8904904939293, 405.134467551114, 590.036311840718, 906.3035773981833, 551.5939985261583, 543.4693239181186, 998.7281750118517, 472.1039507829259, 775.1972388374282, 365.8186301030095, 223.30664762435504, 772.1448435604627, 733.226827212901, 290.9991602143739, 464.7011421704528, 510.4119933437855, 396.78515764970746, 502.16726966643085, 662.6784893786967, 847.9818401038666, 806.5400569278984, 614.0359765601281, 165.84355203215827, 514.1366805163822, 446.22044383900175, 179.14518390862077, 948.7118316129784, 659.4133069529768, 967.735559837513, 735.9198088348245,), (483.50341572743093, 358.79553884580594, 218.81911725902836, 487.64214837364784, 62.861161413469205, 369.43518544747434, 42.808440838646874, 206.77407471750098, 907.7260925729606, 361.2513645596288, 469.67422901020893, 454.64344979488834, 46.437457501178116, 980.5897820631023, 324.0711100639776, 703.7362528476223, 521.3729041335506, 829.6471556536503, 834.8722662173934, 263.3160693841534, 544.3500620883715, 173.84981644645003, 653.6753131338843, 365.29677799104763, 653.3365465568498, 835.4690676766445, 516.6628975539387, 376.3287810435452, 907.2424264468196, 516.3717821675702, 352.89800775972566, 867.7194218712473, 486.86156267417556, 482.11008971312594, 604.2109802856869, 501.83698447074175, 138.96830707712994, 165.60582242899213, 77.08822574124684, 643.4770023883098, 211.51710413575475, 186.9056758087676, 362.22294421509105, 717.3027084540954, 118.33022282354267, 230.34633259932457, 811.1726293729599, 720.2556588749491, 481.51664938948613, 478.81464272008213,), (210.70395105176343, 161.24625986262575, 833.3649963952541, 22.446038154291358, 43.144331335165376, 573.4865113522683, 161.129140367538, 629.540424888155, 39.190865239483564, 572.2692992304645, 55.91200416044417, 258.2596460167085, 180.0375395762971, 958.2383364845537, 599.3575227949298, 563.6047023357652, 18.62003563723691, 719.5472995040886, 661.7450626291582, 283.45462640564045, 85.74850273574452, 449.20336161945676, 992.7758237079788, 867.3023989920944, 170.56678398969626, 830.3635747042808, 600.8387144112, 794.6160702604512, 820.4531801304973, 181.88415187009966, 659.648750057853, 264.5206985452897, 724.1867980777558, 342.6712745603319, 453.47014514009174, 590.5959909323074, 229.81378717877976, 385.46188274184755, 108.5701139015155, 202.34854908817513, 859.5916920106055, 504.35952607071266, 419.7950126716061, 149.56119362645603, 96.2985767232044, 475.86918548947733, 614.7481694803669, 39.03656361812824, 783.8682348387684, 503.3292081384595,), (117.51640657477047, 482.2409351124809, 130.32884077716434, 603.4746508247106, 827.5217786520734, 896.9689528475317, 774.8704230678032, 649.2317351065338, 522.0442296615541, 370.3774502487801, 43.438283336481476, 529.8165189397056, 229.4862563790502, 802.3001960078137, 789.5555851509546, 381.23649619905507, 589.0480861412066, 741.394042282245, 761.5392270474919, 696.7076031574667, 97.71690834678259, 133.88310562460137, 477.14123700454, 232.51289062265568, 859.0532473014114, 283.26666800895583, 876.7663131932245, 408.6467046872152, 189.02101371787072, 709.1488372061078, 789.422482305125, 577.9871458004462, 117.82860398115102, 7.194052450581467, 654.5461208031974, 687.7667367777761, 315.90171462927066, 362.2697897745908, 154.77858056181358, 651.5759347779317, 253.63272623198395, 854.9769262679285, 423.56154743336316, 365.64409443647173, 275.56054287998876, 680.6057050634485, 754.9719006098335, 413.2281582959364, 783.7884169635375, 482.4677570068806,), (370.212968879147, 554.9113562983285, 253.77764544269633, 306.6530866745945, 344.35630421485365, 705.4946079710852, 735.7824306046107, 854.9989228235969, 659.282849126203, 747.8141950798877, 446.5948774152193, 699.3114422950533, 161.79511770813627, 214.45814153808007, 400.56090642572286, 338.4378463093469, 550.5762145968181, 697.0574087244603, 706.7899335328698, 160.73800913321722, 964.5702513745175, 5.290633472571971, 91.08858423870403, 145.3284358357537, 925.8617877978492, 435.377434552764, 64.0804563693106, 221.770244619512, 80.03973669580266, 37.755338285078935, 384.326010426022, 984.4481006312732, 613.9655907167139, 521.0055432205589, 711.6058317089812, 597.4725638140962, 945.7158578456077, 820.2287344282204, 640.291314530137, 438.97204854123584, 201.7072428060146, 656.8842227068429, 803.9099091153836, 286.00651170869537, 33.609351396258425, 599.5149128924895, 515.8136315528284, 231.67097281806159, 169.48289181310216, 36.06113314434878,), (239.25662528737723, 0.28727185462340543, 157.44476085039082, 996.8171438272063, 779.6728789764172, 353.127663356528, 395.1431170512062, 586.0801652096985, 517.7427009892949, 736.925540146395, 68.61717193705897, 90.1658575951505, 284.4937870363794, 829.7648024492999, 915.008973948154, 348.09524824824246, 963.2876650654888, 276.5078486971222, 606.3634079922522, 194.52519308596106, 929.7138408121202, 574.9067217458147, 261.10750567193907, 407.13462610720165, 106.46495639257658, 72.38597052670393, 294.25182446812005, 947.0154651776626, 802.9422425723574, 956.7093495852752, 876.113046243067, 813.1473496063743, 574.8938787739089, 694.1918614713001, 966.0554556331059, 563.1686038049302, 769.9671927338335, 757.4732850466725, 961.2387253016144, 458.848429334124, 460.7204414645122, 586.8265171499795, 27.350697532073866, 116.2683014409226, 67.55426066125358, 633.6617975581885, 994.1980333654756, 676.9129760650999, 229.93013858915413, 315.7306940289286,), (955.4456871233602, 516.5042172048383, 9.723191714424928, 832.1770449232287, 248.25760936207752, 93.00943016209173, 673.6979167818138, 821.0746894925302, 76.02882053368909, 931.3962398356626, 476.5554540892897, 353.53792621617106, 894.3162528702067, 269.07578246879905, 947.1181090634782, 683.1073085237686, 909.9274589878878, 498.9993403636831, 199.6665390943092, 735.4290652428307, 872.7424984082979, 206.77863232200988, 202.76655878827333, 249.69543394695748, 618.5968991818082, 155.99556847615892, 106.6892901053912, 920.1881295329896, 675.8121248765266, 663.42407832045, 613.8269752757525, 764.244212635527, 541.4739546030245, 42.26312673099775, 482.2920812010506, 620.6674748317666, 492.340377704212, 985.3613293244484, 896.999773193295, 868.7688356245534, 490.8053516065467, 984.3946826775544, 916.0068296725573, 280.26662318318705, 222.09278307981327, 576.6651863474098, 54.14817128878413, 799.269917779981, 478.32007661204347, 540.767945591827,), (502.47521212167254, 393.7169228523979, 686.3386465303895, 174.80036211904903, 976.5071952370417, 698.2445680961396, 460.07059004643, 689.2105490596489, 11.819978610448922, 210.7523857014477, 581.0082189794467, 325.43619991542636, 612.7780963617714, 259.700769090298, 548.5634003791886, 237.13915250754525, 471.34536473845543, 613.0840431384478, 365.9241511219268, 498.7700166101968, 210.53248211065633, 700.7126268163007, 372.30962786881605, 854.3260872408196, 279.62953188068406, 179.88922872699587, 131.13916649872394, 575.8927292079621, 228.5667494996686, 99.86974090945311, 269.9491069025366, 235.80517288550763, 432.2187517308349, 380.883382231468, 145.84373533753637, 959.1479207466692, 149.54805726548292, 805.6683615575902, 176.60727665961284, 499.5978261118507, 995.5617702996016, 849.3899153966604, 517.0069749205006, 720.5603295246306, 785.3329366938784, 300.0341821534487, 562.1384520717112, 567.8345192053117, 398.40190186332757, 690.5431759606441,), (60.036377724016596, 813.8180692068463, 476.539424352137, 629.8622123621544, 449.85481906379175, 334.6565385145628, 360.96202105512157, 560.3500348142645, 931.8445757777681, 257.7148254524192, 19.83533361376, 121.23748921670374, 867.3685839741368, 963.1165609179141, 198.9581106368732, 575.8944574733487, 649.130951899575, 174.53883572945838, 780.3090980992371, 355.1090383908918, 673.1928401473501, 487.49374430436166, 736.5261647621551, 889.6324491382205, 381.07099841284577, 286.5235765557891, 631.7121904834689, 144.84833827806742, 167.57553548053383, 807.7171439570685, 337.3883099989331, 631.273408325837, 571.6375738737935, 848.9007719388086, 71.34407817655742, 161.99948894698534, 228.21793938787326, 316.8782416955612, 291.3563527944199, 267.4723908706379, 644.6442931551883, 271.248101351349, 444.89838764797173, 862.8064836470026, 363.1648402504287, 586.9197735260105, 965.5260328224946, 413.98906087367317, 183.886199103384, 23.10109231650759,), (727.7724164324044, 662.0225478086397, 940.4373373379956, 700.6969284246842, 80.06473787554714, 166.91046503965245, 103.00931405688607, 63.79239959116023, 878.6909764497414, 548.4337438182938, 26.364913366241026, 397.0125207601695, 764.9963721335957, 83.25120139068665, 262.45226426498715, 155.27436200016186, 654.8326755433498, 885.4713170963988, 309.3706535499986, 247.39651931519225, 284.175280058089, 626.4806846261897, 131.27711062686387, 838.5177788928746, 28.5712930199129, 663.5491353038045, 860.3688174133925, 325.20131665647875, 476.15474065537353, 974.9815454400946, 540.5841613922831, 272.41442761231417, 444.84493237074383, 969.5422645545461, 697.6228769070474, 177.03663997971208, 597.5841988365606, 631.0475226410597, 635.8511364258356, 563.9844867212875, 523.4598043545075, 639.528248710812, 309.6613281139966, 347.1683001641309, 539.6681221360597, 804.707766246453, 441.8603055765344, 365.7833219483778, 259.80704393772436, 303.57291557308895,), (0.09234639492805563, 815.3797891608882, 847.9464883826706, 343.88491724999426, 469.19795524402616, 8.3171105963733, 922.0883957440528, 946.9575118681244, 477.06707045145714, 9.132599576333167, 430.43531326923346, 292.9226130023878, 231.21539351915854, 7.217985005767535, 373.64387652703357, 411.73586906613457, 560.5543586396154, 394.75207962018334, 163.26949533028878, 737.1178541033773, 389.7232698531959, 378.35806027496176, 262.98027544624904, 422.23938275369267, 239.56181493856056, 764.5382940766145, 909.9544034392849, 807.5749404275756, 684.6402903785192, 284.67685467345297, 742.9268921311517, 808.8086490990372, 412.9038765132473, 853.4820233916041, 182.4071790026428, 289.59529929128547, 636.8927095483467, 617.7294139025154, 272.13413045162406, 622.7537802755535, 187.7894425440112, 19.39539724699635, 49.632072223098625, 534.9707530597663, 185.93759814152565, 102.27348865653974, 269.19513288914555, 715.195942211275, 727.107070447535, 232.81049795595544,), (150.68369369748856, 493.5795531957453, 341.91285386862626, 311.5766852030555, 799.4623519360701, 997.9634461655995, 463.6844672034293, 791.4247142652953, 330.24864703583034, 843.5462992680785, 951.644041732608, 56.038344401892815, 775.6529107125235, 71.38469474905685, 470.1467765987197, 192.19229292645278, 841.5392879402597, 817.2507239995704, 828.2520473943315, 121.96155618577076, 768.1456708511898, 248.96236050723408, 771.2253330273345, 442.96094214292003, 737.6190125535086, 33.51029157027507, 460.8346353122799, 770.9959677901886, 521.1391200572839, 982.1158514056141, 472.8255449915152, 681.4423200578532, 312.1093655840698, 323.0631884829062, 629.1642541306993, 42.18591559473106, 937.4209236322339, 520.9262529175096, 253.31274033508888, 638.5200120420238, 198.72363180239984, 887.5195760435424, 863.6576657496004, 217.78824905510285, 112.92845931623752, 632.9089659017448, 324.5134876300968, 167.36035640828607, 276.1000438384478, 119.64959201073866,), (789.1437801948886, 8.929855889342143, 41.993369021095404, 783.4752402510599, 475.27067876165006, 596.2122839316125, 371.39866330323366, 89.47990274967444, 157.6984561973751, 91.40155230421298, 618.6448584379411, 930.8487845186271, 996.9731029835355, 625.1323302094067, 59.775131609422495, 644.5748966479184, 701.3281400230029, 791.4662443652128, 125.8274576652264, 232.67463085606354, 981.6757109638336, 788.5618257868736, 756.697523201389, 805.5253765865858, 438.71889809659734, 192.96529905015848, 689.2499802840422, 358.1674972504314, 134.73217376095226, 898.6630442700426, 485.30213316251314, 437.5000041650592, 294.7145964408969, 697.0377749956548, 189.77583621965144, 186.6267289491038, 347.6646790182655, 732.4024101227128, 275.32446285830315, 831.3495933911199, 914.4740019780461, 554.5203101660566, 68.87658336115943, 155.02547807532696, 295.67266121073476, 263.349268895432, 367.11270700453946, 0.7198287242494716, 638.0260624435374, 379.04356966657735,), (185.53429009714995, 18.719856588055194, 856.8292883342145, 776.0165677979388, 238.78655821602112, 721.0035626839232, 658.28941737982, 538.9565215934048, 387.49084018073563, 524.0697688104523, 497.54915956954727, 551.4678072492145, 613.0392423505267, 322.60003991279393, 640.9039297850732, 110.46413403940414, 523.2005391932355, 66.67984309626563, 835.1933578982914, 129.7742663222461, 873.4927775805724, 202.07902868878213, 485.3702872366953, 98.75358664853229, 571.012181354699, 836.2995266064913, 657.9212582849684, 525.8213626144117, 701.3140215747126, 255.93853033397806, 366.3540200942962, 605.95362423245, 70.873960950122, 930.211036809051, 208.73809349350148, 491.4055014282741, 889.7673251634665, 79.95680323287202, 801.0199490108841, 60.70018828605151, 558.0198894700217, 938.2438576998607, 415.9532027097531, 369.0686508584735, 708.4337849788369, 823.6429662774005, 265.8156344966652, 40.65315227434285, 70.38812961024409, 303.90454886805543,), (944.6034820072357, 960.5867454371248, 97.11925015766253, 725.1719328091889, 531.6610514024726, 189.46041310667573, 526.1920217936971, 248.76302905401337, 625.325053539152, 178.8978916289171, 749.1943579773724, 415.18460024974024, 105.51418189915573, 638.6242998880421, 357.96336587140377, 458.7205924283744, 665.2423547735436, 882.8240193732261, 166.80931890354745, 180.39653860006823, 401.5571736148952, 337.512870561569, 158.1764558993315, 998.4335720532814, 443.01216999828785, 352.3240752703322, 315.1549961423973, 991.4629906181269, 324.65410862009236, 371.71951243537126, 764.1843679043711, 430.5880727332455, 725.8676836151698, 608.413370257039, 563.1447028042841, 213.9281360437204, 765.7082196342848, 926.1469225926575, 254.08912718302412, 961.6517756313344, 447.49978899824936, 397.09056168940384, 726.3132783372915, 982.9564711785478, 595.7628266237857, 517.6093588403293, 993.4120095545393, 301.8416539323987, 300.3339617281736, 221.8867034824401,), (855.7204255082062, 21.70137145759954, 821.9399224471144, 689.719183909427, 275.95713674360564, 553.6631532853207, 556.3230855067685, 925.9224502117322, 154.7147862187559, 37.73630008376983, 355.65575328549073, 138.40819347641, 367.0828930260639, 582.15570635987, 232.99414855423439, 811.0742367826583, 91.90512128537264, 399.79824847776393, 917.8818033444678, 734.2659538775798, 723.580526744515, 792.90419431428, 172.9024908563226, 825.7052443848854, 689.5951010498992, 576.2324617766981, 907.6556421470943, 595.2312025595801, 300.39325384327464, 730.7893619562772, 576.2836961563387, 78.47739592285386, 55.9227088233194, 770.9015036701385, 347.9302069529332, 817.1417000465422, 416.5217126179194, 867.8310969512203, 869.7865141212199, 226.7176713621516, 652.7791831603508, 602.3016343165425, 11.43438096260352, 777.4186181440473, 382.4869621852632, 304.78339287924285, 41.18225216356319, 539.9297294386691, 149.58001916730123, 502.40161044723277,), (220.79717482028516, 50.51981414582751, 731.5785735649414, 392.8827045190352, 445.61623597771074, 595.1321912948403, 504.7293461120348, 222.0856281514143, 289.78302915668706, 394.3224038780659, 132.18904537087806, 82.54511263858267, 571.4405905106294, 49.31109807008804, 399.1912995469419, 85.0789874682354, 501.8231156057946, 773.8251365562567, 130.37517489517413, 134.87076372595496, 559.2961773135403, 487.8610998043328, 652.2484389791689, 196.09930039322077, 615.9968375942019, 735.6677541453226, 246.24584050711306, 71.64398034108488, 776.7718987916836, 323.41164115621126, 924.1380808950829, 89.59464812673524, 671.7475534497354, 423.54060236564004, 348.3078568465243, 320.7383628526695, 593.8771490410704, 24.206830335269046, 304.8188475463931, 987.6519169637293, 616.2209944545034, 990.1591822713317, 442.2101052342515, 145.81847519027758, 44.87755093137324, 818.1719201318144, 199.68521360575576, 373.82079032056936, 757.7337825930621, 852.7641451792005,), (112.37174078510425, 54.53801336175423, 948.9409150257889, 926.7296951091071, 868.7523516449686, 820.1339564299961, 13.733292693431377, 693.7952250561176, 111.27799231646985, 450.0615706383459, 22.748115151202008, 209.00954112420212, 538.0054579069861, 203.80135287660516, 523.2658814665313, 258.6580034255985, 483.02633034469176, 729.9235835097888, 141.34742092394183, 698.7552320082528, 18.38851255630447, 583.0049433264157, 663.5283419996432, 43.482007499458966, 170.31964704476331, 284.0135410479907, 789.1884945074221, 617.9647763913978, 53.08524865627895, 654.757871815387, 8.334175248427611, 388.6451472369792, 271.3103790803375, 852.0829447572587, 660.1003733293647, 864.2822735599534, 19.078323934194174, 867.4104428544812, 649.4111447522284, 231.16862013781702, 380.7012029628479, 976.6119398898542, 99.60371184253735, 315.45596930877974, 866.7727455281198, 531.5606451632132, 186.41738741751902, 500.65082286887883, 457.98626214778074, 926.3499730264335,), (21.488458214345528, 247.39213974975127, 529.4153638551198, 333.56963463077574, 393.3998178272747, 157.0052505908327, 346.7799029075455, 351.913082267181, 625.2309702510104, 236.20476570721272, 978.2442672445462, 501.25146117715735, 811.8928600997455, 625.792363715763, 878.6772741911603, 890.4020860787615, 814.858637678337, 29.46589845520775, 554.9393515968055, 280.19384462820506, 151.70818076868466, 897.1753149691715, 656.9317589496127, 87.79501713730143, 382.2071218544083, 960.6056934445716, 612.4483933501054, 625.5246004948266, 227.42797745267552, 240.36080817057604, 152.7491541688516, 970.537173095217, 909.7353935641712, 329.2847383705465, 542.2337512058923, 206.75727466100935, 138.5765267985366, 541.353826851015, 800.1050591640936, 862.5876615800681, 308.9983961621827, 705.1363877851007, 523.8054835018718, 135.25249384391523, 995.6858002320087, 975.7769090685993, 145.15239489251186, 933.0299464229068, 917.1121206734174, 317.4962108419391,), (557.3402980935688, 948.6015641647615, 118.38729071717869, 317.59758919121697, 879.6381216162653, 727.0795332601915, 765.4350125691849, 880.131900108576, 414.04015541967885, 411.2517632793493, 443.004487049652, 933.768524391193, 894.1300931338027, 933.2500304158002, 273.79616463152354, 779.1078849727928, 106.77143284340107, 184.74649657456732, 762.4474837329655, 611.981597669024, 266.86261300447956, 567.0430929389746, 230.911348733013, 232.18253426933566, 687.377885178361, 359.26133453455134, 688.141284732716, 476.6022200441854, 502.32458745046125, 604.7117596027515, 712.01912797666, 373.95940113050096, 852.1294324655852, 491.4455970663347, 137.5127223416147, 193.25982947851017, 32.22955322704579, 764.5388541282347, 15.014260451757021, 269.85870264618126, 413.04402219171976, 742.3666616628996, 988.2434174629566, 757.7765897256293, 66.13632423488936, 927.1032061777938, 985.6276395863681, 867.1290026386034, 489.9428985108636, 324.8956268040456,), (457.5449294417121, 246.78384086158633, 404.86676824252845, 41.826275050118156, 735.3317798996605, 380.3741687551209, 312.89749141149747, 611.504874670052, 742.4678547079448, 593.805801392789, 525.2305317028896, 875.7829718598168, 786.9198623188731, 520.6490912237487, 451.6095925636621, 827.0101016593499, 42.41591600909778, 995.8363715240481, 518.7026525875117, 395.6111966358917, 735.1700815502675, 557.7009498134095, 516.1266830947985, 630.6485530927204, 49.29544918127604, 291.17936223412954, 398.0398914545593, 304.5541478222386, 827.6210076057507, 461.347163963963, 422.46218245044145, 613.1342792438925, 54.54666681552644, 516.969650266138, 142.23961877509163, 829.8935044266395, 451.6980878296554, 722.646791492833, 113.17158624359503, 778.7311418760397, 937.8424576992394, 696.1141418397956, 135.30667419036257, 413.55930509761663, 450.8858548470743, 178.8785789436156, 590.315475289654, 712.6343913063026, 201.9320047091576, 454.6832076473508,), (250.08229407723826, 691.7174283309014, 907.1624630054067, 796.4862224757226, 718.374719718563, 123.54421683543349, 114.16493882407775, 449.3984180247218, 362.94338915725456, 523.8173060661488, 384.0873742615717, 791.0665942370252, 511.6664514845053, 949.7538891427195, 378.67863141257976, 380.60700926114674, 768.2603237843883, 912.2527788221324, 565.4915785141691, 659.5303406020439, 150.08973451051176, 868.8185374782038, 178.86676467326868, 712.0474878796226, 419.58169123576727, 309.5183054369818, 768.6304223609426, 446.2582341490725, 626.4528496615293, 117.55397840247983, 131.57103181208575, 202.8517052794463, 622.5603279873416, 253.11097511803925, 458.64265782853664, 855.4110564190316, 543.5011596043003, 5.750548899053909, 882.7029205371946, 237.87835401947677, 588.8623271843092, 475.71198980315387, 410.15136571135633, 79.40491736841615, 600.1021042955892, 244.71317143863337, 547.3416095831464, 619.7660388695853, 557.5486482688888, 825.2525047036826,), (48.918799943029725, 148.25616817969035, 653.2768782155202, 36.72105182241203, 853.8292872341877, 666.996542946195, 838.014435792606, 298.44154664211777, 920.3846982695362, 49.03136274879905, 416.9569238553582, 178.2483917574228, 672.090495261405, 611.089756048654, 691.5136180806667, 594.9689863047196, 787.3075795335859, 177.4192673436795, 455.3378280202657, 578.9662924422967, 931.4798193860056, 93.12628782176957, 299.2944815965134, 368.56942547507407, 378.9393515904601, 67.66667782099533, 427.3238393910946, 550.4710828134854, 292.83271335673953, 134.54578907805103, 694.619438494801, 274.4185427517334, 527.0527334046208, 524.6462092537088, 695.8294973451716, 612.053744928424, 109.30223443632624, 729.7285197626795, 628.9785990785555, 987.1892637585828, 483.662983209447, 688.6542782543451, 933.8916747881832, 986.2784423441115, 287.1873038042605, 608.8446069344726, 316.48855849847223, 525.3912253315947, 995.0243355575512, 353.85334437419004,)))"
+ ]
+ },
+ "execution_count": 43,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Matrix((639.4267984578837, ...), ..., (..., 353.85334437419004))[100x50]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(m)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Similarily, `v` is now a `Vector` with $50$ entries."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "v = Vector(1_000 * random.random() for _ in range(50))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((129.7131375543026, 562.6343376666077, 519.7058001844938, 631.8576074968361, 492.50445371851595, 179.90723165619127, 609.4057577306154, 708.5870675033931, 979.2576595660704, 1.580917225831202, 23.986797518176004, 625.4607423896532, 117.92572392127187, 848.0697885701867, 799.5643512555062, 998.9870072501308, 414.04113952437183, 333.7922569338235, 560.4155049178265, 637.5035435880566, 11.297267978029769, 201.1871389300902, 281.62670325053864, 790.1955028462116, 307.77254924389706, 506.6896270960092, 323.9238393830701, 6.131262435292828, 685.8357886469579, 341.36158523000273, 724.3966428110118, 615.9933429964842, 29.117381811460618, 175.62908823799773, 330.51483209301, 337.936855273543, 672.4729573660214, 916.1630531735751, 797.2543838289249, 645.6522206645749, 481.4955231203607, 627.2004877076889, 892.0583267899182, 536.9675449723645, 335.10965453438513, 783.9890332085388, 413.95306533418574, 742.5846461655369, 835.1057359656187, 299.3437466393607))"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 47,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Vector(129.7131375543026, ..., 299.3437466393607)[50]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(v)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The arithmetic works as before."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 48,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "w = m * v"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Vector(11378937.3105893, ..., 13593029.305789862)[100]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(w)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We can multiply `m` with its transpose or the other way round."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 50,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "n = m * m.transpose()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 51,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Matrix((14370711.265265431, ...), ..., (..., 16545418.239505699))[100x100]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(n)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 52,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "o = m.transpose() * m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 53,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Matrix((32618511.50703142, ...), ..., (..., 32339164.77803234))[50x50]\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(o)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "## Comparison with [numpy](https://www.numpy.org/)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We started out in this chapter by realizing that Python provides us no good data type to model a vector $\\vec{x}$ or a matrix $\\bf{A}$. Then, we built up two custom data types, `Vector` and `Matrix`, that wrap a simple `tuple` object for $\\vec{x}$ and a `tuple` of `tuple`s for $\\bf{A}$ so that we can interact with their `._entries` in a \"natural\" way, which is similar to how we write linear algebra tasks by hand. By doing this, we extend Python with our own little \"dialect\" or **[domain-specific language ](https://en.wikipedia.org/wiki/Domain-specific_language)** (DSL).\n",
+ "\n",
+ "If we feel like sharing our linear algebra library with the world, we could easily do so on either [GitHub ](https://github.com) or [PyPI](https://pypi.org). However, for the domain of linear algebra this would be rather pointless as there is already a widely adopted library with [numpy](https://www.numpy.org/) that not only has a lot more features than ours but also is implemented in C, which makes it a lot faster with big data.\n",
+ "\n",
+ "Let's model the example in the [first part ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb#Example:-Vectors-&-Matrices) with both [numpy](https://www.numpy.org/) and our own DSL and compare them."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 54,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x = (1, 2, 3)\n",
+ "A = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 55,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "TypeError",
+ "evalue": "can't multiply sequence by non-int of type 'tuple'",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mA\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m: can't multiply sequence by non-int of type 'tuple'"
+ ]
+ }
+ ],
+ "source": [
+ "A * x"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The creation of vectors and matrices is similar to our DSL. However, numpy uses the more general concept of an **n-dimensional array** (i.e., the `ndarray` type) where a vector is only a special case of a matrix and a matrix is yet another special case of an even higher dimensional structure."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 56,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "import numpy as np"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 57,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x_arr = np.array(x)\n",
+ "A_arr = np.array(A)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "x_vec = Vector(x)\n",
+ "A_mat = Matrix(A)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The text representations are very similar. However, [numpy](https://www.numpy.org/)'s `ndarray`s keep the entries as `int`s while our `Vector` and `Matrix` objects contain `float`s."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 59,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([1, 2, 3])"
+ ]
+ },
+ "execution_count": 59,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x_arr"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 60,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((1.0, 2.0, 3.0))"
+ ]
+ },
+ "execution_count": 60,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x_vec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[1, 2, 3],\n",
+ " [4, 5, 6],\n",
+ " [7, 8, 9]])"
+ ]
+ },
+ "execution_count": 61,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_arr"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_mat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "[numpy](https://www.numpy.org/)'s `ndarray`s come with a `.shape` instance attribute that returns a `tuple` with the dimensions ..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(3,)"
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x_arr.shape"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 64,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(3, 3)"
+ ]
+ },
+ "execution_count": 64,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_arr.shape"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "... while `Matrix` objects come with `.n_rows` and `.n_cols` properties."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(3, 3)"
+ ]
+ },
+ "execution_count": 65,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_mat.n_rows, A_mat.n_cols"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The built-in [len() ](https://docs.python.org/3/library/functions.html#len) function does not return the number of entries in an `ndarray` but the number of the rows instead. This is equivalent to the first element in the `.shape` attribute."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 66,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 66,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(x_arr)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 67,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 67,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(x_vec)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 68,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "3"
+ ]
+ },
+ "execution_count": 68,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(A_arr)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 69,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "9"
+ ]
+ },
+ "execution_count": 69,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "len(A_mat)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "The `.transpose()` method also exists for `ndarray`s."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 70,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[1, 4, 7],\n",
+ " [2, 5, 8],\n",
+ " [3, 6, 9]])"
+ ]
+ },
+ "execution_count": 70,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_arr.transpose()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 71,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Matrix(((1.0, 4.0, 7.0,), (2.0, 5.0, 8.0,), (3.0, 6.0, 9.0,)))"
+ ]
+ },
+ "execution_count": 71,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_mat.transpose()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "To perform matrix-matrix, matrix-vector, or vector-matrix multiplication in [numpy](https://www.numpy.org/), we use the `.dot()` method. If we use the `*` operator with `ndarray`s, an *entry-wise* multiplication is performed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([14, 32, 50])"
+ ]
+ },
+ "execution_count": 72,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_arr.dot(x_arr)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 73,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[ 1, 4, 9],\n",
+ " [ 4, 10, 18],\n",
+ " [ 7, 16, 27]])"
+ ]
+ },
+ "execution_count": 73,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_arr * x_arr"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 74,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((14.0, 32.0, 50.0))"
+ ]
+ },
+ "execution_count": 74,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "A_mat * x_vec"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Scalar multiplication, however, works as expected."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 75,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([10, 20, 30])"
+ ]
+ },
+ "execution_count": 75,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10 * x_arr"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 76,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Vector((10.0, 20.0, 30.0))"
+ ]
+ },
+ "execution_count": 76,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "10 * x_vec"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "Because we implemented our classes to support the sequence protocol, [numpy](https://www.numpy.org/)'s *one*-dimensional `ndarray`s are actually able to work with them: The `*` operator is applied on a per-entry basis."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 77,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([2., 4., 6.])"
+ ]
+ },
+ "execution_count": 77,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x_arr + x_vec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 78,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([1., 4., 9.])"
+ ]
+ },
+ "execution_count": 78,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "x_arr * x_vec"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 79,
+ "metadata": {
+ "slideshow": {
+ "slide_type": "fragment"
+ }
+ },
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "operands could not be broadcast together with shapes (3,3) (9,) ",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mA_arr\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mA_mat\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (3,3) (9,) "
+ ]
+ }
+ ],
+ "source": [
+ "A_arr + A_mat"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "We conclude that it is rather easy to extend Python in a way that makes the resulting application code read like core Python again. As there are many well established third-party packages out there, it is unlikely that we have to implement a fundamental library ourselves. Yet, we can apply the concepts introduced in this chapter to organize the code in the applications we write."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "livereveal": {
+ "auto_select": "code",
+ "auto_select_fragment": true,
+ "scroll": true,
+ "theme": "serif"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "384px"
+ },
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/05_summary.ipynb b/11_classes/05_summary.ipynb
new file mode 100644
index 0000000..47baf9f
--- /dev/null
+++ b/11_classes/05_summary.ipynb
@@ -0,0 +1,100 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "slide"
+ }
+ },
+ "source": [
+ "# Chapter 11: Classes & Instances (TL;DR)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "slideshow": {
+ "slide_type": "skip"
+ }
+ },
+ "source": [
+ "With the `class` statement, we can create a *user-defined* data type that we also call a **class**.\n",
+ "\n",
+ "Then, to create new **instances** of the data type, we simply call the class, just as we do with the built-in constructors.\n",
+ "\n",
+ "Conceptually, a class is the **blueprint** defining the **behavior** each instance exhibits.\n",
+ "\n",
+ "In the example used throughout the chapter, the `Vector` and `Matrix` classes implement the linear algebra rules all `Vector` and `Matrix` instances follow *in general*. On the contrary, the instances **encapsulate** the **state** of *concrete* vectors and matrices.\n",
+ "\n",
+ "The `class` statement acts as a *namespace* that consists of simple *variable assignments* and *function definitions*:\n",
+ "1. Variables become the **class attributes** that are shared among all instances.\n",
+ "2. Functions may take a different role:\n",
+ " - By default, they become **instance methods** by going through a **binding process** where a reference to the instance on which the method is *invoked* is passed in as the first argument. By convention, the corresponding parameter is called `self`; it embodies an instance's state: That means that instance methods set and get **instance attributes** on and from `self`.\n",
+ " - They may be declared as **class methods**. Then, the binding process is adjusted such that the first argument passed in is a reference to the class itself and, by convention, named `cls`. A common use case is to design **alternative constructors**.\n",
+ " - They may also be declared as **properties**. A use case for that are *derived* attributes that follow semantically from an instance's state.\n",
+ " \n",
+ "The **Python Data Model** concerns what special methods (i.e., the ones with the dunder names) exists and how they work together.\n",
+ "\n",
+ "The instantiation process is controlled by the `.__init__()` method.\n",
+ "\n",
+ "The `__repr__()` and `__str__()` methods implement the **text representation** of an instance, which can be regarded as a Unicode encoded representation of all the state encapsulated in an instance.\n",
+ "\n",
+ "**Sequence emulation** means that a user-defined data type exhibits the same four properties as the built-in sequences, which are regarded as finite and iterable containers with a predictable order. The `.__len__()`, `.__iter__()`, `__reversed__()`, `__getitem__()`, and some others are used to implement the corresponding behaviors.\n",
+ "\n",
+ "Similarly, **number emulation** means that an instance of a user-defined data type behaves like a built-in number. For example, by implementing the `.__abs__()` method, an instance may be passed to the built-in [abs() ](https://docs.python.org/3/library/functions.html#abs) function.\n",
+ "\n",
+ "If different data types, built-in or user-defined, share a well-defined set of behaviors, a single function may be written to work with objects of all the data types. We describe such functions as **polymorphic**.\n",
+ "\n",
+ "Classes may specify how *operators* are **overloaded**. Examples for that are the `.__add__()`, `.__sub__()`, or `.__eq__()` methods.\n",
+ "\n",
+ "**Packages** are folders containing **modules** (i.e., \\*.py files) and a \"*\\_\\_init\\_\\_.py*\" file. We use them to design coherent libraries with reusable code."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "livereveal": {
+ "auto_select": "code",
+ "auto_select_fragment": true,
+ "scroll": true,
+ "theme": "serif"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {
+ "height": "calc(100% - 180px)",
+ "left": "10px",
+ "top": "150px",
+ "width": "384px"
+ },
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/06_review.ipynb b/11_classes/06_review.ipynb
new file mode 100644
index 0000000..df7af79
--- /dev/null
+++ b/11_classes/06_review.ipynb
@@ -0,0 +1,271 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Chapter 11: Classes & Instances (Review Questions)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The questions below assume that you have read the [first ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb), [second ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/02_content.ipynb), [third ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/03_content.ipynb), and [fourth ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/04_content.ipynb) part of Chapter 11.\n",
+ "\n",
+ "Be concise in your answers! Most questions can be answered in *one* sentence."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Essay Questions "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q1**: How are **classes** a way to manage the **state** in a big program? How should we think of classes conceptually?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q2**: What do we mean with **instantiation**? How do **instances** relate to **classes**?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q3:** What is an **implementation detail**? Name two different examples of implementation details regarding the `Vector` and `Matrix` classes!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q4**: How are **instance methods** different from **class methods**? How do **special methods** fit into the picture?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q5**: How do **mutability** and **immutability** come into play when designing a user-defined data type?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q6**: Explain the concept of **method chaining**!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q7**: How can we implement **operator overloading** for a user-defined data type? When do we need to user *reverse* special methods?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## True / False Questions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Motivate your answer with *one short* sentence!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q8**: An instance's **text representation** is a `bytes` object with a special encoding."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q9**: Computed **properties** are special kinds of **instance methods**."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q10**: **Sequence emulation** means designing a user-defined data type around the built-in `list` or `tuple` types."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q11**: The **Python Data Model** can be regarded as the \"Theory\" or \"Mental Model\" behind the Python language."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q12**: **Polymorphism** means that two instances have the same data type, be it a built-in or user-defined one."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q13**: **Number emulation** means that two instances of the same user-defined data type can be added together."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**Q14**: **Packages** are a good place to collect all the code to be reused in a data science project, for example, across different Jupyter notebooks."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ " < your answer >"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.6"
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": false,
+ "sideBar": true,
+ "skip_h1_title": true,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {},
+ "toc_section_display": false,
+ "toc_window_display": false
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/11_classes/sample_package/__init__.py b/11_classes/sample_package/__init__.py
new file mode 100644
index 0000000..8e87577
--- /dev/null
+++ b/11_classes/sample_package/__init__.py
@@ -0,0 +1,34 @@
+"""This package provides linear algebra functionalities.
+
+The package is split into three modules:
+- matrix: defines the Matrix class
+- vector: defines the Vector class
+- utils: defines the norm() function that is shared by Matrix and Vector
+ and package-wide constants
+
+The classes implement arithmetic operations involving vectors and matrices.
+
+See the docstrings in the modules and classes for further info.
+"""
+
+# Import the classes here so that they are available
+# from the package's top level. That means that a user
+# who imports this package with `import sample_package`
+# may then refer to, for example, the Matrix class with
+# simply `sample_package.Matrix` instead of the longer
+# `sample_package.matrix.Matrix`.
+from sample_package.matrix import Matrix
+from sample_package.vector import Vector
+
+
+# Define meta information for the package.
+# There are other (and more modern) ways of
+# doing this, but specifying the following
+# dunder variables here is the traditional way.
+__name__ = "linear_algebra_tools"
+__version__ = "0.1.0" # see https://semver.org/ for how the format works
+__author__ = "Alexander Hess"
+
+# Define what is imported with the "star import"
+# (i.e., with `from sample_package import *`).
+__all__ = ["Matrix", "Vector"]
diff --git a/11_classes/sample_package/matrix.py b/11_classes/sample_package/matrix.py
new file mode 100644
index 0000000..50a683f
--- /dev/null
+++ b/11_classes/sample_package/matrix.py
@@ -0,0 +1,443 @@
+"""This module defines a Matrix class."""
+
+import numbers
+
+# Note the import at the bottom of this file, and
+# see the comments about imports in the matrix module.
+from sample_package import utils
+
+
+class Matrix:
+ """An m-by-n-dimensional matrix from linear algebra.
+
+ All entries are converted to floats, or whatever is set in the typing attribute.
+
+ Attributes:
+ storage (callable): data type used to store the entries internally;
+ defaults to tuple
+ typing (callable): type casting applied to all entries upon creation;
+ defaults to float
+ vector_cls (vector.Vector): a reference to the Vector class to work with
+ zero_threshold (float): max. tolerance when comparing an entry to zero;
+ defaults to 1e-12
+ """
+
+ storage = utils.DEFAULT_ENTRIES_STORAGE
+ typing = utils.DEFAULT_ENTRY_TYPE
+ # the `vector_cls` attribute is set at the bottom of this file
+ zero_threshold = utils.ZERO_THRESHOLD
+
+ def __init__(self, data):
+ """Create a new matrix.
+
+ Args:
+ data (sequence of sequences): the matrix's entries;
+ viewed as a sequence of the matrix's rows (i.e., row-major order);
+ use the .from_columns() class method if the data come as a sequence
+ of the matrix's columns (i.e., column-major order)
+
+ Raises:
+ ValueError:
+ - if no entries are provided
+ - if the number of columns is inconsistent across the rows
+
+ Example Usage:
+ >>> Matrix([(1, 2), (3, 4)])
+ Matrix(((1.0, 2.0,), (3.0, 4.0,)))
+ """
+ self._entries = self.storage(
+ self.storage(self.typing(x) for x in r) for r in data
+ )
+ for row in self._entries[1:]:
+ if len(row) != self.n_cols:
+ raise ValueError("rows must have the same number of entries")
+ if len(self) == 0:
+ raise ValueError("a matrix must have at least one entry")
+
+ @classmethod
+ def from_columns(cls, data):
+ """Create a new matrix.
+
+ This is an alternative constructor for data provided in column-major order.
+
+ Args:
+ data (sequence of sequences): the matrix's entries;
+ viewed as a sequence of the matrix's columns (i.e., column-major order);
+ use the normal constructor method if the data come as a sequence
+ of the matrix's rows (i.e., row-major order)
+
+ Raises:
+ ValueError:
+ - if no entries are provided
+ - if the number of rows is inconsistent across the columns
+
+ Example Usage:
+ >>> Matrix.from_columns([(1, 2), (3, 4)])
+ Matrix(((1.0, 3.0,), (2.0, 4.0,)))
+ """
+ return cls(data).transpose()
+
+ @classmethod
+ def from_rows(cls, data):
+ """See docstring for .__init__()."""
+ # Some users may want to use this .from_rows() constructor
+ # to explicitly communicate that the data are in row-major order.
+ # Otherwise, this method is redundant.
+ return cls(data)
+
+ def __repr__(self):
+ """Text representation of a Matrix."""
+ name = self.__class__.__name__
+ args = ", ".join(
+ "(" + ", ".join(repr(c) for c in r) + ",)" for r in self._entries
+ )
+ return f"{name}(({args}))"
+
+ def __str__(self):
+ """Human-readable text representation of a Matrix."""
+ name = self.__class__.__name__
+ first, last, m, n = self[0], self[-1], self.n_rows, self.n_cols
+ return f"{name}(({first!r}, ...), ..., (..., {last!r}))[{m:d}x{n:d}]"
+
+ @property
+ def n_rows(self):
+ """Number of rows in a Matrix."""
+ return len(self._entries)
+
+ @property
+ def n_cols(self):
+ """Number of columns in a Matrix."""
+ return len(self._entries[0])
+
+ def __len__(self):
+ """Number of entries in a Matrix."""
+ return self.n_rows * self.n_cols
+
+ def __getitem__(self, index):
+ """Obtain an individual entry of a Matrix.
+
+ Args:
+ index (int / tuple of int's): if index is an integer,
+ the Matrix is viewed as a sequence in row-major order;
+ if index is a tuple of integers, the first one refers to
+ the row and the second one to the column of the entry
+
+ Returns:
+ entry (Matrix.typing)
+
+ Example Usage:
+ >>> m = Matrix([(1, 2), (3, 4)])
+ >>> m[0]
+ 1.0
+ >>> m[-1]
+ 4.0
+ >>> m[0, 1]
+ 2.0
+ """
+ # Sequence-like indexing (one-dimensional)
+ if isinstance(index, int):
+ if index < 0:
+ index += len(self)
+ if not (0 <= index < len(self)):
+ raise IndexError("integer index out of range")
+ row, col = divmod(index, self.n_cols)
+ return self._entries[row][col]
+ # Mathematical-like indexing (two-dimensional)
+ elif (
+ isinstance(index, tuple)
+ and len(index) == 2
+ and isinstance(index[0], int)
+ and isinstance(index[1], int)
+ ):
+ return self._entries[index[0]][index[1]]
+ raise TypeError("index must be either an int or a tuple of two int's")
+
+ def rows(self):
+ """Loop over a Matrix's rows.
+
+ Returns:
+ rows (generator): produces a Matrix's rows as Vectors
+ """
+ return (self.vector_cls(r) for r in self._entries)
+
+ def cols(self):
+ """Loop over a Matrix's columns.
+
+ Returns:
+ columns (generator): produces a Matrix's columns as Vectors
+ """
+ return (
+ self.vector_cls(self._entries[r][c] for r in range(self.n_rows))
+ for c in range(self.n_cols)
+ )
+
+ def entries(self, *, reverse=False, row_major=True):
+ """Loop over a Matrix's entries.
+
+ Args:
+ reverse (bool): flag to loop backwards; defaults to False
+ row_major (bool): flag to loop in row-major order; defaults to True
+
+ Returns:
+ entries (generator): produces a Matrix's entries
+ """
+ if reverse:
+ rows = range(self.n_rows - 1, -1, -1)
+ cols = range(self.n_cols - 1, -1, -1)
+ else:
+ rows, cols = range(self.n_rows), range(self.n_cols)
+ if row_major:
+ return (self._entries[r][c] for r in rows for c in cols)
+ return (self._entries[r][c] for c in cols for r in rows)
+
+ def __iter__(self):
+ """Loop over a Matrix's entries.
+
+ See .entries() for more customization options.
+ """
+ return self.entries()
+
+ def __reversed__(self):
+ """Loop over a Matrix's entries in reverse order.
+
+ See .entries() for more customization options.
+ """
+ return self.entries(reverse=True)
+
+ def __add__(self, other):
+ """Handle `self + other` and `other + self`.
+
+ This may be either matrix addition or broadcasting addition.
+
+ Example Usage:
+ >>> Matrix([(1, 2), (3, 4)]) + Matrix([(2, 3), (4, 5)])
+ Matrix(((3.0, 5.0,), (7.0, 9.0,)))
+
+ >>> Matrix([(1, 2), (3, 4)]) + 5
+ Matrix(((6.0, 7.0,), (8.0, 9.0,)))
+
+ >>> 10 + Matrix([(1, 2), (3, 4)])
+ Matrix(((11.0, 12.0,), (13.0, 14.0,)))
+ """
+ # Matrix addition
+ if isinstance(other, self.__class__):
+ if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):
+ raise ValueError("matrices must have the same dimensions")
+ return self.__class__(
+ (s_col + o_col for (s_col, o_col) in zip(s_row, o_row))
+ for (s_row, o_row) in zip(self._entries, other._entries)
+ )
+ # Broadcasting addition
+ elif isinstance(other, numbers.Number):
+ return self.__class__((c + other for c in r) for r in self._entries)
+ return NotImplemented
+
+ def __radd__(self, other):
+ """See docstring for .__add__()."""
+ if isinstance(other, self.vector_cls):
+ raise TypeError("vectors and matrices cannot be added")
+ # As both matrix and broadcasting addition are commutative,
+ # we dispatch to .__add__().
+ return self + other
+
+ def __sub__(self, other):
+ """Handle `self - other` and `other - self`.
+
+ This may be either matrix subtraction or broadcasting subtraction.
+
+ Example Usage:
+ >>> Matrix([(2, 3), (4, 5)]) - Matrix([(1, 2), (3, 4)])
+ Matrix(((1.0, 1.0,), (1.0, 1.0,)))
+
+ >>> Matrix([(1, 2), (3, 4)]) - 1
+ Matrix(((0.0, 1.0,), (2.0, 3.0,)))
+
+ >>> 10 - Matrix([(1, 2), (3, 4)])
+ Matrix(((9.0, 8.0,), (7.0, 6.0,)))
+ """
+ # As subtraction is the inverse of addition,
+ # we first dispatch to .__neg__() to invert the signs of
+ # all entries in other and then dispatch to .__add__().
+ return self + (-other)
+
+ def __rsub__(self, other):
+ """See docstring for .__sub__()."""
+ if isinstance(other, self.vector_cls):
+ raise TypeError("vectors and matrices cannot be subtracted")
+ # Same comments as in .__sub__() apply
+ # with the roles of self and other swapped.
+ return (-self) + other
+
+ def _matrix_multiply(self, other):
+ """Internal utility method to multiply to Matrix instances."""
+ if self.n_cols != other.n_rows:
+ raise ValueError("matrices must have compatible dimensions")
+ # Matrix-matrix multiplication means that each entry of the resulting
+ # Matrix is the dot product of the respective row of the "left" Matrix
+ # and column of the "right" Matrix. So, the rows/columns are represented
+ # by the Vector instances provided by the .cols() and .rows() methods.
+ return self.__class__((rv * cv for cv in other.cols()) for rv in self.rows())
+
+ def __mul__(self, other):
+ """Handle `self * other` and `other * self`.
+
+ This may be either scalar multiplication, matrix-vector multiplication,
+ vector-matrix multiplication, or matrix-matrix multiplication.
+
+ Example Usage:
+ >>> Matrix([(1, 2), (3, 4)]) * Matrix([(1, 2), (3, 4)])
+ Matrix(((7.0, 10.0,), (15.0, 22.0,)))
+
+ >>> 2 * Matrix([(1, 2), (3, 4)])
+ Matrix(((2.0, 4.0,), (6.0, 8.0,)))
+
+ >>> Matrix([(1, 2), (3, 4)]) * 3
+ Matrix(((3.0, 6.0,), (9.0, 12.0,)))
+
+ Matrix-vector and vector-matrix multiplication are not commutative.
+
+ >>> from sample_package import Vector
+
+ >>> Matrix([(1, 2), (3, 4)]) * Vector([5, 6])
+ Vector((17.0, 39.0))
+
+ >>> Vector([5, 6]) * Matrix([(1, 2), (3, 4)])
+ Vector((23.0, 34.0))
+ """
+ # Scalar multiplication
+ if isinstance(other, numbers.Number):
+ return self.__class__((x * other for x in r) for r in self._entries)
+ # Matrix-vector multiplication: Vector is a column Vector
+ elif isinstance(other, self.vector_cls):
+ # First, cast the other Vector as a Matrix, then do matrix-matrix
+ # multiplication, and lastly return the result as a Vector again.
+ return self._matrix_multiply(other.as_matrix()).as_vector()
+ # Matrix-matrix multiplication
+ elif isinstance(other, self.__class__):
+ return self._matrix_multiply(other)
+ return NotImplemented
+
+ def __rmul__(self, other):
+ """See docstring for .__mul__()."""
+ # As scalar multiplication is commutative, we dispatch to .__mul__().
+ if isinstance(other, numbers.Number):
+ return self * other
+ # Vector-matrix multiplication: Vector is a row Vector
+ elif isinstance(other, self.vector_cls):
+ return other.as_matrix(column=False)._matrix_multiply(self).as_vector()
+ return NotImplemented
+
+ def __truediv__(self, other):
+ """Handle `self / other`.
+
+ Divide a Matrix by a scalar.
+
+ Example Usage:
+ >>> Matrix([(1, 2), (3, 4)]) / 4
+ Matrix(((0.25, 0.5,), (0.75, 1.0,)))
+ """
+ # As scalar division division is the same as multiplication
+ # with the inverse, we dispatch to .__mul__().
+ if isinstance(other, numbers.Number):
+ return self * (1 / other)
+ return NotImplemented
+
+ def __eq__(self, other):
+ """Handle `self == other`.
+
+ Compare two Matrix instances for equality.
+
+ Example Usage:
+ >>> Matrix([(1, 2), (3, 4)]) == Matrix([(1, 2), (3, 4)])
+ True
+
+ >>> Matrix([(1, 2), (3, 4)]) == Matrix([(5, 6), (7, 8)])
+ False
+ """
+ if isinstance(other, self.__class__):
+ if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):
+ raise ValueError("matrices must have the same dimensions")
+ for x, y in zip(self, other):
+ if abs(x - y) > self.zero_threshold:
+ return False # exit early if two corresponding entries differ
+ return True
+ return NotImplemented
+
+ def __pos__(self):
+ """Handle `+self`.
+
+ This is simply an identity operator returning the Matrix itself.
+ """
+ return self
+
+ def __neg__(self):
+ """Handle `-self`.
+
+ Negate all entries of a Matrix.
+ """
+ return self.__class__((-x for x in r) for r in self._entries)
+
+ def __abs__(self):
+ """The Frobenius norm of a Matrix."""
+ return utils.norm(self) # uses the norm() function shared vector.Vector
+
+ def __bool__(self):
+ """A Matrix is truthy if its Frobenius norm is strictly positive."""
+ return bool(abs(self))
+
+ def __float__(self):
+ """Cast a Matrix as a scalar.
+
+ Returns:
+ scalar (float)
+
+ Raises:
+ RuntimeError: if the Matrix has more than one entry
+ """
+ if not (self.n_rows == 1 and self.n_cols == 1):
+ raise RuntimeError("matrix must have exactly one entry to become a scalar")
+ return self[0]
+
+ def as_vector(self):
+ """Get a Vector representation of a Matrix.
+
+ Returns:
+ vector (vector.Vector)
+
+ Raises:
+ RuntimeError: if one of the two dimensions, .n_rows or .n_cols, is not 1
+
+ Example Usage:
+ >>> Matrix([(1, 2, 3)]).as_vector()
+ Vector((1.0, 2.0, 3.0))
+ """
+ if not (self.n_rows == 1 or self.n_cols == 1):
+ raise RuntimeError("one dimension (m or n) must be 1")
+ return self.vector_cls(x for x in self)
+
+ def transpose(self):
+ """Switch the rows and columns of a Matrix.
+
+ Returns:
+ matrix (Matrix)
+
+ Example Usage:
+ >>> m = Matrix([(1, 2), (3, 4)])
+ >>> m
+ Matrix(((1.0, 2.0,), (3.0, 4.0,)))
+ >>> m.transpose()
+ Matrix(((1.0, 3.0,), (2.0, 4.0,)))
+ """
+ return self.__class__(zip(*self._entries))
+
+
+# This import needs to be made here as otherwise an ImportError is raised.
+# That is so as both the matrix and vector modules import a class from each other.
+# We call that a circular import. Whereas Python handles "circular" references
+# (e.g., both the Matrix and Vector classes have methods that reference the
+# respective other class), that is forbidden for imports.
+from sample_package import vector
+
+# This attribute cannot be set in the class definition
+# as the vector module is only imported down here.
+Matrix.vector_cls = vector.Vector
diff --git a/11_classes/sample_package/utils.py b/11_classes/sample_package/utils.py
new file mode 100644
index 0000000..2720372
--- /dev/null
+++ b/11_classes/sample_package/utils.py
@@ -0,0 +1,35 @@
+"""This module provides utilities for the whole package.
+
+The defined constants are used as defaults in the Vector and Matrix classes.
+
+The norm() function is shared by Vector.__abs__() and Matrix.__abs__().
+"""
+
+import math
+
+
+# Define constants (i.e., normal variables that are, by convention, named in UPPERCASE)
+# that are used as the defaults for class attributes within Vector and Matrix.
+DEFAULT_ENTRIES_STORAGE = tuple
+DEFAULT_ENTRY_TYPE = float
+ZERO_THRESHOLD = 1e-12
+
+
+def norm(vec_or_mat):
+ """Calculate the Frobenius or Euclidean norm of a matrix or vector.
+
+ Find more infos here: https://en.wikipedia.org/wiki/Matrix_norm#Frobenius_norm
+
+ Args:
+ vec_or_mat (Vector / Matrix): object whose entries are squared and summed up
+
+ Returns:
+ norm (float)
+
+ Example Usage:
+ As Vector and Matrix objects are by design non-empty sequences,
+ norm() may be called, for example, with `[3, 4]` as the argument:
+ >>> norm([3, 4])
+ 5.0
+ """
+ return math.sqrt(sum(x ** 2 for x in vec_or_mat))
diff --git a/11_classes/sample_package/vector.py b/11_classes/sample_package/vector.py
new file mode 100644
index 0000000..e9315c4
--- /dev/null
+++ b/11_classes/sample_package/vector.py
@@ -0,0 +1,262 @@
+"""This module defines a Vector class."""
+
+# Imports from the standard library go first ...
+import numbers
+
+# ... and are followed by project-internal ones.
+# If third-party libraries are needed, they are
+# put into a group on their own in between.
+# Within a group, imports are sorted lexicographically.
+from sample_package import matrix
+from sample_package import utils
+
+
+class Vector:
+ """A one-dimensional vector from linear algebra.
+
+ All entries are converted to floats, or whatever is set in the typing attribute.
+
+ Attributes:
+ matrix_cls (matrix.Matrix): a reference to the Matrix class to work with
+ storage (callable): data type used to store the entries internally;
+ defaults to tuple
+ typing (callable): type casting applied to all entries upon creation;
+ defaults to float
+ zero_threshold (float): max. tolerance when comparing an entry to zero;
+ defaults to 1e-12
+ """
+
+ matrix_cls = matrix.Matrix
+ storage = utils.DEFAULT_ENTRIES_STORAGE
+ typing = utils.DEFAULT_ENTRY_TYPE
+ zero_threshold = utils.ZERO_THRESHOLD
+
+ def __init__(self, data):
+ """Create a new vector.
+
+ Args:
+ data (sequence): the vector's entries
+
+ Raises:
+ ValueError: if no entries are provided
+
+ Example Usage:
+ >>> Vector([1, 2, 3])
+ Vector((1.0, 2.0, 3.0))
+
+ >>> Vector(range(3))
+ Vector((0.0, 1.0, 2.0))
+ """
+ self._entries = self.storage(self.typing(x) for x in data)
+ if len(self) == 0:
+ raise ValueError("a vector must have at least one entry")
+
+ def __repr__(self):
+ """Text representation of a Vector."""
+ name = self.__class__.__name__
+ args = ", ".join(repr(x) for x in self)
+ return f"{name}(({args}))"
+
+ def __str__(self):
+ """Human-readable text representation of a Vector."""
+ name = self.__class__.__name__
+ first, last, n_entries = self[0], self[-1], len(self)
+ return f"{name}({first!r}, ..., {last!r})[{n_entries:d}]"
+
+ def __len__(self):
+ """Number of entries in a Vector."""
+ return len(self._entries)
+
+ def __getitem__(self, index):
+ """Obtain an individual entry of a Vector."""
+ if not isinstance(index, int):
+ raise TypeError("index must be an integer")
+ return self._entries[index]
+
+ def __iter__(self):
+ """Loop over a Vector's entries."""
+ return iter(self._entries)
+
+ def __reversed__(self):
+ """Loop over a Vector's entries in reverse order."""
+ return reversed(self._entries)
+
+ def __add__(self, other):
+ """Handle `self + other` and `other + self`.
+
+ This may be either vector addition or broadcasting addition.
+
+ Example Usage:
+ >>> Vector([1, 2, 3]) + Vector([2, 3, 4])
+ Vector((3.0, 5.0, 7.0))
+
+ >>> Vector([1, 2, 3]) + 4
+ Vector((5.0, 6.0, 7.0))
+
+ >>> 10 + Vector([1, 2, 3])
+ Vector((11.0, 12.0, 13.0))
+ """
+ # Vector addition
+ if isinstance(other, self.__class__):
+ if len(self) != len(other):
+ raise ValueError("vectors must be of the same length")
+ return self.__class__(x + y for (x, y) in zip(self, other))
+ # Broadcasting addition
+ elif isinstance(other, numbers.Number):
+ return self.__class__(x + other for x in self)
+ return NotImplemented
+
+ def __radd__(self, other):
+ """See docstring for .__add__()."""
+ # As both vector and broadcasting addition are commutative,
+ # we dispatch to .__add__().
+ return self + other
+
+ def __sub__(self, other):
+ """Handle `self - other` and `other - self`.
+
+ This may be either vector subtraction or broadcasting subtraction.
+
+ Example Usage:
+ >>> Vector([7, 8, 9]) - Vector([1, 2, 3])
+ Vector((6.0, 6.0, 6.0))
+
+ >>> Vector([1, 2, 3]) - 1
+ Vector((0.0, 1.0, 2.0))
+
+ >>> 10 - Vector([1, 2, 3])
+ Vector((9.0, 8.0, 7.0))
+ """
+ # As subtraction is the inverse of addition,
+ # we first dispatch to .__neg__() to invert the signs of
+ # all entries in other and then dispatch to .__add__().
+ return self + (-other)
+
+ def __rsub__(self, other):
+ """See docstring for .__sub__()."""
+ # Same comments as in .__sub__() apply
+ # with the roles of self and other swapped.
+ return (-self) + other
+
+ def __mul__(self, other):
+ """Handle `self * other` and `other * self`.
+
+ This may be either the dot product of two vectors or scalar multiplication.
+
+ Example Usage:
+ >>> Vector([1, 2, 3]) * Vector([2, 3, 4])
+ 20.0
+
+ >>> 2 * Vector([1, 2, 3])
+ Vector((2.0, 4.0, 6.0))
+
+ >>> Vector([1, 2, 3]) * 3
+ Vector((3.0, 6.0, 9.0))
+ """
+ # Dot product
+ if isinstance(other, self.__class__):
+ if len(self) != len(other):
+ raise ValueError("vectors must be of the same length")
+ return sum(x * y for (x, y) in zip(self, other))
+ # Scalar multiplication
+ elif isinstance(other, numbers.Number):
+ return self.__class__(x * other for x in self)
+ return NotImplemented
+
+ def __rmul__(self, other):
+ """See docstring for .__mul__()."""
+ # As both dot product and scalar multiplication are commutative,
+ # we dispatch to .__mul__().
+ return self * other
+
+ def __truediv__(self, other):
+ """Handle `self / other`.
+
+ Divide a Vector by a scalar.
+
+ Example Usage:
+ >>> Vector([9, 6, 12]) / 3
+ Vector((3.0, 2.0, 4.0))
+ """
+ # As scalar division division is the same as multiplication
+ # with the inverse, we dispatch to .__mul__().
+ if isinstance(other, numbers.Number):
+ return self * (1 / other)
+ return NotImplemented
+
+ def __eq__(self, other):
+ """Handle `self == other`.
+
+ Compare two Vectors for equality.
+
+ Example Usage:
+ >>> Vector([1, 2, 3]) == Vector([1, 2, 3])
+ True
+
+ >>> Vector([1, 2, 3]) == Vector([4, 5, 6])
+ False
+ """
+ if isinstance(other, self.__class__):
+ if len(self) != len(other):
+ raise ValueError("vectors must be of the same length")
+ for x, y in zip(self, other):
+ if abs(x - y) > self.zero_threshold:
+ return False # exit early if two corresponding entries differ
+ return True
+ return NotImplemented
+
+ def __pos__(self):
+ """Handle `+self`.
+
+ This is simply an identity operator returning the Vector itself.
+ """
+ return self
+
+ def __neg__(self):
+ """Handle `-self`.
+
+ Negate all entries of a Vector.
+ """
+ return self.__class__(-x for x in self)
+
+ def __abs__(self):
+ """The Euclidean norm of a vector."""
+ return utils.norm(self) # uses the norm() function shared matrix.Matrix
+
+ def __bool__(self):
+ """A Vector is truthy if its Euclidean norm is strictly positive."""
+ return bool(abs(self))
+
+ def __float__(self):
+ """Cast a Vector as a scalar.
+
+ Returns:
+ scalar (float)
+
+ Raises:
+ RuntimeError: if the Vector has more than one entry
+ """
+ if len(self) != 1:
+ raise RuntimeError("vector must have exactly one entry to become a scalar")
+ return self[0]
+
+ def as_matrix(self, *, column=True):
+ """Get a Matrix representation of a Vector.
+
+ Args:
+ column (bool): if the vector is interpreted as a
+ column vector or a row vector; defaults to True
+
+ Returns:
+ matrix (matrix.Matrix)
+
+ Example Usage:
+ >>> v = Vector([1, 2, 3])
+ >>> v.as_matrix()
+ Matrix(((1.0,), (2.0,), (3.0,)))
+ >>> v.as_matrix(column=False)
+ Matrix(((1.0, 2.0, 3.0,)))
+ """
+ if column:
+ return self.matrix_cls([x] for x in self)
+ return self.matrix_cls([(x for x in self)])
diff --git a/CONTENTS.md b/CONTENTS.md
index 02fe78a..bdad0d7 100644
--- a/CONTENTS.md
+++ b/CONTENTS.md
@@ -262,3 +262,32 @@ If this is not possible,
- [review questions ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/07_review.ipynb)
- [further resources ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/09_mappings/08_resources.ipynb)
[](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/09_mappings/08_resources.ipynb)
+ - *Chapter 11*: Classes & Instances
+ - [content ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/00_content.ipynb)
+ [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/00_content.ipynb)
+ (`class` Statement;
+ Instantiation;
+ Text Representations;
+ Instance Methods vs. Class Methods;
+ Computed Properties)
+ - [exercises ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/01_exercises.ipynb)
+ [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/01_exercises.ipynb)
+ (A Traveling Salesman Problem)
+ - [content ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/02_content.ipynb)
+ [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/02_content.ipynb)
+ (Sequence Emulation & Iteration;
+ Python's Data Model;
+ (Im)mutable Data Types;
+ Method Chaining;
+ Polymorphism)
+ - [content ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/03_content.ipynb)
+ [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/03_content.ipynb)
+ (Operator Overloading: Arithmetic & Relational Operators;
+ Number Emulation)
+ - [content ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/04_content.ipynb)
+ [](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/04_content.ipynb)
+ (Writing one's own Packages;
+ The final `Vector` & `Matrix` Classes;
+ Comparison with `numpy`)
+ - [summary ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/05_summary.ipynb)
+ - [review questions ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/11_classes/06_review.ipynb)
diff --git a/README.md b/README.md
index dd051e6..49ae3d0 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,7 @@ For a more *detailed version* with **clickable links**
- *Chapter 7*: Sequential Data
- *Chapter 8*: Map, Filter, & Reduce
- *Chapter 9*: Mappings & Sets
+ - *Chapter 11*: Classes & Instances
#### Videos
diff --git a/noxfile.py b/noxfile.py
index e77cc26..869cdab 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -4,6 +4,8 @@ Nox provides the following tasks:
- "init-project": install the pre-commit hooks
+- "doctests": run the xdoctests in the source files
+
- "fix-branch-references": adjusts links with git branch references in
various files (e.g., Mardown or notebooks)
@@ -22,6 +24,11 @@ import nox
REPOSITORY = "webartifex/intro-to-python"
+SRC_LOCATIONS = (
+ "02_functions/sample_module.py",
+ "11_classes/sample_package",
+)
+
# Use a unified .cache/ folder for all develop tools.
nox.options.envdir = ".cache/nox"
@@ -45,6 +52,13 @@ def init_project(session):
)
+@nox.session(venv_backend="none")
+def doctests(session):
+ """Run the xdoctests in the source files."""
+ for location in SRC_LOCATIONS:
+ session.run("poetry", "run", "xdoctest", "--silent", location)
+
+
@nox.session(name="fix-branch-references", venv_backend="none")
def fix_branch_references(_session):
"""Change git branch references.
diff --git a/poetry.lock b/poetry.lock
index 8e454f7..e50d7f0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -267,7 +267,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
parso = ">=0.7.0,<0.8.0"
[package.extras]
-qa = ["flake8 (3.7.9)"]
+qa = ["flake8 (==3.7.9)"]
testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"]
[[package]]
@@ -428,7 +428,7 @@ test = ["jupyter-contrib-core", "nose", "requests", "selenium", "mock"]
[[package]]
name = "jupyterlab"
-version = "2.2.8"
+version = "2.2.9"
description = "The JupyterLab notebook server extension."
category = "main"
optional = false
@@ -547,11 +547,11 @@ testpath = "*"
traitlets = ">=4.2"
[package.extras]
-all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
+all = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)", "tornado (>=4.0)", "sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
docs = ["sphinx (>=1.5.1)", "sphinx-rtd-theme", "nbsphinx (>=0.2.12)", "ipython"]
serve = ["tornado (>=4.0)"]
-test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (0.2.2)"]
-webpdf = ["pyppeteer (0.2.2)"]
+test = ["pytest", "pytest-cov", "pytest-dependency", "ipykernel", "ipywidgets (>=7)", "pyppeteer (==0.2.2)"]
+webpdf = ["pyppeteer (==0.2.2)"]
[[package]]
name = "nbformat"
@@ -573,7 +573,7 @@ test = ["fastjsonschema", "testpath", "pytest", "pytest-cov"]
[[package]]
name = "nest-asyncio"
-version = "1.4.1"
+version = "1.4.2"
description = "Patch asyncio to allow nested event loops"
category = "main"
optional = false
@@ -654,11 +654,11 @@ six = "*"
[[package]]
name = "pandocfilters"
-version = "1.4.2"
+version = "1.4.3"
description = "Utilities for writing pandoc filters in python"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "parso"
@@ -754,7 +754,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pygments"
-version = "2.7.1"
+version = "2.7.2"
description = "Pygments is a syntax highlighting package written in Python."
category = "main"
optional = false
@@ -835,18 +835,18 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
name = "rise"
-version = "5.6.1"
+version = "5.7.0"
description = "Reveal.js - Jupyter/IPython Slideshow Extension"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
[package.dependencies]
-notebook = ">=5.5.0"
+notebook = ">=6.0"
[[package]]
name = "send2trash"
@@ -929,11 +929,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
-version = "20.0.35"
+version = "20.1.0"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
@@ -965,10 +965,28 @@ category = "main"
optional = false
python-versions = "*"
+[[package]]
+name = "xdoctest"
+version = "0.15.0"
+description = "A rewrite of the builtin doctest module"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+six = "*"
+
+[package.extras]
+all = ["six", "pytest", "pytest-cov", "codecov", "scikit-build", "cmake", "ninja", "pybind11", "pygments", "colorama", "nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"]
+colors = ["pygments", "colorama"]
+jupyter = ["nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"]
+optional = ["pygments", "colorama", "nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"]
+tests = ["pytest", "pytest-cov", "codecov", "scikit-build", "cmake", "ninja", "pybind11", "nbformat", "nbconvert", "jupyter-client", "ipython", "ipykernel"]
+
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
-content-hash = "abc30be36880f8b203c72895f70f906ef451fca6b4e5113891c9487bc95e02e5"
+content-hash = "0483228a1bbf92c52f5045db05de80b951dbd7302db756ca6f65cd56b482b814"
[metadata.files]
appdirs = [
@@ -1157,8 +1175,8 @@ jupyter-nbextensions-configurator = [
{file = "jupyter_nbextensions_configurator-0.4.1.tar.gz", hash = "sha256:e5e86b5d9d898e1ffb30ebb08e4ad8696999f798fef3ff3262d7b999076e4e83"},
]
jupyterlab = [
- {file = "jupyterlab-2.2.8-py3-none-any.whl", hash = "sha256:95d0509557881cfa8a5fcdf225f2fca46faf1bc52fc56a28e0b72fcc594c90ab"},
- {file = "jupyterlab-2.2.8.tar.gz", hash = "sha256:c8377bee30504919c1e79949f9fe35443ab7f5c4be622c95307e8108410c8b8c"},
+ {file = "jupyterlab-2.2.9-py3-none-any.whl", hash = "sha256:59af02c26a15ec2d2862a15bc72e41ae304b406a0b0d3f4f705eeb7caf91902b"},
+ {file = "jupyterlab-2.2.9.tar.gz", hash = "sha256:3be8f8edea173753dd838c1b6d3bbcb6f5c801121f824a477025c1b6a1d33dc6"},
]
jupyterlab-pygments = [
{file = "jupyterlab_pygments-0.1.2-py2.py3-none-any.whl", hash = "sha256:abfb880fd1561987efaefcb2d2ac75145d2a5d0139b1876d5be806e32f630008"},
@@ -1258,8 +1276,8 @@ nbformat = [
{file = "nbformat-5.0.8.tar.gz", hash = "sha256:f545b22138865bfbcc6b1ffe89ed5a2b8e2dc5d4fe876f2ca60d8e6f702a30f8"},
]
nest-asyncio = [
- {file = "nest_asyncio-1.4.1-py3-none-any.whl", hash = "sha256:a4487c4f49f2d11a7bb89a512a6886b6a5045f47097f49815b2851aaa8599cf0"},
- {file = "nest_asyncio-1.4.1.tar.gz", hash = "sha256:b86c3193abda5b2eeccf8c79894bc71c680369a178f4b068514ac00720b14e01"},
+ {file = "nest_asyncio-1.4.2-py3-none-any.whl", hash = "sha256:c2d3bdc76ba235a7ad215128afe31d74a320d25790c50cd94685ec5ea221b94d"},
+ {file = "nest_asyncio-1.4.2.tar.gz", hash = "sha256:c614fcfaca72b1f04778bc0e73f49c84500b3d045c49d149fc46f1566643c175"},
]
nodeenv = [
{file = "nodeenv-1.5.0-py2.py3-none-any.whl", hash = "sha256:5304d424c529c997bc888453aeaa6362d242b6b4631e90f3d4bf1b290f1c84a9"},
@@ -1306,7 +1324,7 @@ packaging = [
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
]
pandocfilters = [
- {file = "pandocfilters-1.4.2.tar.gz", hash = "sha256:b3dd70e169bb5449e6bc6ff96aea89c5eea8c5f6ab5e207fc2f521a2cf4a0da9"},
+ {file = "pandocfilters-1.4.3.tar.gz", hash = "sha256:bc63fbb50534b4b1f8ebe1860889289e8af94a23bff7445259592df25a3906eb"},
]
parso = [
{file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"},
@@ -1345,8 +1363,8 @@ pycparser = [
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
pygments = [
- {file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"},
- {file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"},
+ {file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"},
+ {file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
@@ -1433,8 +1451,8 @@ requests = [
{file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"},
]
rise = [
- {file = "rise-5.6.1-py2.py3-none-any.whl", hash = "sha256:e9637ee5499ad7801474da53a2c830350a44b2192c2f113594e4426190e55ad4"},
- {file = "rise-5.6.1.tar.gz", hash = "sha256:1343f068d01adc4dd0226d9b278ce93fc92f365d827431a57e8d5679eb39f4d6"},
+ {file = "rise-5.7.0-py2.py3-none-any.whl", hash = "sha256:b500c7c3f7b09c8194a66feeffd60ead6da0132d9fa18a2bb7352587778ef482"},
+ {file = "rise-5.7.0.tar.gz", hash = "sha256:6c00721189e0b457ca40ab4eb0abef8edbba6c71bc04d7f04ad813a214ddea74"},
]
send2trash = [
{file = "Send2Trash-1.5.0-py3-none-any.whl", hash = "sha256:f1691922577b6fa12821234aeb57599d887c4900b9ca537948d2dac34aea888b"},
@@ -1476,8 +1494,8 @@ urllib3 = [
{file = "urllib3-1.25.11.tar.gz", hash = "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2"},
]
virtualenv = [
- {file = "virtualenv-20.0.35-py2.py3-none-any.whl", hash = "sha256:0ebc633426d7468664067309842c81edab11ae97fcaf27e8ad7f5748c89b431b"},
- {file = "virtualenv-20.0.35.tar.gz", hash = "sha256:2a72c80fa2ad8f4e2985c06e6fc12c3d60d060e410572f553c90619b0f6efaf3"},
+ {file = "virtualenv-20.1.0-py2.py3-none-any.whl", hash = "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2"},
+ {file = "virtualenv-20.1.0.tar.gz", hash = "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380"},
]
wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
@@ -1487,3 +1505,7 @@ webencodings = [
{file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
{file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
]
+xdoctest = [
+ {file = "xdoctest-0.15.0-py2.py3-none-any.whl", hash = "sha256:695ea04303a48cbb319709270d43f7bae7f3de3701aec73f09d90a216499992e"},
+ {file = "xdoctest-0.15.0.tar.gz", hash = "sha256:7f0a184d403b69b166ebec1aadb13c98c96c59101e974ae2e4db4c3a803ec371"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index 588c1b4..aa92bb6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,9 @@ numpy = "^1.19.2"
nox = "^2020.8.22"
pre-commit = "^2.7.1"
+# Testing
+xdoctest = "^0.15.0"
+
# Live coding during presentation mode
jupyter-contrib-nbextensions = "^0.5.1"
rise = "^5.6.1"