From 2b8b0b4b16dcea7356370bc0494ee69bd1142d35 Mon Sep 17 00:00:00 2001 From: Alexander Hess Date: Thu, 16 Apr 2020 05:35:23 +0200 Subject: [PATCH] Add initial version of chapter 10 --- 01_elements_00_content.ipynb | 8 +- 02_functions_00_content.ipynb | 6 +- 04_iteration_00_content.ipynb | 2 +- 05_numbers_00_content.ipynb | 2 +- 06_text_00_content.ipynb | 2 +- 07_sequences_00_content.ipynb | 4 +- 09_mappings_00_content.ipynb | 13 +- 10_classes_00_content.ipynb | 7688 +++++++++++++++++++++++++++++++++ 8 files changed, 7712 insertions(+), 13 deletions(-) create mode 100644 10_classes_00_content.ipynb diff --git a/01_elements_00_content.ipynb b/01_elements_00_content.ipynb index 88d36b1..698947d 100644 --- a/01_elements_00_content.ipynb +++ b/01_elements_00_content.ipynb @@ -1343,7 +1343,7 @@ "source": [ "Different types imply different behaviors for the objects. The `b` object, for example, may be \"asked\" if it is a whole number with the [is_integer() ](https://docs.python.org/3/library/stdtypes.html#float.is_integer) \"functionality\" that comes with *every* `float` object.\n", "\n", - "Formally, we call such type-specific functionalities **methods** (i.e., as opposed to functions) and we look at them in detail in Chapter 10. For now, it suffices to know that we access them with the **dot operator** `.` on the object. Of course, `b` is a whole number, which the boolean object `True` tells us." + "Formally, we call such type-specific functionalities **methods** (i.e., as opposed to functions) and we look at them in detail in [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb). For now, it suffices to know that we access them with the **dot operator** `.` on the object. Of course, `b` is a whole number, which the boolean object `True` tells us." ] }, { @@ -2474,7 +2474,7 @@ } }, "source": [ - "Some variables magically exist when a Python process is started or are added by Jupyter. We may safely ignore the former until Chapter 10 and the latter for good." + "Some variables magically exist when a Python process is started or are added by Jupyter. We may safely ignore the former until [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb) and the latter for good." ] }, { @@ -2898,7 +2898,7 @@ } }, "source": [ - "Variables with leading and trailing double underscores, referred to as **dunder** in Python jargon, are used for built-in functionalities and to implement object-oriented features as we see in Chapter 10. We must *not* use this style for variables!" + "Variables with leading and trailing double underscores, referred to as **dunder** in Python jargon, are used for built-in functionalities and to implement object-oriented features as we see in [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb). We must *not* use this style for variables!" ] }, { @@ -3734,7 +3734,7 @@ " - distinct and well-contained areas/parts of the memory that hold the actual data\n", " - the concept by which Python manages the memory for us\n", " - can be classified into objects of the same **type** (i.e., same abstract \"structure\" but different concrete data)\n", - " - built-in objects (incl. **literals**) vs. user-defined objects (cf., Chapter 10)\n", + " - built-in objects (incl. **literals**) vs. user-defined objects (cf., [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb))\n", " - e.g., `1`, `1.0`, and `\"one\"` are three different objects of distinct types that are also literals (i.e., by the way we type them into the command line Python knows what the value and type are)\n", "\n", "\n", diff --git a/02_functions_00_content.ipynb b/02_functions_00_content.ipynb index e810c48..5552ee6 100644 --- a/02_functions_00_content.ipynb +++ b/02_functions_00_content.ipynb @@ -1012,7 +1012,7 @@ "source": [ "Its value may seem awkward at first: It consists of a location showing where the function is defined (i.e., `__main__` here, which is Python's way of saying \"in this notebook\") and the signature wrapped inside angle brackets `<` and `>`.\n", " \n", - "The angle brackets are a convention to indicate that the value may *not* be used as a *literal* (i.e., typed back into another code cell). Chapter 10 introduces the concept of a **text representation** of an object, which is related to the *semantic* meaning of an object's value as discussed in [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Value-/-\"Meaning\"), and the angle brackets convention is one such way to represent an object as text. When executed, the angle brackets cause a `SyntaxError` because Python expects the `<` operator to come with an operand on both sides (cf., [Chapter 3 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_content.ipynb#Relational-Operators))." + "The angle brackets are a convention to indicate that the value may *not* be used as a *literal* (i.e., typed back into another code cell). [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb) introduces the concept of a **text representation** of an object, which is related to the *semantic* meaning of an object's value as discussed in [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Value-/-\"Meaning\"), and the angle brackets convention is one such way to represent an object as text. When executed, the angle brackets cause a `SyntaxError` because Python expects the `<` operator to come with an operand on both sides (cf., [Chapter 3 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/03_conditionals_00_content.ipynb#Relational-Operators))." ] }, { @@ -3599,7 +3599,7 @@ } }, "source": [ - "Besides the usual dunder-style attributes, the built-in [dir() ](https://docs.python.org/3/library/functions.html#dir) function lists some attributes in an upper case naming convention and many others starting with a *single* underscore `_`. To understand the former, we must wait until Chapter 10, while the latter is explained further below." + "Besides the usual dunder-style attributes, the built-in [dir() ](https://docs.python.org/3/library/functions.html#dir) function lists some attributes in an upper case naming convention and many others starting with a *single* underscore `_`. To understand the former, we must wait until [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb), while the latter is explained further below." ] }, { @@ -4519,7 +4519,7 @@ } }, "source": [ - "Packages are a generalization of modules, and we look at one in detail in Chapter 10. You may, however, already look at a [sample package ](https://github.com/webartifex/intro-to-python/tree/master/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 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb). You may, however, already look at a [sample package ](https://github.com/webartifex/intro-to-python/tree/master/sample_package) in the repository, which is nothing but a folder with *.py* files in it.\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/04_iteration_00_content.ipynb b/04_iteration_00_content.ipynb index 3976ad7..aec2180 100644 --- a/04_iteration_00_content.ipynb +++ b/04_iteration_00_content.ipynb @@ -5562,7 +5562,7 @@ "\n", "On the contrary, the abstract concept of **iterables** is all about looping: Any object that we can loop over is, by definition, an iterable. So, `range` objects, for example, are iterables, even though they hold no references to other objects. Moreover, looping does *not* have to occur in a *predictable* order, although this is the case for both `list` and `range` objects.\n", "\n", - "Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. In [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Collections-vs.-Sequences), we formalize these two concepts and introduce many more. Finally, Chapter 10 gives an explanation how abstract concepts are implemented and play together.\n", + "Typically, containers are iterables, and iterables are containers. Yet, only because these two concepts coincide often, we must not think of them as the same. In [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb#Collections-vs.-Sequences), we formalize these two concepts and introduce many more. Finally, [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb) gives an explanation how abstract concepts are implemented and play together.\n", "\n", "Let's continue with `first_names` below as an example an illustrate what iterable containers are." ] diff --git a/05_numbers_00_content.ipynb b/05_numbers_00_content.ipynb index c2d6d1d..4046aa4 100644 --- a/05_numbers_00_content.ipynb +++ b/05_numbers_00_content.ipynb @@ -34,7 +34,7 @@ "\n", "We start with the \"simple\" ones: Numeric types in this chapter and textual data in [Chapter 6 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/06_text_00_content.ipynb). An important fact that holds for all objects of these types is that they are **immutable**. To reuse the bag analogy from [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/01_elements_00_content.ipynb#Objects-vs.-Types-vs.-Values), this means that the $0$s and $1$s making up an object's *value* cannot be changed once the bag is created in memory, implying that any operation with or method on the object creates a *new* object in a *different* memory location.\n", "\n", - "[Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb), [Chapter 8 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mfr_00_content.ipynb), and [Chapter 9 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/09_mappings_00_content.ipynb) then cover the more \"complex\" data types, including, for example, the `list` type. Finally, Chapter 10 completes the picture by introducing language constructs to create custom types.\n", + "[Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.ipynb), [Chapter 8 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/08_mfr_00_content.ipynb), and [Chapter 9 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/09_mappings_00_content.ipynb) then cover the more \"complex\" data types, including, for example, the `list` type. Finally, [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb) completes the picture by introducing language constructs to create custom types.\n", "\n", "We have already seen many hints indicating that numbers are not as trivial to work with as it seems at first sight:\n", "\n", diff --git a/06_text_00_content.ipynb b/06_text_00_content.ipynb index 36a991a..cc68262 100644 --- a/06_text_00_content.ipynb +++ b/06_text_00_content.ipynb @@ -290,7 +290,7 @@ } }, "source": [ - "As an alternative to the literal notation, we may use the built-in [str() ](https://docs.python.org/3/library/stdtypes.html#str) constructor to cast non-`str` objects as `str` ones. As Chapter 10 reveals, basically any object in Python has a **text representation**. Because of that we may also pass `list` objects, the boolean `True` and `False`, or `None` to [str() ](https://docs.python.org/3/library/stdtypes.html#str)." + "As an alternative to the literal notation, we may use the built-in [str() ](https://docs.python.org/3/library/stdtypes.html#str) constructor to cast non-`str` objects as `str` ones. As [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb) reveals, basically any object in Python has a **text representation**. Because of that we may also pass `list` objects, the boolean `True` and `False`, or `None` to [str() ](https://docs.python.org/3/library/stdtypes.html#str)." ] }, { diff --git a/07_sequences_00_content.ipynb b/07_sequences_00_content.ipynb index ece7bd2..b68ac5e 100644 --- a/07_sequences_00_content.ipynb +++ b/07_sequences_00_content.ipynb @@ -4002,7 +4002,7 @@ "\n", "The revised `add_xyz()` function below is more natural to reason about as it does *not* modify the passed in `arg` internally. [PythonTutor ](http://pythontutor.com/visualize.html#code=letters%20%3D%20%5B%22a%22,%20%22b%22,%20%22c%22%5D%0A%0Adef%20add_xyz%28arg%29%3A%0A%20%20%20%20new_arg%20%3D%20arg%5B%3A%5D%0A%20%20%20%20new_arg.extend%28%5B%22x%22,%20%22y%22,%20%22z%22%5D%29%0A%20%20%20%20return%20new_arg%0A%0Aletters_with_xyz%20%3D%20add_xyz%28letters%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false) shows that as well. This approach is following the **[functional programming ](https://en.wikipedia.org/wiki/Functional_programming)** paradigm that is going through a \"renaissance\" currently. Two essential characteristics of functional programming are that a function *never* changes its inputs and *always* returns the same output given the same inputs.\n", "\n", - "For a beginner, it is probably better to stick to this idea and not change any arguments as the original `add_xyz()` above. However, functions that modify and return the argument passed in are an important aspect of object-oriented programming, as explained in Chapter 10." + "For a beginner, it is probably better to stick to this idea and not change any arguments as the original `add_xyz()` above. However, functions that modify and return the argument passed in are an important aspect of object-oriented programming, as explained in [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb)." ] }, { @@ -6317,7 +6317,7 @@ "source": [ "We implicitly assume that the first element represents the $x$ and the second the $y$ coordinate. While that follows intuitively from convention in math, we should at least add comments somewhere in the code to document this assumption.\n", "\n", - "A better way is to create a *custom* data type. While that is covered in depth in Chapter 10, the [collections ](https://docs.python.org/3/library/collections.html) module in the [standard library ](https://docs.python.org/3/library/index.html) provides a [namedtuple() ](https://docs.python.org/3/library/collections.html#collections.namedtuple) **factory function** that creates \"simple\" custom data types on top of the standard `tuple` type." + "A better way is to create a *custom* data type. While that is covered in depth in [Chapter 10 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/10_classes_00_content.ipynb), the [collections ](https://docs.python.org/3/library/collections.html) module in the [standard library ](https://docs.python.org/3/library/index.html) provides a [namedtuple() ](https://docs.python.org/3/library/collections.html#collections.namedtuple) **factory function** that creates \"simple\" custom data types on top of the standard `tuple` type." ] }, { diff --git a/09_mappings_00_content.ipynb b/09_mappings_00_content.ipynb index f08479b..b66e0e8 100644 --- a/09_mappings_00_content.ipynb +++ b/09_mappings_00_content.ipynb @@ -1,5 +1,16 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" *before* reading this chapter in [JupyterLab ](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder ](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/09_mappings_00_content.ipynb))" + ] + }, { "cell_type": "markdown", "metadata": { @@ -8,7 +19,7 @@ } }, "source": [ - "# Chapter 8: Mappings & Sets" + "# Chapter 9: Mappings & Sets" ] }, { diff --git a/10_classes_00_content.ipynb b/10_classes_00_content.ipynb new file mode 100644 index 0000000..5af284a --- /dev/null +++ b/10_classes_00_content.ipynb @@ -0,0 +1,7688 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "**Important**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" *before* reading this chapter in [JupyterLab ](https://jupyterlab.readthedocs.io/en/stable/) (e.g., in the cloud on [MyBinder ](https://mybinder.org/v2/gh/webartifex/intro-to-python/master?urlpath=lab/tree/10_classes_00_content.ipynb))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Chapter 10: 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\"* (e.g., the vectors and matrices below).\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", + "**Side Note**: Often times, we see the terminology \"classes & objects\" used instead of \"classes & instances\" used on forums like [Stack Overflow ](https://stackoverflow.com). In this book, we are more precise as both classes and instances are objects on their own in Python." + ] + }, + { + "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 the popular third-party library [numpy](http://www.numpy.org/) is the de-facto standard for that and is recommended to be used for 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.\n", + "\n", + "Without classes, we could model a vector with a `tuple` or a `list` depending on if we want the vector to be mutable or not.\n", + "\n", + "Let's take the following vector $\\vec{y}$ as an example and model it as a `tuple`:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "$\\vec{y} = \\begin{pmatrix} 1 \\\\ 2 \\\\ 3 \\end{pmatrix}$" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "y = (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": [ + "y" + ] + }, + { + "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 nested objects represent rows or columns. A common convention is to go with the former.\n", + "\n", + "For example, let's model the matrix $\\bf{X}$ below as a `list` of row `list`s:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "$\\bf{X} = \\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": [ + "X = [[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": [ + "X" + ] + }, + { + "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 as Python does not know about the **semantics** (i.e., \"rules\") inherent to vectors and matrices modeled as `tuple`s and `list`s of `list`s.\n", + "\n", + "In the example, we should be able to multiply a matrix like $\\bf{X}$ with a vector like $\\vec{y}$ 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{X} * \\vec{y} = \\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[0mX\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0my\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": [ + "X * y" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "With Python's object-oriented features, we can \"teach\" it the rules of [linear algebra ](https://en.wikipedia.org/wiki/Linear_algebra). By doing so, we create a **[domain-specific language ](https://en.wikipedia.org/wiki/Domain-specific_language)** (DSL) that simplifies working with vectors and matrices." + ] + }, + { + "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., the `dummy_method()`) and variable assignments (i.e., the `dummy_variable`). Any code put here is executed just as if it were outside the `class` statement. 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), 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 standard 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 a full-fledged 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": [ + "94199515148736" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(Vector)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Its type `type` indicates that it represents a user-defined data type and it evaluates to its fully qualified name.\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/master/02_functions_00_content.ipynb#Constructors) and also in the \"*The `namedtuple` Type*\" section in [Chapter 7 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/07_sequences_00_content.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 for custom data types that can be derived out of a plain `tuple`." + ] + }, + { + "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 standard 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 standard 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 the *explicitly* defined attributes, [dir() ](https://docs.python.org/3/library/functions.html#dir) also shows *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 standard 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 implement a `__init__()` method that contains all the validation logic that we require a `Vector` instance to adhere to.\n", + "\n", + "`__init__()` is an example of a so-called **special method** that we may use to make new data types work with Python's language features seamlessly. 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 is created from some finite and iterable object (e.g., a `tuple`) 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 all the state encapsulated within an instance.\n", + "\n", + "A best practice is to *separate* the usage (i.e., \"behavior\") of a new data type from its implementation. 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 standard one-dimensional vector from linear algebra.\n", + "\n", + " All entries are converted to floats.\n", + " \"\"\"\n", + "\n", + " def __init__(self, data):\n", + " \"\"\"Initiate a new vector.\n", + "\n", + " Args:\n", + " data (iterable): the vector's entries;\n", + " must be finite and have at least one element\n", + "\n", + " Raises:\n", + " ValueError: if the provided data do not have enough entries\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 work with Python's language features seamlessly\" 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 a full-fledged object as well." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140613942015760" + ] + }, + "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`." + ] + }, + { + "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": [ + "Every instance comes with a special `__class__` attribute that also references the corresponding class object." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Vector" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v.__class__" + ] + }, + { + "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": 22, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "<__main__.Vector at 0x7fe33c107710>" + ] + }, + "execution_count": 22, + "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 a generic `TypeError` ..." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "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": 24, + "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 iterable with no elements raises a custom `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "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 17\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 18\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---> 19\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, we are not supposed to do that because of the underscore `_` convention. In other words, only an expression like `self._entries` within a method is allowed." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1.0, 2.0, 3.0]" + ] + }, + "execution_count": 26, + "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 `)`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "y = 1, 2, 3" + ] + }, + { + "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": [ + "y" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 2, 3)" + ] + }, + "execution_count": 29, + "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": 30, + "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(f\"{x:.3f}\" 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:.1f}, ..., {last:.1f})[{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 used as a \"literal\" notation.\n", + "\n", + "According to the specification, `__repr__()` should return a `str` object that, when entered in a code cell, 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": 31, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 32, + "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, we create a *new* `Vector` instance with the *same* state as `v`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "To be more explicit, we could alternatively call the built-in [repr()]( https://docs.python.org/3/library/functions.html#repr) function with `v` as the argument." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Vector((1.000, 2.000, 3.000))'" + ] + }, + "execution_count": 34, + "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. We can 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 number of entries in brackets. So, even for a `Vector` containing millions of entries, we could easily make sense of the representation. In other words, a human understands the semantic meaning of the $0$s and $1$s in memory." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'Vector(1.0, ..., 3.0)[3]'" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "str(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The [print() ](https://docs.python.org/3/library/functions.html#print) function does not show the enclosing quotes." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "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": [ + "Below is a first implementation of the `Matrix` class that stores the entries internally as a list of lists." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "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(f\"{c:.3f}\" 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:.1f}, ...), ..., (..., {last:.1f}))[{m:d}x{n:d}]\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`Matrix` is of course a full-fledged object as well." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "94199515162880" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(Matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "type" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(Matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Matrix" + ] + }, + "execution_count": 40, + "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": 41, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "140613942007376" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "id(m)" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "__main__.Matrix" + ] + }, + "execution_count": 43, + "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": 44, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 44, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "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": 46, + "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": 47, + "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": [ + "## 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 and can *not* be set to some arbitrary value. 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 design our `Matrix` class such that instances have two attributes, `n_rows` and `n_cols`, that are automatically set to the correct `int` objects. To do that, we use the [property() ](https://docs.python.org/3/library/functions.html#property) built-in with a special `@` syntax and implement **derived attributes** that are computed from an object's current state. 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": 48, + "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(f\"{c:.3f}\" 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": 49, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6)])" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(2, 3)" + ] + }, + "execution_count": 50, + "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": 51, + "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" + ] + }, + { + "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 works with attributes on the `self` argument. If it does *not* need access to `self` to do its job, it is conceptually *not* an instance method and we should probably convert it into some other kind of method.\n", + "\n", + "An example of an instance method from linear algebra is the `transpose()` method below that switches the rows and columns of a `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. Without a concrete `Matrix`'s rows and columns, `transpose()` could not work, conceptually speaking." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "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(f\"{c:.3f}\" 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": 53, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 54, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 4.000, 7.000,), (2.000, 5.000, 8.000,), (3.000, 6.000, 9.000,)))" + ] + }, + "execution_count": 55, + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "n = m.transpose().transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 58, + "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 below." + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 59, + "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 `@` syntax. Then, Python adapts the binding process described above such that it implicitly inserts a reference to the class object itself when the method is invoked. By convention, we name this parameter `cls`. Class methods are commonly used to provide an alternative way to create instances, usually from a different kind of arguments.\n", + "\n", + "As an example, `from_columns()` expects an iterable of columns instead of rows as its `data` argument. It forwards the invocation to the `__init__()` method (i.e., what `cls(data)` does behind the scenes; `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": 60, + "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(f\"{c:.3f}\" 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": 61, + "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": 62, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 62, + "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": [ + "## 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/master/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. We implicitly invoke the `__len__()` method inside the `__init__()` method already with `len(self)`. This not only reuses code but also ensures that we calculate the number of entries only in one way within the entire class." + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "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(f\"{x:.3f}\" 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": 64, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 66, + "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 `list` type." + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3.0" + ] + }, + "execution_count": 67, + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "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": [ + "Behind the scenes, Python simply loops over the indexes implied by `len(v)` with the help of the [range() ](https://docs.python.org/3/library/functions.html#func-range) built-in and obtains each `entry` one by one. With sequence emulation, we should never have to do that \"manually.\"" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.0 2.0 3.0 " + ] + } + ], + "source": [ + "for index in range(len(v)):\n", + " entry = v[index]\n", + " print(entry, end=\" \")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "`v` may also be iterated over in reverse order with the [reversed() ](https://docs.python.org/3/library/functions.html#reversed) built-in." + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "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": [ + "Because of the iteration, 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": 71, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 71, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 in v" + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 72, + "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": 73, + "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* the approach where we 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\n", + "- `__init__()` and `__getitem__()` invoke `__len__()` via the `len(self)` expressions" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "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": 75, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 76, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(m)" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "9.0" + ] + }, + "execution_count": 78, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m[-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "... but also index in the two dimensions separately." + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3.0" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m[0, 2]" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "9.0" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m[-1, -1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "As before, Python figures out the iteration on its own ..." + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "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": 82, + "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": 83, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "1 in m" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 84, + "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/master/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 sub-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": [ + "### Alternative 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 iterate 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`s 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 *iterators* 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 the popular idea in linear algebra to view matrices 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__()`." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "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(f\"{c:.3f}\" 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 iterators 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": 86, + "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(f\"{x:.3f}\" 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": 87, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 88, + "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": 89, + "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": 90, + "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": 91, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vector((1.000, 2.000, 3.000)) Vector((4.000, 5.000, 6.000)) Vector((7.000, 8.000, 9.000)) " + ] + } + ], + "source": [ + "for row_vector in m.rows():\n", + " print(row_vector, end=\" \")" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vector((1.000, 4.000, 7.000)) Vector((2.000, 5.000, 8.000)) Vector((3.000, 6.000, 9.000)) " + ] + } + ], + "source": [ + "for col_vector in m.cols():\n", + " print(col_vector, end=\" \")" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "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": 94, + "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` instances 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/master/07_sequences_00_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": 95, + "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(f\"{c:.3f}\" 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": 96, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 97, + "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": 98, + "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": 99, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 4.000, 7.000,), (2.000, 5.000, 8.000,), (3.000, 6.000, 9.000,)))" + ] + }, + "execution_count": 99, + "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": 100, + "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": 101, + "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(f\"{c:.3f}\" 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": 102, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 103, + "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": 104, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "n = m.transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 4.000, 7.000,), (2.000, 5.000, 8.000,), (3.000, 6.000, 9.000,)))" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 4.000, 7.000,), (2.000, 5.000, 8.000,), (3.000, 6.000, 9.000,)))" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m is n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "### Customizing 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": 108, + "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(f\"{x:.3f}\" 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": 109, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 110, + "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": 111, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "del v[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 112, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((nan, 2.000, 3.000))" + ] + }, + "execution_count": 112, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 113, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "v[0] = 99" + ] + }, + { + "cell_type": "code", + "execution_count": 114, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((99.000, 2.000, 3.000))" + ] + }, + "execution_count": 114, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "After this discourse on mutable `Vector` and `Matrix` classes, we continue with immutable classes in the rest of this chapter." + ] + }, + { + "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) mentioned in previous chapters.\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": 115, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 115, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum((1, 2, 3, 4))" + ] + }, + { + "cell_type": "code", + "execution_count": 116, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 116, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum([1, 2, 3, 4])" + ] + }, + { + "cell_type": "code", + "execution_count": 117, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 117, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum({1, 2, 3, 4})" + ] + }, + { + "cell_type": "code", + "execution_count": 118, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10" + ] + }, + "execution_count": 118, + "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": 119, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10.0" + ] + }, + "execution_count": 119, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sum(Vector([1, 2, 3, 4]))" + ] + }, + { + "cell_type": "code", + "execution_count": 120, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "10.0" + ] + }, + "execution_count": 120, + "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\" in a similar way. In particular they they both can provide all their entries as a flat sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 121, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "import math" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def norm(vector_or_matrix):\n", + " \"\"\"Calculate the Frobenius or Euclidean norm of a matrix or vector.\n", + "\n", + " Args:\n", + " vector_or_matrix (Vector/Matrix): the entries whose squares\n", + " are summed up\n", + "\n", + " Returns:\n", + " norm (float)\n", + " \"\"\"\n", + " return math.sqrt(sum(x ** 2 for x in vector_or_matrix))" + ] + }, + { + "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": 123, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5.477225575051661" + ] + }, + "execution_count": 123, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "norm(Vector([1, 2, 3, 4]))" + ] + }, + { + "cell_type": "code", + "execution_count": 124, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5.477225575051661" + ] + }, + "execution_count": 124, + "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": 125, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5.477225575051661" + ] + }, + "execution_count": 125, + "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 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." + ] + }, + { + "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 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` default argument)." + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "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(f\"{x:.3f}\" 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": 127, + "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(f\"{c:.3f}\" 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": 128, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 129, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 129, + "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 of dimension $3 \\times 1$." + ] + }, + { + "cell_type": "code", + "execution_count": 130, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = v.as_matrix()" + ] + }, + { + "cell_type": "code", + "execution_count": 131, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000,), (2.000,), (3.000,)))" + ] + }, + "execution_count": 131, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 132, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 1)" + ] + }, + "execution_count": 132, + "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": 133, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 133, + "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": 134, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = v.as_matrix(column=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 135, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,)))" + ] + }, + "execution_count": 135, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 136, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(1, 3)" + ] + }, + "execution_count": 136, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m.n_rows, m.n_cols" + ] + }, + { + "cell_type": "code", + "execution_count": 137, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 137, + "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 a `RuntimeError` as raised in `as_vector()` above." + ] + }, + { + "cell_type": "code", + "execution_count": 138, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])" + ] + }, + { + "cell_type": "code", + "execution_count": 139, + "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": [ + "Using special methods such as `__add__()`, `__sub__()`, `__mul__()`, and some others, user-defined data types can emulate numeric types (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 string concatenation in [Chapter 1 ](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/master/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 an implicit 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": "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.\n", + "\n", + "To check for the latter, 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/master/05_numbers_00_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": 140, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "import numbers" + ] + }, + { + "cell_type": "code", + "execution_count": 141, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 141, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "isinstance(1, int)" + ] + }, + { + "cell_type": "code", + "execution_count": 142, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 142, + "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": 143, + "metadata": { + "code_folding": [], + "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(f\"{x:.3f}\" 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, self.__class__): # vector addition\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to 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, self.__class__): # vector subtraction\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to 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, self.__class__): # dot product\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to 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": 144, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 145, + "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__()`." + ] + }, + { + "cell_type": "code", + "execution_count": 146, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((2.000, 4.000, 6.000))" + ] + }, + "execution_count": 146, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "2 * v" + ] + }, + { + "cell_type": "code", + "execution_count": 147, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((3.000, 6.000, 9.000))" + ] + }, + "execution_count": 147, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v * 3" + ] + }, + { + "cell_type": "code", + "execution_count": 148, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "14.0" + ] + }, + "execution_count": 148, + "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": 149, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "vectors need to 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 need to 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 need to 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." + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((0.333, 0.667, 1.000))" + ] + }, + "execution_count": 150, + "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": 151, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((2.000, 4.000, 6.000))" + ] + }, + "execution_count": 151, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v + v" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((0.000, 0.000, 0.000))" + ] + }, + "execution_count": 152, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v - v" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "vectors need to 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 need to 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 need to be of the same length" + ] + } + ], + "source": [ + "v + w" + ] + }, + { + "cell_type": "code", + "execution_count": 154, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((101.000, 102.000, 103.000))" + ] + }, + "execution_count": 154, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v + 100" + ] + }, + { + "cell_type": "code", + "execution_count": 155, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((99.000, 98.000, 97.000))" + ] + }, + "execution_count": 155, + "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": 156, + "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": 156, + "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 updated `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": 157, + "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(f\"{c:.3f}\" 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, self.__class__): # matrix addition\n", + " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n", + " raise ValueError(\"matrices need to be of 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, self.__class__): # matrix subtraction\n", + " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n", + " raise ValueError(\"matrices need to be of 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 need to 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, self.__class__):\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": 158, + "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": 159, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((10.000, 20.000, 30.000,), (40.000, 50.000, 60.000,), (70.000, 80.000, 90.000,)))" + ] + }, + "execution_count": 159, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 * m" + ] + }, + { + "cell_type": "code", + "execution_count": 160, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 160, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(2 * m + m * 3) / 5" + ] + }, + { + "cell_type": "code", + "execution_count": 161, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((0.000, 0.000, 0.000,), (0.000, 0.000, 0.000,), (0.000, 0.000, 0.000,)))" + ] + }, + "execution_count": 161, + "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": 162, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((138.000, 171.000, 204.000,), (174.000, 216.000, 258.000,)))" + ] + }, + "execution_count": 162, + "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": 163, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "matrices need to 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 need to 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 need to 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": 164, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((14.000, 32.000, 50.000))" + ] + }, + "execution_count": 164, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m * v" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((30.000, 36.000, 42.000))" + ] + }, + "execution_count": 165, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v * m" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((68.000, 86.000))" + ] + }, + "execution_count": 166, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "n * v" + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "ename": "ValueError", + "evalue": "matrices need to 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 need to 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 need to 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": 168, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((101.000, 102.000, 103.000,), (104.000, 105.000, 106.000,), (107.000, 108.000, 109.000,)))" + ] + }, + "execution_count": 168, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m + 100" + ] + }, + { + "cell_type": "code", + "execution_count": 169, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((99.000, 98.000, 97.000,), (96.000, 95.000, 94.000,), (93.000, 92.000, 91.000,)))" + ] + }, + "execution_count": 169, + "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": 170, + "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 above, 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": 171, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])\n", + "w = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 172, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 172, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v == w" + ] + }, + { + "cell_type": "code", + "execution_count": 173, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 173, + "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 binary 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/master/05_numbers_00_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. If the `Vector`s differ in their numbers of entries, we fail loudly and raise a `ValueError`. Alternatively, we could fail silently and return `False` instead." + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "class Vector:\n", + "\n", + " zero_threshold = 1e-12\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(f\"{x:.3f}\" 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, self.__class__):\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to be of the same length\")\n", + " for x, y in zip(self, other):\n", + " if abs(x - y) > self.zero_threshold:\n", + " return False\n", + " return True\n", + " return NotImplemented" + ] + }, + { + "cell_type": "code", + "execution_count": 175, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])\n", + "w = Vector([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": 176, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 176, + "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. However, Python implicitly creates the `!=` for us in that it just inverts the result of `__eq__()`. The remaining four operators are not semantically meaningful in the context of linear algebra." + ] + }, + { + "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": 177, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 177, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 178, + "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": [ + "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": 179, + "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(f\"{x:.3f}\" 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 one entry to become a scalar\")\n", + " return self._entries[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 180, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector([1, 2, 3])\n", + "w = Vector([3, 4])\n", + "\n", + "z = Vector([0, 0])\n", + "s = Vector([42])" + ] + }, + { + "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": 181, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 181, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "+v" + ] + }, + { + "cell_type": "code", + "execution_count": 182, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 182, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "+v is v" + ] + }, + { + "cell_type": "code", + "execution_count": 183, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((-3.000, -4.000))" + ] + }, + "execution_count": 183, + "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": 184, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "5.0" + ] + }, + "execution_count": 184, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "abs(w)" + ] + }, + { + "cell_type": "code", + "execution_count": 185, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0" + ] + }, + "execution_count": 185, + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 186, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 186, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bool(v)" + ] + }, + { + "cell_type": "code", + "execution_count": 187, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 187, + "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": 188, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "42.0" + ] + }, + "execution_count": 188, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "float(s)" + ] + }, + { + "cell_type": "code", + "execution_count": 189, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "vector must have 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 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 one entry to become a scalar" + ] + } + ], + "source": [ + "float(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## The whole Picture" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Below are the two final implementations of the `Vector` and `Matrix` classes that integrate all of the functionalities introduced in this chapter. The code is cleaned up and fully documented.\n", + "\n", + "The `storage` and `typing` **class attributes** are added referencing the [list() ](https://docs.python.org/3/library/functions.html#func-list) and [float() ](https://docs.python.org/3/library/functions.html#float) built-ins. These class attributes are then used in the `__init__()` methods as defaults. They can be used just like instance attributes on the `self` object directly. If an attribute is not defined on an instance, Python first checks if it can fall back to the same name on the corresponding class object before it raises an `AttributeError`. If we want to refer to a class attribute right away, we could replace the `self.storage` and `self.typing` with `self.__class__.storage` and `self.__class__.typing` respectively.\n", + "\n", + "Also, we replace the text strings `\"Vector\"` and `\"Matrix\"` in the `__repr__()` and `__str__()` methods with `self.__class__.__name__`. This way, we only use the names of the classes in the `class` statements' header lines. Similarly, we replace all class instantiations of the form `Vector()` and `Matrix()` with `self.__class__()` to avoid using the classes' names again.\n", + "\n", + "Lastly, we adapt the `__sub__()` and `__rsub__()` methods to use the negation provided by `__neg__()` and then dispatch to `__add__()` for code reuse." + ] + }, + { + "cell_type": "code", + "execution_count": 190, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "class Vector:\n", + " \"\"\"A standard one-dimensional vector from linear algebra.\n", + "\n", + " Attributes:\n", + " storage (callable): must return an iterable that is used\n", + " to store the entries of the vector; defaults to list\n", + " typing (callable): type casting applied to all vector\n", + " entries upon creation; defaults to float\n", + " zero_threshold (float): maximum difference allowed when\n", + " comparing an entry to zero; defaults to 1e-12\n", + " \"\"\"\n", + "\n", + " storage = list\n", + " typing = float\n", + " zero_threshold = 1e-12\n", + "\n", + " def __init__(self, data):\n", + " \"\"\"Initiate a new vector.\n", + "\n", + " Args:\n", + " data (iterable): the vector's entries;\n", + " must be finite and have at least one element\n", + "\n", + " Raises:\n", + " ValueError: if the provided data do not have enough entries\n", + " \"\"\"\n", + " self._entries = self.storage(self.typing(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", + " name, args = self.__class__.__name__, \", \".join(f\"{x:.3f}\" for x in self)\n", + " return f\"{name}(({args}))\"\n", + "\n", + " def __str__(self):\n", + " name = self.__class__.__name__\n", + " first, last, entries = self[0], self[-1], len(self)\n", + " return f\"{name}({first:.1f}, ..., {last:.1f})[{entries:d}]\"\n", + "\n", + " def __len__(self):\n", + " return len(self._entries)\n", + "\n", + " def __getitem__(self, index):\n", + " return self._entries[index]\n", + "\n", + " def __iter__(self):\n", + " return iter(self._entries)\n", + "\n", + " def __reversed__(self):\n", + " return reversed(self._entries)\n", + "\n", + " def __add__(self, other):\n", + " if isinstance(other, self.__class__):\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to be of the same length\")\n", + " return self.__class__(x + y for (x, y) in zip(self, other))\n", + " elif isinstance(other, numbers.Number):\n", + " return self.__class__(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", + " return self + (- other)\n", + "\n", + " def __rsub__(self, other):\n", + " return (- self) + other\n", + "\n", + " def __mul__(self, other):\n", + " if isinstance(other, self.__class__):\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to be of the same length\")\n", + " return sum(x * y for (x, y) in zip(self, other))\n", + " elif isinstance(other, numbers.Number):\n", + " return self.__class__(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 __eq__(self, other):\n", + " if isinstance(other, self.__class__):\n", + " if len(self) != len(other):\n", + " raise ValueError(\"vectors need to be of the same length\")\n", + " for x, y in zip(self, other):\n", + " if abs(x - y) > self.zero_threshold:\n", + " return False\n", + " return True\n", + " return NotImplemented\n", + "\n", + " def __pos__(self):\n", + " return self\n", + "\n", + " def __neg__(self):\n", + " return self.__class__(-x for x in self)\n", + "\n", + " def __abs__(self):\n", + " \"\"\"The Euclidean norm of the vector.\"\"\"\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 one entry to become a scalar\")\n", + " return self[0]\n", + "\n", + " def as_matrix(self, *, column=True):\n", + " \"\"\"Convert the vector into a matrix.\n", + "\n", + " Args:\n", + " column (bool): if the vector should be interpreted as\n", + " as a column vector or not; defaults to True\n", + "\n", + " Returns:\n", + " matrix (Matrix)\n", + " \"\"\"\n", + " if column:\n", + " return Matrix([x] for x in self)\n", + " return Matrix([(x for x in self)])" + ] + }, + { + "cell_type": "code", + "execution_count": 191, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "class Matrix:\n", + " \"\"\"A standard m-by-n-dimensional matrix from linear algebra.\n", + "\n", + " Attributes:\n", + " storage (callable): must return an iterable that is used\n", + " to store the entries of the matrix; defaults to list\n", + " typing (callable): type casting applied to all vector\n", + " entries upon creation; defaults to float\n", + " zero_threshold (float): maximum difference allowed when\n", + " comparing an entry to zero; defaults to 1e-12\n", + " \"\"\"\n", + "\n", + " storage = list\n", + " typing = float\n", + " zero_threshold = 1e-12\n", + "\n", + " def __init__(self, data):\n", + " \"\"\"Initiate a new matrix.\n", + "\n", + " Args:\n", + " data (iterable of iterables): the matrix's entries;\n", + " the inner iterables represent the rows;\n", + " the number of column entries must be consistent across rows\n", + " where the first row sets the standard;\n", + " must be finite and have at least one element\n", + "\n", + " Raises:\n", + " ValueError:\n", + " - if the number of columns is inconsistent across rows\n", + " - if the provided data do not have enough entries\n", + " \"\"\"\n", + " self._entries = self.storage(\n", + " self.storage(self.typing(x) for x in r) for r in data\n", + " )\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", + " @classmethod\n", + " def from_columns(cls, data):\n", + " \"\"\"Initiate a new matrix.\n", + "\n", + " An alternative constructor for data provided in column-major order.\n", + "\n", + " Args:\n", + " data (iterable of iterables): the matrix's entries;\n", + " the inner iterables represent the columns;\n", + " the number of row entries must be consistent per column\n", + " where the first column sets the standard;\n", + " must be finite and have at least one element\n", + "\n", + " Raises:\n", + " ValueError:\n", + " - if the number of rows is inconsistent across columns\n", + " - if the provided data do not have enough entries\n", + " \"\"\"\n", + " return cls(data).transpose()\n", + "\n", + " def __repr__(self):\n", + " name = self.__class__.__name__\n", + " args = \", \".join(\n", + " \"(\" + \", \".join(f\"{c:.3f}\" for c in r) + \",)\" for r in self._entries\n", + " )\n", + " return f\"{name}(({args}))\"\n", + "\n", + " def __str__(self):\n", + " name = self.__class__.__name__\n", + " first, last, m, n = self[0], self[-1], self.n_rows, self.n_cols\n", + " return f\"{name}(({first:.1f}, ...), ..., (..., {last:.1f}))[{m:d}x{n:d}]\"\n", + "\n", + " @property\n", + " def n_rows(self):\n", + " \"\"\"Number of rows in the matrix.\"\"\"\n", + " return len(self._entries)\n", + "\n", + " @property\n", + " def n_cols(self):\n", + " \"\"\"Number of columns in the matrix.\"\"\"\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\")\n", + "\n", + " def rows(self):\n", + " \"\"\"Loop over the rows of the matrix.\n", + "\n", + " Returns:\n", + " rows (Generator): produces Vectors representing\n", + " individual rows of the matrix\n", + " \"\"\"\n", + " return (Vector(r) for r in self._entries)\n", + "\n", + " def cols(self):\n", + " \"\"\"Loop over the columns of the matrix.\n", + "\n", + " Returns:\n", + " columns (Generator): produces Vectors representing\n", + " individual columns of the matrix\n", + " \"\"\"\n", + " return (\n", + " Vector(self._entries[r][c] for r in range(self.n_rows))\n", + " for c in range(self.n_cols)\n", + " )\n", + "\n", + " def entries(self, *, reverse=False, row_major=True):\n", + " \"\"\"Loop over the entries of the matrix.\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 False\n", + "\n", + " Returns:\n", + " entries (Generator): produces the entries of the matrix\n", + " \"\"\"\n", + " if reverse:\n", + " rows = range(self.n_rows - 1, -1, -1)\n", + " cols = 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)\n", + " \n", + " def __add__(self, other):\n", + " if isinstance(other, self.__class__):\n", + " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n", + " raise ValueError(\"matrices need to be of the same dimensions\")\n", + " return self.__class__(\n", + " (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", + " )\n", + " elif isinstance(other, numbers.Number):\n", + " return self.__class__((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", + " return self + (-other)\n", + "\n", + " def __rsub__(self, other):\n", + " if isinstance(other, Vector):\n", + " raise TypeError(\"vectors and matrices cannot be subtracted\")\n", + " return (-self) + other\n", + "\n", + " def _matrix_multiply(self, other):\n", + " if self.n_cols != other.n_rows:\n", + " raise ValueError(\"matrices need to have compatible dimensions\")\n", + " return self.__class__(\n", + " (rv * cv for cv in other.cols()) for rv in self.rows()\n", + " )\n", + "\n", + " def __mul__(self, other):\n", + " if isinstance(other, numbers.Number):\n", + " return self.__class__((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, self.__class__):\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 (\n", + " other.as_matrix(column=False)._matrix_multiply(self).as_vector()\n", + " )\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 __eq__(self, other):\n", + " if isinstance(other, self.__class__):\n", + " if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n", + " raise ValueError(\"matrices need to be of the same dimensions\")\n", + " for x, y in zip(self, other):\n", + " if abs(x - y) > self.zero_threshold:\n", + " return False\n", + " return True\n", + " return NotImplemented\n", + "\n", + " def __pos__(self):\n", + " return self\n", + "\n", + " def __neg__(self):\n", + " return self.__class__((-x for x in r) for r in self._entries)\n", + "\n", + " def __abs__(self):\n", + " \"\"\"The Frobenius norm of the matrix.\"\"\"\n", + " return norm(self)\n", + "\n", + " def __bool__(self):\n", + " return bool(abs(self))\n", + "\n", + " def __float__(self):\n", + " if not (self.n_rows == 1 and self.n_cols == 1):\n", + " raise RuntimeError(\"matrix must have one entry to become a scalar\")\n", + " return self[0]\n", + "\n", + " def as_vector(self):\n", + " \"\"\"Cast the matrix as a one-dimensional vector.\n", + "\n", + " Returns:\n", + " vector (Vector)\n", + "\n", + " Raises:\n", + " RuntimeError: if not one of the two dimensions is 1\n", + " \"\"\"\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)\n", + "\n", + " def transpose(self):\n", + " \"\"\"Switch the rows and columns of the matrix.\n", + "\n", + " Returns:\n", + " matrix (Matrix)\n", + " \"\"\"\n", + " return self.__class__(zip(*self._entries))" + ] + }, + { + "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": 192, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 193, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [], + "source": [ + "random.seed(42)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "We initialize `m` with random numbers in the range between `0` and `1000`." + ] + }, + { + "cell_type": "code", + "execution_count": 194, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "m = Matrix((1000 * 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": 195, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((639.427, 25.011, 275.029, 223.211, 736.471, 676.699, 892.180, 86.939, 421.922, 29.797, 218.638, 505.355, 26.536, 198.838, 649.884, 544.941, 220.441, 589.266, 809.430, 6.499, 805.819, 698.139, 340.251, 155.479, 957.213, 336.595, 92.746, 96.716, 847.494, 603.726, 807.128, 729.732, 536.228, 973.116, 378.534, 552.041, 829.405, 618.520, 861.707, 577.352, 704.572, 45.824, 227.898, 289.388, 79.792, 232.791, 101.001, 277.974, 635.684, 364.832,), (370.181, 209.507, 266.978, 936.655, 648.035, 609.131, 171.139, 729.127, 163.402, 379.455, 989.523, 640.000, 556.950, 684.614, 842.852, 776.000, 229.048, 32.100, 315.453, 267.741, 210.983, 942.910, 876.368, 314.678, 655.439, 395.632, 914.548, 458.852, 264.880, 246.628, 561.368, 262.742, 584.586, 897.823, 399.401, 219.321, 997.538, 509.526, 90.909, 47.116, 109.649, 627.446, 792.079, 422.160, 63.528, 381.619, 996.121, 529.114, 971.078, 860.780,), (11.481, 720.722, 681.710, 536.970, 266.825, 640.962, 111.552, 434.765, 453.724, 953.816, 875.853, 263.389, 500.586, 178.652, 912.628, 870.519, 298.445, 638.949, 608.970, 152.839, 762.511, 539.379, 778.626, 530.354, 0.572, 324.156, 19.477, 929.099, 878.722, 831.666, 307.514, 57.925, 878.010, 946.949, 85.653, 485.990, 69.213, 760.602, 765.834, 128.391, 475.282, 549.804, 265.057, 872.433, 423.138, 211.798, 539.296, 729.931, 201.151, 311.716,), (995.149, 649.878, 438.100, 517.576, 121.004, 224.697, 338.086, 588.309, 230.115, 220.217, 70.993, 631.103, 228.942, 905.420, 859.635, 70.857, 238.005, 668.978, 214.237, 132.312, 935.514, 571.043, 472.671, 784.619, 807.497, 190.410, 96.931, 431.051, 423.579, 467.025, 729.076, 673.365, 984.165, 98.418, 402.621, 339.303, 861.673, 248.656, 190.209, 448.614, 421.882, 278.545, 249.806, 923.266, 443.131, 861.349, 550.325, 50.588, 999.282, 836.028,), (968.996, 926.367, 848.696, 166.311, 485.641, 213.747, 401.040, 58.635, 378.973, 985.309, 265.203, 784.071, 455.008, 423.007, 957.318, 995.423, 555.768, 718.408, 154.797, 296.708, 968.709, 579.180, 542.195, 747.976, 57.165, 584.178, 502.850, 852.720, 157.433, 960.779, 80.111, 185.825, 595.035, 675.213, 235.204, 119.887, 890.287, 246.215, 594.519, 619.382, 419.225, 583.672, 522.783, 934.706, 204.259, 716.192, 238.686, 395.786, 671.690, 299.997,), (316.177, 751.864, 72.543, 458.286, 998.454, 996.096, 73.261, 213.154, 265.200, 933.259, 880.864, 879.270, 369.527, 157.747, 833.745, 703.540, 611.678, 987.233, 653.976, 7.823, 817.104, 299.379, 663.389, 938.930, 134.291, 115.429, 107.036, 553.224, 272.348, 604.830, 717.612, 203.597, 634.238, 263.984, 488.532, 905.336, 846.104, 92.298, 423.576, 276.680, 3.546, 771.119, 637.113, 261.955, 741.231, 551.680, 427.687, 9.670, 75.244, 883.106,), (903.929, 545.590, 834.595, 582.510, 148.094, 127.446, 308.258, 898.981, 796.122, 860.703, 898.925, 210.077, 249.530, 102.794, 780.116, 884.135, 406.377, 620.662, 154.553, 929.881, 864.606, 976.206, 810.772, 881.416, 24.786, 736.564, 332.185, 930.816, 802.235, 864.064, 810.749, 266.806, 787.375, 108.096, 872.167, 858.593, 222.434, 816.587, 460.303, 305.191, 795.345, 227.595, 23.664, 193.130, 328.262, 864.353, 966.889, 279.125, 641.482, 399.678,), (981.150, 536.216, 939.237, 115.342, 970.401, 178.568, 962.534, 265.466, 108.403, 434.564, 728.545, 313.677, 606.209, 511.423, 385.195, 576.588, 254.723, 708.785, 1.691, 925.575, 538.452, 719.430, 741.950, 670.629, 364.221, 69.974, 664.238, 330.200, 313.916, 848.015, 719.754, 300.322, 309.285, 408.393, 402.400, 295.655, 127.288, 420.446, 940.364, 677.318, 902.806, 615.515, 300.950, 547.937, 0.406, 286.914, 429.888, 579.985, 654.706, 464.988,), (442.160, 213.701, 473.186, 901.181, 796.025, 169.691, 84.796, 515.452, 632.941, 335.188, 818.423, 751.138, 672.796, 224.641, 199.130, 24.425, 244.843, 475.136, 849.738, 72.828, 414.441, 629.765, 194.435, 696.354, 494.377, 243.984, 656.058, 5.545, 750.964, 770.046, 106.587, 425.146, 175.887, 957.966, 517.958, 50.218, 249.198, 848.336, 456.462, 801.417, 667.578, 987.892, 595.452, 950.040, 891.426, 612.652, 719.274, 504.778, 830.569, 547.872,), (897.208, 743.655, 474.674, 259.192, 247.240, 637.661, 765.814, 521.300, 626.748, 274.597, 77.483, 285.728, 271.715, 319.710, 540.152, 138.374, 231.261, 693.950, 706.419, 64.229, 407.599, 542.611, 415.774, 206.834, 420.144, 904.838, 584.079, 695.523, 856.732, 765.595, 380.381, 5.896, 351.759, 753.475, 853.448, 953.430, 419.021, 747.516, 546.132, 603.253, 220.539, 219.422, 435.836, 29.025, 336.130, 679.142, 404.317, 165.045, 467.390, 127.628,), (622.257, 26.966, 394.020, 564.392, 27.102, 642.750, 135.699, 461.698, 50.285, 379.104, 211.660, 326.846, 761.230, 379.126, 752.010, 831.924, 252.272, 81.906, 19.383, 539.419, 999.908, 349.960, 650.144, 781.233, 651.755, 754.233, 949.612, 199.361, 20.380, 152.382, 126.221, 669.459, 563.970, 217.965, 699.465, 766.898, 167.789, 607.247, 747.926, 114.533, 819.301, 964.721, 108.099, 25.678, 311.957, 677.347, 958.173, 396.654, 715.015, 75.996,), (690.614, 627.242, 101.901, 772.481, 850.293, 600.412, 121.055, 983.844, 782.635, 347.204, 428.378, 370.571, 505.961, 341.231, 849.576, 822.331, 105.539, 960.788, 635.585, 828.707, 707.309, 435.487, 733.795, 965.474, 270.082, 808.199, 538.173, 483.498, 435.574, 731.026, 268.396, 851.713, 830.731, 86.663, 881.631, 243.863, 464.708, 610.332, 378.989, 28.700, 850.953, 181.840, 212.120, 797.832, 340.339, 880.320, 701.184, 276.269, 10.151, 948.063,), (85.613, 720.075, 488.578, 758.165, 690.609, 645.903, 490.821, 792.933, 93.053, 221.596, 691.787, 306.206, 581.556, 473.260, 530.922, 425.504, 745.935, 330.791, 702.855, 270.916, 251.404, 120.656, 192.584, 119.555, 535.864, 762.190, 185.150, 216.385, 484.199, 724.585, 976.607, 524.637, 282.999, 100.526, 194.118, 227.483, 179.442, 14.148, 534.135, 274.311, 974.295, 553.359, 697.417, 126.279, 868.461, 490.879, 872.720, 574.064, 469.397, 440.469,), (184.364, 51.377, 941.064, 477.729, 822.116, 400.707, 74.082, 629.446, 53.609, 149.198, 562.840, 303.836, 993.918, 118.452, 764.443, 606.318, 790.741, 225.687, 522.573, 450.514, 442.721, 860.167, 990.031, 305.380, 621.027, 609.631, 740.089, 947.590, 207.788, 211.025, 660.428, 157.057, 173.814, 75.065, 2.676, 450.504, 593.811, 291.259, 231.476, 706.956, 702.988, 454.031, 687.385, 923.911, 787.828, 625.058, 661.183, 933.668, 425.139, 544.562,), (647.635, 908.411, 826.631, 71.410, 165.923, 307.612, 748.958, 569.207, 288.611, 124.354, 688.678, 699.734, 942.676, 500.472, 493.795, 80.442, 39.861, 432.029, 322.322, 250.368, 91.327, 961.911, 835.959, 575.199, 950.786, 999.572, 672.282, 269.511, 40.232, 756.269, 470.501, 651.509, 916.073, 181.489, 585.330, 634.785, 491.726, 91.242, 347.961, 333.308, 670.134, 857.733, 329.804, 693.674, 288.218, 945.194, 813.566, 550.097, 454.826, 314.517,), (323.274, 970.185, 404.175, 514.596, 988.119, 657.660, 542.594, 413.248, 187.583, 361.779, 756.443, 625.409, 759.991, 203.558, 549.220, 927.673, 438.116, 698.250, 121.426, 973.147, 608.872, 239.297, 158.378, 550.839, 552.251, 93.209, 992.257, 912.930, 461.448, 117.466, 832.143, 498.376, 716.603, 508.872, 273.425, 834.724, 980.245, 243.731, 551.265, 383.586, 921.868, 508.241, 879.326, 864.027, 276.247, 790.006, 414.942, 934.248, 507.738, 820.549,), (282.839, 298.556, 586.938, 998.902, 489.640, 148.595, 538.581, 345.124, 551.917, 543.430, 455.345, 321.777, 188.652, 697.498, 571.798, 233.562, 775.544, 43.647, 744.705, 705.228, 811.409, 386.079, 663.689, 820.748, 980.818, 495.329, 37.020, 502.291, 590.180, 869.700, 874.190, 440.306, 525.951, 456.928, 722.444, 409.979, 654.781, 154.361, 469.491, 969.204, 338.561, 692.705, 649.837, 851.765, 852.341, 859.342, 380.009, 316.661, 718.717, 759.402,), (872.383, 35.899, 68.421, 631.161, 920.929, 997.426, 746.766, 433.971, 98.443, 633.748, 872.579, 443.679, 694.001, 903.424, 45.991, 796.143, 293.368, 374.841, 145.570, 531.166, 565.928, 792.519, 169.984, 78.968, 870.840, 619.710, 240.830, 912.829, 143.118, 461.150, 253.977, 255.327, 9.397, 804.633, 901.209, 677.611, 157.976, 441.730, 345.566, 587.572, 638.939, 424.309, 250.098, 845.304, 199.217, 384.693, 483.208, 237.206, 571.923, 574.812,), (992.692, 295.231, 977.944, 658.230, 274.480, 565.929, 685.799, 744.669, 49.044, 606.406, 496.727, 904.155, 286.194, 798.860, 607.065, 352.321, 636.618, 620.891, 677.764, 720.928, 659.182, 838.337, 628.248, 903.404, 646.341, 308.933, 440.823, 579.574, 732.360, 90.133, 295.110, 747.481, 175.640, 132.160, 539.408, 971.490, 530.852, 913.487, 830.473, 256.970, 824.690, 481.848, 806.488, 746.559, 338.715, 115.170, 962.893, 140.757, 966.500, 860.141,), (724.217, 979.942, 967.270, 804.588, 365.775, 790.682, 13.919, 536.572, 454.786, 672.828, 672.341, 584.560, 822.417, 940.292, 108.346, 233.822, 25.025, 884.235, 561.407, 915.256, 221.367, 63.217, 823.855, 909.388, 302.190, 408.296, 139.777, 946.262, 304.365, 492.625, 97.192, 887.259, 135.664, 453.644, 670.486, 743.140, 945.974, 419.127, 742.269, 154.523, 414.885, 99.022, 489.347, 408.116, 951.522, 32.716, 370.530, 443.383, 950.555, 855.450,), (99.355, 685.680, 544.466, 977.843, 358.674, 398.140, 189.809, 122.160, 848.033, 454.717, 662.769, 641.704, 597.146, 21.357, 786.795, 243.569, 125.924, 564.578, 68.610, 765.157, 207.157, 215.951, 869.695, 328.560, 147.554, 900.531, 2.836, 858.406, 144.688, 129.992, 250.654, 174.497, 661.058, 25.780, 14.860, 789.985, 237.932, 323.771, 174.246, 52.399, 741.718, 526.086, 745.665, 476.246, 778.017, 513.238, 109.054, 503.839, 945.416, 43.365,), (783.227, 866.981, 521.451, 458.043, 964.026, 60.825, 478.982, 401.617, 686.097, 490.269, 909.701, 73.491, 80.790, 608.297, 65.682, 275.016, 633.077, 548.356, 325.185, 994.628, 530.557, 453.715, 605.427, 99.178, 701.779, 852.793, 650.917, 768.963, 720.840, 215.023, 451.555, 228.494, 338.932, 453.499, 415.990, 95.086, 426.764, 665.108, 374.301, 152.639, 922.985, 67.133, 831.772, 93.230, 96.564, 738.796, 811.769, 556.371, 586.465, 561.586,), (329.646, 122.231, 353.598, 665.341, 750.284, 868.092, 721.061, 968.399, 600.410, 351.646, 577.919, 212.739, 656.736, 224.245, 108.218, 845.373, 367.561, 762.606, 574.100, 807.221, 845.155, 974.547, 818.427, 613.573, 642.699, 26.254, 929.084, 829.461, 267.448, 180.416, 702.699, 308.985, 339.825, 6.106, 869.863, 566.321, 400.784, 141.875, 633.172, 30.657, 746.112, 215.133, 419.832, 340.896, 370.053, 721.596, 776.836, 567.594, 84.957, 52.609,), (157.410, 617.838, 673.969, 272.103, 661.939, 485.662, 442.044, 273.167, 754.943, 113.818, 429.914, 283.246, 678.486, 486.633, 667.133, 45.417, 395.263, 599.325, 7.687, 301.419, 211.234, 137.235, 255.520, 328.122, 7.730, 747.014, 175.695, 380.207, 703.671, 500.262, 833.354, 806.200, 72.075, 861.764, 42.302, 18.742, 921.162, 862.110, 575.759, 573.400, 709.499, 417.694, 115.173, 20.857, 324.768, 801.322, 618.125, 832.026, 919.770, 88.130,), (844.484, 243.316, 588.871, 523.963, 395.767, 310.275, 339.513, 333.069, 168.133, 510.483, 114.027, 509.952, 905.923, 349.375, 727.379, 818.949, 815.037, 236.269, 146.444, 197.272, 602.399, 760.215, 655.509, 177.146, 772.848, 494.117, 754.446, 759.877, 448.905, 924.154, 564.492, 635.298, 624.522, 864.247, 627.217, 150.957, 68.286, 442.208, 302.820, 274.674, 56.172, 507.337, 310.408, 451.914, 56.890, 831.697, 76.731, 864.250, 855.293, 615.008,), (507.068, 462.712, 554.316, 791.818, 895.877, 449.734, 809.816, 651.837, 321.527, 475.629, 150.861, 61.874, 103.502, 899.127, 343.438, 714.316, 504.549, 172.559, 247.744, 437.758, 439.422, 522.748, 158.746, 372.852, 282.894, 408.769, 338.367, 597.886, 789.227, 647.305, 65.912, 94.506, 678.379, 284.147, 723.734, 656.564, 906.343, 873.280, 333.362, 582.740, 141.428, 349.821, 967.697, 698.480, 391.958, 595.041, 938.002, 309.582, 376.679, 791.662,), (813.185, 670.116, 828.959, 738.775, 685.414, 526.393, 646.025, 423.406, 361.828, 362.598, 180.263, 214.193, 947.668, 486.271, 226.543, 137.565, 77.165, 844.428, 101.141, 770.875, 835.120, 883.682, 37.747, 336.764, 766.308, 131.049, 376.720, 162.247, 831.345, 771.098, 809.044, 165.539, 437.673, 410.859, 676.363, 237.530, 444.199, 284.928, 748.537, 448.928, 534.011, 309.468, 808.624, 469.016, 835.113, 367.841, 947.130, 984.440, 461.680, 281.772,), (381.872, 527.460, 966.268, 816.891, 801.259, 138.399, 250.003, 641.179, 874.117, 554.541, 102.590, 845.892, 851.166, 285.063, 763.117, 272.791, 905.306, 147.349, 437.473, 946.413, 222.038, 451.128, 349.585, 26.670, 53.257, 502.007, 235.778, 994.525, 374.913, 28.188, 930.826, 839.176, 649.961, 791.381, 137.600, 286.879, 829.762, 696.072, 138.793, 705.536, 448.601, 5.251, 79.226, 255.924, 834.963, 548.804, 727.235, 527.772, 111.187, 288.102,), (301.151, 47.749, 419.826, 793.899, 457.114, 110.858, 905.147, 596.739, 16.435, 515.376, 241.938, 143.577, 429.239, 614.810, 240.564, 416.568, 664.371, 85.614, 974.654, 67.679, 526.059, 507.328, 988.331, 554.152, 390.454, 470.135, 635.671, 981.039, 253.650, 16.242, 788.520, 344.802, 732.941, 628.257, 771.501, 735.187, 332.519, 44.336, 546.014, 813.509, 175.089, 779.143, 464.623, 695.389, 631.736, 811.498, 63.101, 776.190, 457.680, 293.443,), (43.806, 199.470, 41.906, 933.371, 515.384, 989.123, 543.031, 253.314, 753.291, 191.103, 356.974, 780.842, 865.798, 331.925, 124.475, 368.019, 889.487, 743.308, 894.637, 386.645, 973.724, 496.203, 497.523, 924.310, 519.276, 801.148, 727.081, 78.927, 602.453, 822.341, 545.474, 321.211, 80.069, 660.919, 306.496, 602.622, 426.116, 689.765, 351.547, 42.355, 870.037, 352.559, 998.151, 274.555, 980.027, 947.904, 75.041, 637.513, 363.311, 801.096,), (679.411, 952.789, 142.779, 607.573, 781.312, 34.799, 67.233, 778.515, 366.328, 382.854, 567.245, 605.095, 679.062, 948.824, 372.013, 763.084, 573.922, 529.460, 398.034, 649.561, 249.612, 113.449, 735.675, 499.044, 386.987, 561.673, 261.777, 260.290, 446.273, 996.365, 285.577, 916.479, 491.200, 122.637, 852.826, 452.043, 898.679, 445.111, 87.791, 681.929, 845.521, 319.588, 347.425, 64.939, 542.171, 891.332, 851.362, 711.809, 927.324, 637.700,), (793.696, 508.756, 121.362, 200.980, 138.877, 790.373, 26.284, 554.021, 368.911, 803.662, 551.647, 611.948, 86.215, 309.291, 999.595, 718.870, 525.696, 769.165, 823.339, 73.751, 972.380, 642.339, 449.974, 680.109, 344.515, 877.960, 780.263, 639.794, 181.963, 966.265, 432.618, 910.712, 55.413, 124.161, 153.015, 164.657, 322.661, 709.332, 346.023, 940.904, 894.926, 845.934, 250.605, 635.057, 550.841, 125.170, 302.825, 533.478, 502.573, 168.636,), (941.607, 154.194, 658.733, 720.633, 605.139, 842.530, 563.618, 825.236, 28.373, 45.462, 641.454, 576.771, 651.130, 766.959, 416.587, 638.991, 498.038, 627.164, 289.672, 956.650, 482.945, 804.688, 684.991, 297.434, 72.973, 59.913, 439.605, 484.251, 204.023, 606.660, 312.582, 718.363, 734.200, 860.777, 975.374, 130.766, 370.540, 561.651, 319.116, 466.473, 267.472, 247.919, 96.812, 290.212, 384.150, 615.377, 248.270, 865.308, 159.700, 327.436,), (577.687, 312.715, 763.121, 498.266, 514.725, 498.760, 308.540, 23.176, 945.233, 505.444, 966.687, 215.144, 352.895, 50.540, 494.894, 882.339, 654.260, 470.587, 536.691, 847.172, 430.928, 882.456, 727.508, 763.857, 365.937, 400.582, 570.282, 194.655, 553.223, 73.532, 504.256, 764.404, 279.721, 989.091, 680.399, 118.811, 975.083, 393.904, 794.897, 339.085, 938.949, 754.965, 199.058, 509.123, 500.078, 45.303, 137.036, 333.041, 473.744, 456.989,), (606.261, 515.506, 327.966, 613.068, 162.502, 990.616, 739.319, 299.234, 336.373, 828.289, 532.340, 708.740, 299.791, 815.749, 368.358, 673.806, 979.898, 583.702, 796.755, 725.324, 688.044, 26.647, 474.590, 967.071, 782.904, 776.162, 577.634, 721.400, 583.523, 170.512, 629.025, 619.736, 841.167, 147.776, 680.727, 31.571, 948.205, 109.896, 18.937, 313.692, 151.431, 690.500, 410.377, 774.972, 920.521, 872.818, 735.837, 62.281, 138.082, 207.342,), (325.050, 662.227, 525.477, 313.753, 173.182, 912.124, 342.327, 354.287, 771.990, 720.925, 643.309, 693.313, 610.077, 192.264, 246.519, 558.087, 224.867, 972.911, 297.615, 289.004, 207.278, 704.988, 317.041, 348.803, 933.700, 795.405, 273.458, 121.874, 676.622, 379.694, 980.161, 818.377, 954.609, 804.616, 290.453, 287.630, 714.141, 346.364, 442.376, 256.444, 479.079, 202.068, 538.578, 933.024, 696.171, 137.273, 615.677, 586.830, 242.458, 669.834,), (531.041, 637.945, 52.491, 413.301, 717.358, 100.545, 770.766, 5.181, 550.353, 929.100, 406.907, 935.032, 878.400, 477.449, 199.456, 963.914, 321.168, 645.898, 907.937, 89.461, 574.133, 535.152, 723.118, 936.669, 913.230, 175.065, 882.245, 175.789, 919.635, 997.172, 396.995, 495.384, 936.609, 962.131, 926.040, 876.743, 9.267, 567.962, 107.301, 982.994, 284.562, 989.099, 543.300, 493.912, 938.561, 851.060, 468.021, 192.811, 112.647, 162.494,), (458.914, 257.265, 186.199, 736.618, 790.768, 567.781, 757.283, 175.495, 856.147, 897.043, 826.990, 515.281, 86.738, 669.256, 184.781, 140.612, 323.602, 248.047, 260.785, 235.521, 753.757, 954.035, 301.946, 722.883, 11.436, 653.683, 692.769, 62.124, 118.225, 306.806, 405.417, 502.520, 895.118, 703.557, 310.978, 117.416, 916.130, 295.038, 614.625, 219.129, 133.569, 153.186, 747.735, 605.739, 415.846, 549.235, 470.828, 537.518, 664.094, 218.412,), (247.465, 754.740, 873.135, 81.870, 446.748, 703.766, 78.103, 564.169, 61.758, 547.649, 505.487, 572.702, 149.852, 328.118, 520.342, 116.240, 205.401, 583.148, 90.942, 510.375, 808.692, 453.432, 513.248, 456.798, 57.737, 462.378, 806.915, 723.280, 395.949, 816.453, 745.804, 578.311, 45.290, 344.529, 63.760, 994.124, 934.583, 69.019, 933.776, 31.735, 408.867, 768.972, 765.828, 978.333, 645.881, 420.362, 992.857, 382.480, 869.620, 906.767,), (375.646, 682.730, 661.793, 539.300, 653.534, 347.770, 178.474, 537.258, 528.843, 727.858, 222.690, 3.473, 22.735, 298.363, 673.500, 544.445, 531.934, 823.360, 247.512, 346.160, 275.650, 937.410, 725.024, 112.845, 809.478, 419.241, 766.053, 883.757, 15.646, 206.082, 100.897, 33.576, 597.785, 703.286, 48.676, 740.541, 402.265, 234.339, 217.269, 863.730, 56.444, 503.896, 289.263, 815.786, 731.517, 318.904, 597.918, 672.532, 320.665, 301.764,), (143.260, 660.212, 221.043, 300.501, 60.958, 948.520, 879.714, 911.578, 625.993, 427.201, 495.621, 972.290, 941.586, 671.343, 785.805, 318.734, 416.325, 149.218, 376.460, 754.416, 473.519, 849.341, 300.736, 707.577, 805.776, 914.741, 562.386, 967.786, 557.287, 134.093, 242.859, 203.337, 646.706, 922.226, 847.133, 92.464, 724.585, 190.482, 268.462, 673.672, 602.922, 873.620, 188.163, 761.696, 724.305, 558.850, 479.394, 869.474, 332.964, 957.020,), (15.334, 937.160, 962.078, 117.316, 999.572, 478.921, 242.593, 604.402, 204.513, 915.126, 552.079, 775.514, 380.662, 533.650, 359.260, 261.562, 512.817, 497.277, 98.608, 981.318, 469.490, 839.731, 914.330, 370.705, 413.930, 562.525, 221.274, 145.923, 260.774, 934.758, 579.143, 417.578, 152.411, 329.865, 379.840, 833.363, 499.301, 654.608, 684.847, 257.327, 821.592, 966.508, 641.694, 490.596, 168.234, 794.976, 169.266, 720.314, 488.316, 916.899,), (542.137, 641.809, 58.732, 33.824, 846.697, 945.188, 668.216, 764.339, 412.392, 842.545, 231.433, 707.170, 9.141, 505.733, 373.201, 617.835, 666.755, 616.519, 483.204, 487.854, 6.612, 551.644, 11.851, 529.418, 274.741, 977.479, 17.143, 813.157, 674.033, 806.168, 909.773, 107.016, 96.314, 148.897, 191.932, 526.456, 815.214, 267.325, 396.896, 373.052, 406.027, 565.002, 990.233, 225.857, 684.042, 847.867, 653.736, 858.219, 759.586, 93.501,), (379.264, 552.701, 56.115, 9.450, 171.384, 499.858, 433.910, 784.376, 565.857, 857.960, 95.362, 528.159, 42.552, 211.417, 868.117, 887.554, 475.500, 46.562, 74.348, 925.585, 899.312, 563.510, 32.902, 928.766, 314.485, 961.469, 587.036, 752.254, 712.711, 398.296, 76.937, 162.450, 240.472, 834.651, 389.157, 896.526, 331.730, 755.609, 139.951, 988.478, 724.164, 500.793, 974.323, 53.696, 437.088, 838.675, 340.593, 769.006, 954.858, 396.703,), (773.555, 29.626, 273.327, 992.586, 490.603, 355.811, 941.143, 431.848, 679.695, 660.672, 85.694, 618.616, 798.055, 713.109, 82.038, 154.221, 711.677, 633.901, 739.655, 316.678, 106.551, 5.195, 308.267, 359.917, 269.766, 132.507, 187.392, 448.844, 554.740, 408.044, 26.262, 353.914, 93.064, 598.044, 324.430, 385.238, 291.847, 387.800, 84.700, 901.136, 905.208, 978.173, 571.960, 169.583, 380.732, 138.840, 301.131, 493.124, 63.267, 434.676,), (421.102, 484.231, 76.921, 251.700, 246.590, 625.034, 593.806, 195.548, 106.972, 304.658, 948.823, 332.217, 620.192, 804.076, 329.542, 334.736, 815.475, 859.508, 974.225, 136.124, 320.665, 947.279, 200.851, 314.183, 964.575, 968.725, 291.448, 694.958, 491.007, 575.879, 242.424, 376.055, 816.495, 392.935, 113.888, 563.851, 592.227, 545.629, 681.713, 550.099, 953.005, 461.622, 708.367, 438.455, 291.331, 692.835, 818.966, 795.657, 409.142, 499.303,), (633.336, 242.021, 658.663, 715.236, 789.077, 73.965, 990.701, 479.235, 400.805, 506.613, 920.392, 691.709, 543.645, 790.721, 359.529, 895.502, 536.906, 638.180, 84.982, 768.954, 657.602, 355.009, 647.000, 44.297, 983.608, 677.472, 399.618, 752.683, 965.717, 430.456, 10.548, 258.738, 510.676, 518.798, 580.518, 575.235, 445.779, 391.134, 772.342, 588.590, 500.466, 344.967, 24.563, 104.549, 415.975, 961.728, 116.069, 940.676, 141.675, 311.890,), (455.333, 206.867, 482.926, 476.163, 438.166, 696.763, 318.909, 300.264, 810.186, 115.085, 849.180, 647.970, 677.139, 164.354, 983.900, 243.913, 174.453, 160.136, 559.849, 958.463, 231.856, 405.047, 184.452, 640.479, 432.134, 29.192, 614.107, 197.324, 592.203, 388.836, 704.736, 205.784, 752.325, 808.730, 62.564, 101.752, 871.979, 186.960, 325.985, 457.550, 262.353, 862.637, 527.715, 639.109, 596.971, 611.308, 587.005, 347.925, 845.518, 617.363,), (813.738, 705.988, 297.445, 614.485, 84.752, 133.948, 117.862, 305.380, 183.045, 693.437, 510.825, 418.239, 137.867, 383.710, 185.754, 635.502, 693.433, 645.260, 999.900, 554.913, 489.642, 140.297, 314.580, 451.001, 53.611, 359.039, 9.583, 136.535, 815.216, 963.829, 505.438, 494.970, 684.697, 415.630, 839.892, 488.700, 82.671, 30.861, 761.057, 292.090, 274.853, 537.609, 168.209, 457.321, 742.518, 765.920, 549.726, 113.211, 114.207, 775.113,), (823.283, 366.862, 822.611, 41.611, 718.980, 546.353, 989.776, 102.416, 830.071, 751.345, 297.709, 999.313, 449.732, 348.577, 816.729, 439.070, 993.958, 775.632, 236.946, 810.703, 587.924, 350.631, 710.754, 632.771, 165.982, 139.235, 206.620, 206.943, 59.358, 350.815, 281.085, 538.769, 323.654, 704.054, 289.333, 267.343, 858.017, 985.488, 679.299, 95.225, 962.772, 785.691, 918.769, 992.486, 867.048, 126.888, 866.079, 249.677, 711.395, 828.482,), (761.474, 676.235, 489.459, 577.426, 268.717, 414.225, 451.992, 633.628, 880.125, 93.095, 515.613, 278.226, 936.336, 369.071, 950.254, 327.289, 2.473, 774.135, 732.724, 730.932, 458.449, 664.144, 358.223, 63.331, 534.424, 217.830, 429.643, 211.851, 268.537, 828.344, 337.755, 577.934, 566.142, 485.338, 343.740, 682.552, 48.409, 99.575, 783.890, 459.582, 124.237, 857.652, 441.286, 0.676, 958.032, 202.318, 688.592, 131.913, 649.997, 158.977,), (932.726, 274.019, 654.588, 250.389, 371.844, 903.800, 165.525, 396.342, 305.509, 699.441, 234.144, 655.485, 703.698, 1.086, 476.807, 132.700, 226.191, 679.983, 9.287, 695.597, 817.109, 988.155, 422.314, 132.175, 70.828, 383.070, 730.763, 102.427, 313.351, 880.989, 137.129, 773.460, 753.158, 133.146, 992.940, 142.853, 530.508, 8.475, 650.020, 440.099, 722.432, 628.080, 151.374, 411.710, 686.566, 859.963, 86.688, 100.465, 752.446, 589.574,), (384.032, 963.249, 314.504, 139.830, 276.968, 84.249, 553.397, 600.008, 607.593, 778.970, 690.476, 847.892, 658.405, 301.649, 517.749, 509.523, 747.844, 295.542, 54.569, 897.913, 954.672, 494.888, 112.744, 499.583, 593.930, 528.287, 977.697, 986.883, 933.924, 131.983, 860.814, 568.380, 365.412, 682.942, 762.726, 954.453, 770.367, 16.689, 67.533, 262.185, 39.827, 60.469, 789.290, 506.611, 628.571, 501.049, 415.432, 701.811, 82.428, 536.565,), (616.047, 277.468, 309.907, 511.305, 203.197, 808.060, 536.390, 390.732, 634.294, 834.526, 681.056, 66.115, 698.676, 729.958, 846.468, 57.906, 86.213, 434.484, 453.372, 608.832, 309.290, 741.694, 740.658, 119.443, 707.901, 701.500, 163.833, 953.016, 523.005, 782.987, 720.766, 166.963, 126.931, 781.124, 268.764, 886.415, 771.430, 29.356, 807.108, 271.981, 63.872, 712.323, 576.652, 77.070, 455.198, 360.124, 499.610, 566.886, 367.688, 255.116,), (102.905, 573.912, 722.778, 228.404, 508.825, 44.038, 862.928, 244.551, 471.765, 382.979, 150.085, 931.160, 857.485, 552.865, 913.948, 740.666, 419.369, 321.802, 416.257, 720.288, 271.258, 77.887, 372.808, 502.041, 901.941, 179.344, 804.338, 981.414, 954.079, 68.926, 465.094, 282.307, 844.847, 327.301, 553.091, 7.969, 200.671, 563.807, 303.909, 622.718, 463.927, 591.691, 493.361, 772.613, 195.422, 900.443, 760.482, 245.127, 6.378, 410.036,), (232.998, 346.424, 839.574, 877.199, 950.990, 1.462, 657.304, 849.006, 727.215, 103.949, 529.814, 238.168, 492.029, 59.895, 996.959, 711.652, 93.027, 921.274, 897.287, 519.759, 700.847, 372.492, 974.555, 84.902, 95.577, 133.514, 819.963, 74.830, 567.821, 434.983, 964.218, 236.772, 260.991, 315.009, 800.817, 700.726, 735.353, 318.058, 271.956, 74.687, 202.713, 779.937, 584.708, 155.411, 164.375, 466.055, 406.513, 535.925, 964.635, 207.636,), (308.308, 265.008, 119.914, 157.615, 686.055, 826.387, 696.878, 40.330, 835.925, 327.794, 91.213, 248.219, 355.743, 513.460, 677.183, 260.167, 990.677, 31.082, 404.392, 452.201, 748.078, 249.853, 462.043, 803.897, 139.793, 11.957, 830.372, 982.567, 130.715, 823.673, 372.240, 630.298, 644.685, 582.324, 258.821, 812.747, 21.800, 64.472, 902.496, 443.431, 128.792, 905.078, 829.356, 331.550, 42.696, 460.995, 167.989, 573.884, 821.685, 394.999,), (29.482, 683.213, 172.807, 214.716, 187.151, 279.855, 883.424, 34.649, 619.177, 245.785, 295.105, 411.992, 550.689, 60.979, 279.776, 137.236, 199.458, 884.653, 525.814, 630.754, 802.168, 794.846, 989.404, 781.916, 359.112, 544.518, 484.679, 912.677, 502.393, 388.381, 179.816, 318.878, 219.020, 895.765, 778.538, 58.591, 991.531, 529.432, 766.842, 999.606, 973.980, 100.134, 656.864, 266.527, 816.285, 917.259, 55.909, 996.392, 219.412, 846.505,), (797.391, 354.805, 839.222, 845.221, 176.100, 592.520, 806.210, 697.627, 913.980, 28.207, 700.561, 947.559, 563.606, 563.109, 188.234, 988.006, 881.626, 492.227, 309.053, 490.436, 90.257, 232.623, 218.809, 526.449, 0.683, 917.896, 201.464, 130.490, 716.938, 918.781, 844.284, 323.589, 21.913, 586.609, 917.224, 774.366, 846.481, 860.669, 960.559, 373.591, 941.923, 395.596, 101.032, 301.765, 136.452, 157.504, 948.694, 791.843, 960.662, 649.180,), (174.203, 968.743, 693.560, 928.845, 786.992, 223.237, 588.950, 175.352, 306.823, 688.498, 127.348, 728.831, 948.788, 948.698, 391.601, 994.283, 965.184, 32.383, 602.389, 921.038, 967.532, 220.895, 565.550, 936.688, 140.643, 745.343, 237.996, 982.380, 167.887, 885.320, 88.726, 708.966, 639.103, 886.654, 446.630, 265.212, 249.541, 67.795, 256.658, 108.035, 1.281, 385.937, 732.584, 969.102, 884.552, 493.082, 378.713, 546.024, 101.429, 479.489,), (864.051, 650.970, 687.160, 162.657, 73.709, 845.762, 294.909, 318.717, 951.662, 74.200, 170.105, 375.458, 731.985, 547.046, 898.124, 93.105, 594.031, 613.645, 482.737, 30.993, 942.442, 164.997, 889.753, 157.104, 101.258, 205.550, 189.984, 697.197, 722.038, 729.974, 265.265, 281.523, 237.548, 49.635, 571.770, 839.855, 153.590, 360.829, 427.606, 294.549, 662.020, 600.216, 199.675, 25.736, 170.832, 291.811, 81.936, 843.769, 308.353, 397.467,), (489.097, 661.040, 91.137, 544.126, 184.881, 885.493, 369.402, 445.775, 263.296, 465.058, 226.242, 268.562, 61.639, 752.235, 667.468, 85.707, 343.791, 541.449, 970.706, 589.726, 553.602, 840.797, 818.405, 418.632, 535.514, 866.894, 474.824, 881.654, 476.262, 78.955, 902.777, 714.306, 502.101, 900.455, 800.450, 677.535, 620.273, 120.282, 757.189, 172.879, 984.368, 972.165, 808.461, 126.186, 423.375, 988.280, 435.393, 997.269, 627.226, 834.108,), (257.886, 910.804, 914.174, 67.012, 388.099, 397.355, 326.127, 276.201, 458.157, 873.466, 786.551, 623.058, 522.562, 419.594, 414.442, 148.200, 587.934, 758.377, 939.650, 924.928, 562.684, 100.990, 286.093, 535.633, 343.869, 410.890, 383.043, 485.585, 608.962, 37.466, 275.414, 143.853, 608.655, 693.661, 38.783, 889.574, 331.494, 237.579, 745.750, 920.835, 897.031, 20.233, 817.386, 303.055, 280.347, 491.619, 696.183, 98.217, 868.900, 134.386,), (974.219, 443.111, 825.823, 269.420, 416.773, 645.555, 187.936, 211.391, 823.969, 740.945, 759.493, 867.189, 821.017, 515.270, 158.972, 311.123, 506.795, 135.650, 851.295, 879.333, 28.948, 192.763, 832.930, 836.977, 249.490, 456.449, 918.031, 704.634, 273.977, 823.506, 505.126, 635.410, 123.873, 30.563, 372.467, 594.044, 177.584, 870.481, 586.880, 349.760, 163.514, 894.492, 748.961, 688.851, 285.069, 386.566, 162.907, 572.258, 964.918, 857.111,), (647.380, 677.695, 269.084, 409.507, 20.052, 780.304, 767.573, 8.898, 911.515, 647.372, 601.142, 8.464, 252.390, 805.086, 305.460, 967.024, 642.740, 423.807, 376.475, 348.709, 251.999, 466.698, 677.196, 824.311, 397.153, 102.312, 511.514, 662.355, 843.284, 374.230, 647.342, 609.050, 298.476, 108.105, 63.837, 988.361, 640.595, 861.492, 261.147, 711.094, 892.374, 298.791, 149.929, 765.474, 899.687, 805.430, 802.364, 600.033, 660.530, 680.756,), (721.283, 655.401, 997.469, 259.426, 418.566, 388.275, 35.319, 708.069, 572.042, 189.915, 726.550, 222.363, 534.635, 784.897, 906.527, 671.868, 507.315, 845.419, 840.639, 876.495, 181.136, 97.603, 127.944, 258.652, 808.344, 762.918, 183.068, 679.712, 335.632, 89.300, 355.283, 744.210, 307.085, 788.090, 331.317, 260.559, 294.051, 851.214, 470.537, 866.393, 583.575, 944.301, 71.216, 889.426, 500.477, 867.498, 381.669, 298.356, 54.062, 854.240,), (137.365, 200.297, 409.192, 569.404, 906.621, 457.571, 316.383, 715.668, 778.941, 487.581, 631.033, 176.824, 634.499, 4.714, 273.528, 761.193, 168.606, 764.484, 489.577, 763.569, 88.041, 614.480, 633.488, 403.393, 965.328, 383.316, 37.725, 199.414, 373.101, 14.077, 322.213, 833.229, 190.577, 676.748, 626.687, 248.823, 693.525, 344.350, 128.931, 383.550, 588.671, 167.020, 823.844, 298.202, 290.828, 727.832, 596.370, 337.835, 887.974, 995.472,), (342.733, 901.384, 359.251, 188.426, 948.084, 918.205, 403.392, 228.418, 727.169, 131.206, 734.077, 589.693, 168.985, 366.591, 650.540, 37.363, 876.544, 255.789, 534.733, 48.587, 994.680, 661.970, 653.568, 19.742, 689.750, 416.776, 380.254, 547.282, 474.396, 153.127, 695.192, 630.311, 301.116, 661.665, 662.483, 269.977, 605.634, 137.164, 830.653, 104.914, 718.766, 117.735, 114.013, 106.264, 198.647, 199.749, 262.994, 523.146, 201.673, 703.437,), (295.351, 39.406, 496.355, 207.694, 933.124, 330.604, 2.738, 671.633, 906.880, 835.232, 669.022, 149.144, 90.083, 511.705, 723.564, 101.290, 255.892, 231.160, 988.666, 296.022, 464.277, 99.809, 174.702, 39.445, 290.567, 801.596, 312.707, 738.540, 94.998, 758.204, 45.889, 851.999, 663.355, 170.512, 357.527, 437.715, 621.807, 878.476, 92.951, 814.964, 182.869, 400.778, 962.310, 271.833, 385.715, 850.672, 799.899, 648.846, 796.910, 113.057,), (696.170, 58.646, 942.467, 159.395, 416.028, 590.750, 802.265, 678.393, 181.261, 379.751, 358.599, 28.816, 684.464, 838.536, 973.445, 130.655, 920.398, 112.937, 411.284, 45.972, 261.613, 314.238, 704.568, 677.929, 767.529, 576.649, 565.012, 977.896, 669.844, 338.301, 523.103, 700.580, 95.242, 661.713, 248.577, 345.749, 676.296, 384.877, 839.033, 558.344, 987.792, 54.566, 643.399, 156.927, 848.846, 851.871, 869.416, 74.865, 491.648, 240.892,), (970.145, 50.351, 222.707, 643.317, 403.277, 235.000, 459.095, 801.261, 448.099, 856.589, 447.068, 118.707, 497.373, 653.373, 102.643, 412.339, 557.131, 0.170, 90.896, 604.203, 619.066, 304.598, 508.338, 206.851, 671.474, 950.355, 363.342, 54.275, 222.918, 454.460, 560.151, 619.758, 473.134, 657.167, 715.914, 114.023, 759.320, 221.747, 341.357, 829.884, 964.434, 291.984, 521.985, 702.096, 45.778, 164.155, 140.449, 716.856, 721.559, 106.962,), (610.852, 187.543, 930.216, 392.896, 457.046, 781.420, 716.788, 107.766, 414.470, 926.627, 837.466, 588.811, 772.145, 450.505, 658.457, 956.190, 134.636, 498.829, 530.364, 48.572, 935.308, 838.816, 482.932, 508.333, 921.162, 177.202, 578.554, 730.495, 128.382, 387.406, 600.546, 882.696, 504.110, 384.663, 979.501, 915.910, 762.375, 273.596, 963.594, 970.492, 452.846, 133.372, 412.745, 700.014, 748.427, 298.913, 701.494, 860.708, 711.874, 935.512,), (632.583, 200.889, 624.168, 290.579, 345.294, 672.374, 981.335, 650.086, 958.326, 503.893, 694.312, 322.381, 115.405, 352.238, 480.370, 570.638, 667.157, 417.478, 747.869, 841.389, 285.928, 847.301, 808.305, 522.730, 25.272, 145.327, 670.254, 199.902, 750.185, 160.935, 286.616, 250.561, 839.382, 690.595, 295.141, 753.594, 32.306, 814.026, 102.480, 868.035, 739.492, 864.841, 742.413, 561.271, 237.595, 784.307, 798.822, 287.639, 664.494, 926.485,), (387.555, 956.702, 975.823, 312.654, 552.125, 12.965, 251.341, 620.562, 780.925, 870.118, 829.983, 911.486, 704.629, 647.632, 755.108, 546.615, 603.387, 776.147, 964.290, 294.178, 177.426, 682.659, 186.977, 173.795, 513.847, 377.198, 428.508, 556.599, 130.067, 583.986, 255.016, 330.563, 709.689, 154.806, 153.703, 322.630, 50.855, 932.382, 615.609, 661.890, 490.551, 572.677, 358.086, 784.031, 317.859, 219.962, 183.869, 68.176, 505.140, 415.931,), (537.036, 91.946, 221.198, 213.354, 331.884, 360.798, 218.445, 752.656, 530.499, 996.631, 823.688, 981.133, 8.804, 668.938, 445.667, 904.496, 613.701, 621.132, 958.897, 682.551, 320.937, 915.932, 944.988, 385.876, 540.237, 282.829, 911.336, 822.109, 374.958, 802.817, 445.557, 43.852, 898.282, 192.512, 513.895, 948.212, 167.395, 956.276, 537.975, 7.331, 65.472, 670.335, 773.582, 864.951, 424.209, 103.927, 537.754, 702.743, 976.224, 775.207,), (646.561, 939.751, 746.891, 153.755, 459.225, 331.715, 87.521, 54.268, 794.215, 558.084, 574.962, 227.646, 258.648, 388.033, 630.217, 433.025, 16.891, 673.299, 534.834, 641.423, 618.349, 755.961, 585.608, 700.882, 72.589, 928.048, 106.136, 786.892, 301.351, 86.505, 765.274, 437.676, 395.113, 660.723, 473.988, 533.468, 136.381, 390.993, 797.485, 545.724, 960.134, 144.076, 677.289, 914.169, 795.017, 729.325, 373.131, 949.465, 553.524, 555.070,), (123.235, 5.030, 596.655, 537.152, 946.979, 304.691, 748.297, 903.546, 343.852, 412.654, 645.932, 512.553, 160.954, 220.788, 834.748, 194.326, 181.358, 799.660, 852.251, 851.388, 932.370, 994.313, 461.265, 549.665, 290.653, 67.363, 98.202, 725.589, 487.190, 330.851, 128.215, 655.473, 100.045, 619.516, 900.119, 317.786, 450.646, 616.342, 305.675, 584.170, 565.552, 364.493, 316.333, 428.307, 4.832, 246.175, 221.526, 739.816, 436.124, 840.110,), (134.306, 732.973, 877.886, 462.848, 358.742, 305.472, 551.672, 175.770, 606.628, 841.793, 858.714, 140.002, 538.618, 263.235, 886.336, 76.462, 75.400, 18.630, 507.178, 31.194, 581.890, 405.134, 590.036, 906.304, 551.594, 543.469, 998.728, 472.104, 775.197, 365.819, 223.307, 772.145, 733.227, 290.999, 464.701, 510.412, 396.785, 502.167, 662.678, 847.982, 806.540, 614.036, 165.844, 514.137, 446.220, 179.145, 948.712, 659.413, 967.736, 735.920,), (483.503, 358.796, 218.819, 487.642, 62.861, 369.435, 42.808, 206.774, 907.726, 361.251, 469.674, 454.643, 46.437, 980.590, 324.071, 703.736, 521.373, 829.647, 834.872, 263.316, 544.350, 173.850, 653.675, 365.297, 653.337, 835.469, 516.663, 376.329, 907.242, 516.372, 352.898, 867.719, 486.862, 482.110, 604.211, 501.837, 138.968, 165.606, 77.088, 643.477, 211.517, 186.906, 362.223, 717.303, 118.330, 230.346, 811.173, 720.256, 481.517, 478.815,), (210.704, 161.246, 833.365, 22.446, 43.144, 573.487, 161.129, 629.540, 39.191, 572.269, 55.912, 258.260, 180.038, 958.238, 599.358, 563.605, 18.620, 719.547, 661.745, 283.455, 85.749, 449.203, 992.776, 867.302, 170.567, 830.364, 600.839, 794.616, 820.453, 181.884, 659.649, 264.521, 724.187, 342.671, 453.470, 590.596, 229.814, 385.462, 108.570, 202.349, 859.592, 504.360, 419.795, 149.561, 96.299, 475.869, 614.748, 39.037, 783.868, 503.329,), (117.516, 482.241, 130.329, 603.475, 827.522, 896.969, 774.870, 649.232, 522.044, 370.377, 43.438, 529.817, 229.486, 802.300, 789.556, 381.236, 589.048, 741.394, 761.539, 696.708, 97.717, 133.883, 477.141, 232.513, 859.053, 283.267, 876.766, 408.647, 189.021, 709.149, 789.422, 577.987, 117.829, 7.194, 654.546, 687.767, 315.902, 362.270, 154.779, 651.576, 253.633, 854.977, 423.562, 365.644, 275.561, 680.606, 754.972, 413.228, 783.788, 482.468,), (370.213, 554.911, 253.778, 306.653, 344.356, 705.495, 735.782, 854.999, 659.283, 747.814, 446.595, 699.311, 161.795, 214.458, 400.561, 338.438, 550.576, 697.057, 706.790, 160.738, 964.570, 5.291, 91.089, 145.328, 925.862, 435.377, 64.080, 221.770, 80.040, 37.755, 384.326, 984.448, 613.966, 521.006, 711.606, 597.473, 945.716, 820.229, 640.291, 438.972, 201.707, 656.884, 803.910, 286.007, 33.609, 599.515, 515.814, 231.671, 169.483, 36.061,), (239.257, 0.287, 157.445, 996.817, 779.673, 353.128, 395.143, 586.080, 517.743, 736.926, 68.617, 90.166, 284.494, 829.765, 915.009, 348.095, 963.288, 276.508, 606.363, 194.525, 929.714, 574.907, 261.108, 407.135, 106.465, 72.386, 294.252, 947.015, 802.942, 956.709, 876.113, 813.147, 574.894, 694.192, 966.055, 563.169, 769.967, 757.473, 961.239, 458.848, 460.720, 586.827, 27.351, 116.268, 67.554, 633.662, 994.198, 676.913, 229.930, 315.731,), (955.446, 516.504, 9.723, 832.177, 248.258, 93.009, 673.698, 821.075, 76.029, 931.396, 476.555, 353.538, 894.316, 269.076, 947.118, 683.107, 909.927, 498.999, 199.667, 735.429, 872.742, 206.779, 202.767, 249.695, 618.597, 155.996, 106.689, 920.188, 675.812, 663.424, 613.827, 764.244, 541.474, 42.263, 482.292, 620.667, 492.340, 985.361, 897.000, 868.769, 490.805, 984.395, 916.007, 280.267, 222.093, 576.665, 54.148, 799.270, 478.320, 540.768,), (502.475, 393.717, 686.339, 174.800, 976.507, 698.245, 460.071, 689.211, 11.820, 210.752, 581.008, 325.436, 612.778, 259.701, 548.563, 237.139, 471.345, 613.084, 365.924, 498.770, 210.532, 700.713, 372.310, 854.326, 279.630, 179.889, 131.139, 575.893, 228.567, 99.870, 269.949, 235.805, 432.219, 380.883, 145.844, 959.148, 149.548, 805.668, 176.607, 499.598, 995.562, 849.390, 517.007, 720.560, 785.333, 300.034, 562.138, 567.835, 398.402, 690.543,), (60.036, 813.818, 476.539, 629.862, 449.855, 334.657, 360.962, 560.350, 931.845, 257.715, 19.835, 121.237, 867.369, 963.117, 198.958, 575.894, 649.131, 174.539, 780.309, 355.109, 673.193, 487.494, 736.526, 889.632, 381.071, 286.524, 631.712, 144.848, 167.576, 807.717, 337.388, 631.273, 571.638, 848.901, 71.344, 161.999, 228.218, 316.878, 291.356, 267.472, 644.644, 271.248, 444.898, 862.806, 363.165, 586.920, 965.526, 413.989, 183.886, 23.101,), (727.772, 662.023, 940.437, 700.697, 80.065, 166.910, 103.009, 63.792, 878.691, 548.434, 26.365, 397.013, 764.996, 83.251, 262.452, 155.274, 654.833, 885.471, 309.371, 247.397, 284.175, 626.481, 131.277, 838.518, 28.571, 663.549, 860.369, 325.201, 476.155, 974.982, 540.584, 272.414, 444.845, 969.542, 697.623, 177.037, 597.584, 631.048, 635.851, 563.984, 523.460, 639.528, 309.661, 347.168, 539.668, 804.708, 441.860, 365.783, 259.807, 303.573,), (0.092, 815.380, 847.946, 343.885, 469.198, 8.317, 922.088, 946.958, 477.067, 9.133, 430.435, 292.923, 231.215, 7.218, 373.644, 411.736, 560.554, 394.752, 163.269, 737.118, 389.723, 378.358, 262.980, 422.239, 239.562, 764.538, 909.954, 807.575, 684.640, 284.677, 742.927, 808.809, 412.904, 853.482, 182.407, 289.595, 636.893, 617.729, 272.134, 622.754, 187.789, 19.395, 49.632, 534.971, 185.938, 102.273, 269.195, 715.196, 727.107, 232.810,), (150.684, 493.580, 341.913, 311.577, 799.462, 997.963, 463.684, 791.425, 330.249, 843.546, 951.644, 56.038, 775.653, 71.385, 470.147, 192.192, 841.539, 817.251, 828.252, 121.962, 768.146, 248.962, 771.225, 442.961, 737.619, 33.510, 460.835, 770.996, 521.139, 982.116, 472.826, 681.442, 312.109, 323.063, 629.164, 42.186, 937.421, 520.926, 253.313, 638.520, 198.724, 887.520, 863.658, 217.788, 112.928, 632.909, 324.513, 167.360, 276.100, 119.650,), (789.144, 8.930, 41.993, 783.475, 475.271, 596.212, 371.399, 89.480, 157.698, 91.402, 618.645, 930.849, 996.973, 625.132, 59.775, 644.575, 701.328, 791.466, 125.827, 232.675, 981.676, 788.562, 756.698, 805.525, 438.719, 192.965, 689.250, 358.167, 134.732, 898.663, 485.302, 437.500, 294.715, 697.038, 189.776, 186.627, 347.665, 732.402, 275.324, 831.350, 914.474, 554.520, 68.877, 155.025, 295.673, 263.349, 367.113, 0.720, 638.026, 379.044,), (185.534, 18.720, 856.829, 776.017, 238.787, 721.004, 658.289, 538.957, 387.491, 524.070, 497.549, 551.468, 613.039, 322.600, 640.904, 110.464, 523.201, 66.680, 835.193, 129.774, 873.493, 202.079, 485.370, 98.754, 571.012, 836.300, 657.921, 525.821, 701.314, 255.939, 366.354, 605.954, 70.874, 930.211, 208.738, 491.406, 889.767, 79.957, 801.020, 60.700, 558.020, 938.244, 415.953, 369.069, 708.434, 823.643, 265.816, 40.653, 70.388, 303.905,), (944.603, 960.587, 97.119, 725.172, 531.661, 189.460, 526.192, 248.763, 625.325, 178.898, 749.194, 415.185, 105.514, 638.624, 357.963, 458.721, 665.242, 882.824, 166.809, 180.397, 401.557, 337.513, 158.176, 998.434, 443.012, 352.324, 315.155, 991.463, 324.654, 371.720, 764.184, 430.588, 725.868, 608.413, 563.145, 213.928, 765.708, 926.147, 254.089, 961.652, 447.500, 397.091, 726.313, 982.956, 595.763, 517.609, 993.412, 301.842, 300.334, 221.887,), (855.720, 21.701, 821.940, 689.719, 275.957, 553.663, 556.323, 925.922, 154.715, 37.736, 355.656, 138.408, 367.083, 582.156, 232.994, 811.074, 91.905, 399.798, 917.882, 734.266, 723.581, 792.904, 172.902, 825.705, 689.595, 576.232, 907.656, 595.231, 300.393, 730.789, 576.284, 78.477, 55.923, 770.902, 347.930, 817.142, 416.522, 867.831, 869.787, 226.718, 652.779, 602.302, 11.434, 777.419, 382.487, 304.783, 41.182, 539.930, 149.580, 502.402,), (220.797, 50.520, 731.579, 392.883, 445.616, 595.132, 504.729, 222.086, 289.783, 394.322, 132.189, 82.545, 571.441, 49.311, 399.191, 85.079, 501.823, 773.825, 130.375, 134.871, 559.296, 487.861, 652.248, 196.099, 615.997, 735.668, 246.246, 71.644, 776.772, 323.412, 924.138, 89.595, 671.748, 423.541, 348.308, 320.738, 593.877, 24.207, 304.819, 987.652, 616.221, 990.159, 442.210, 145.818, 44.878, 818.172, 199.685, 373.821, 757.734, 852.764,), (112.372, 54.538, 948.941, 926.730, 868.752, 820.134, 13.733, 693.795, 111.278, 450.062, 22.748, 209.010, 538.005, 203.801, 523.266, 258.658, 483.026, 729.924, 141.347, 698.755, 18.389, 583.005, 663.528, 43.482, 170.320, 284.014, 789.188, 617.965, 53.085, 654.758, 8.334, 388.645, 271.310, 852.083, 660.100, 864.282, 19.078, 867.410, 649.411, 231.169, 380.701, 976.612, 99.604, 315.456, 866.773, 531.561, 186.417, 500.651, 457.986, 926.350,), (21.488, 247.392, 529.415, 333.570, 393.400, 157.005, 346.780, 351.913, 625.231, 236.205, 978.244, 501.251, 811.893, 625.792, 878.677, 890.402, 814.859, 29.466, 554.939, 280.194, 151.708, 897.175, 656.932, 87.795, 382.207, 960.606, 612.448, 625.525, 227.428, 240.361, 152.749, 970.537, 909.735, 329.285, 542.234, 206.757, 138.577, 541.354, 800.105, 862.588, 308.998, 705.136, 523.805, 135.252, 995.686, 975.777, 145.152, 933.030, 917.112, 317.496,), (557.340, 948.602, 118.387, 317.598, 879.638, 727.080, 765.435, 880.132, 414.040, 411.252, 443.004, 933.769, 894.130, 933.250, 273.796, 779.108, 106.771, 184.746, 762.447, 611.982, 266.863, 567.043, 230.911, 232.183, 687.378, 359.261, 688.141, 476.602, 502.325, 604.712, 712.019, 373.959, 852.129, 491.446, 137.513, 193.260, 32.230, 764.539, 15.014, 269.859, 413.044, 742.367, 988.243, 757.777, 66.136, 927.103, 985.628, 867.129, 489.943, 324.896,), (457.545, 246.784, 404.867, 41.826, 735.332, 380.374, 312.897, 611.505, 742.468, 593.806, 525.231, 875.783, 786.920, 520.649, 451.610, 827.010, 42.416, 995.836, 518.703, 395.611, 735.170, 557.701, 516.127, 630.649, 49.295, 291.179, 398.040, 304.554, 827.621, 461.347, 422.462, 613.134, 54.547, 516.970, 142.240, 829.894, 451.698, 722.647, 113.172, 778.731, 937.842, 696.114, 135.307, 413.559, 450.886, 178.879, 590.315, 712.634, 201.932, 454.683,), (250.082, 691.717, 907.162, 796.486, 718.375, 123.544, 114.165, 449.398, 362.943, 523.817, 384.087, 791.067, 511.666, 949.754, 378.679, 380.607, 768.260, 912.253, 565.492, 659.530, 150.090, 868.819, 178.867, 712.047, 419.582, 309.518, 768.630, 446.258, 626.453, 117.554, 131.571, 202.852, 622.560, 253.111, 458.643, 855.411, 543.501, 5.751, 882.703, 237.878, 588.862, 475.712, 410.151, 79.405, 600.102, 244.713, 547.342, 619.766, 557.549, 825.253,), (48.919, 148.256, 653.277, 36.721, 853.829, 666.997, 838.014, 298.442, 920.385, 49.031, 416.957, 178.248, 672.090, 611.090, 691.514, 594.969, 787.308, 177.419, 455.338, 578.966, 931.480, 93.126, 299.294, 368.569, 378.939, 67.667, 427.324, 550.471, 292.833, 134.546, 694.619, 274.419, 527.053, 524.646, 695.829, 612.054, 109.302, 729.729, 628.979, 987.189, 483.663, 688.654, 933.892, 986.278, 287.187, 608.845, 316.489, 525.391, 995.024, 353.853,)))" + ] + }, + "execution_count": 195, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 196, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Matrix((639.4, ...), ..., (..., 353.9))[100x50]\n" + ] + } + ], + "source": [ + "print(m)" + ] + }, + { + "cell_type": "code", + "execution_count": 197, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "v = Vector(1000 * random.random() for _ in range(50))" + ] + }, + { + "cell_type": "code", + "execution_count": 198, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((129.713, 562.634, 519.706, 631.858, 492.504, 179.907, 609.406, 708.587, 979.258, 1.581, 23.987, 625.461, 117.926, 848.070, 799.564, 998.987, 414.041, 333.792, 560.416, 637.504, 11.297, 201.187, 281.627, 790.196, 307.773, 506.690, 323.924, 6.131, 685.836, 341.362, 724.397, 615.993, 29.117, 175.629, 330.515, 337.937, 672.473, 916.163, 797.254, 645.652, 481.496, 627.200, 892.058, 536.968, 335.110, 783.989, 413.953, 742.585, 835.106, 299.344))" + ] + }, + "execution_count": 198, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "v" + ] + }, + { + "cell_type": "code", + "execution_count": 199, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vector(129.7, ..., 299.3)[50]\n" + ] + } + ], + "source": [ + "print(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The arithmetic works as before." + ] + }, + { + "cell_type": "code", + "execution_count": 200, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "w = m * v" + ] + }, + { + "cell_type": "code", + "execution_count": 201, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vector(11378937.3, ..., 13593029.3)[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": 202, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "n = m * m.transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 203, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Matrix((14370711.3, ...), ..., (..., 16545418.2))[100x100]\n" + ] + } + ], + "source": [ + "print(n)" + ] + }, + { + "cell_type": "code", + "execution_count": 204, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "o = m.transpose() * m" + ] + }, + { + "cell_type": "code", + "execution_count": 205, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Matrix((32618511.5, ...), ..., (..., 32339164.8))[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{v}$ or a matrix $\\bf{X}$. Then, we built up two custom data types `Vector` and `Matrix` that wrap a simple `list` object for $\\vec{v}$ and a `list` of `list`s for $\\bf{X}$ so that we can interact with their `_entries` in a \"natural\" (i.e., similar to how we write linear algebra tasks by hand) *and* Pythonic (i.e., how we typically interact with objects in Python) way. By doing this, we extend Python with our own little \"dialect\" or [DSL ](https://en.wikipedia.org/wiki/Domain-specific_language).\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 take a quick look at [numpy](https://www.numpy.org/) and compare it with our DSL using the example from the top." + ] + }, + { + "cell_type": "code", + "execution_count": 206, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "y = (1, 2, 3)\n", + "X = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]" + ] + }, + { + "cell_type": "code", + "execution_count": 207, + "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[0mX\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0my\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": [ + "X * y" + ] + }, + { + "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** 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": 208, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 209, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "y_arr = np.array(y)\n", + "X_arr = np.array(X)" + ] + }, + { + "cell_type": "code", + "execution_count": 210, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "y_vec = Vector(y)\n", + "X_mat = Matrix(X)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The text representations are very similar. However, the [numpy](https://www.numpy.org/) arrays keep the entries as `int`s." + ] + }, + { + "cell_type": "code", + "execution_count": 211, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1, 2, 3])" + ] + }, + "execution_count": 211, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_arr" + ] + }, + { + "cell_type": "code", + "execution_count": 212, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((1.000, 2.000, 3.000))" + ] + }, + "execution_count": 212, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_vec" + ] + }, + { + "cell_type": "code", + "execution_count": 213, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 8, 9]])" + ] + }, + "execution_count": 213, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_arr" + ] + }, + { + "cell_type": "code", + "execution_count": 214, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 2.000, 3.000,), (4.000, 5.000, 6.000,), (7.000, 8.000, 9.000,)))" + ] + }, + "execution_count": 214, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_mat" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "[numpy](https://www.numpy.org/) arrays come with a `shape` instance attribute that returns a `tuple` with the dimensions." + ] + }, + { + "cell_type": "code", + "execution_count": 215, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3,)" + ] + }, + "execution_count": 215, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_arr.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 216, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 3)" + ] + }, + "execution_count": 216, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_arr.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 217, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "(3, 3)" + ] + }, + "execution_count": 217, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_mat.n_rows, X_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 the array but the number of the rows instead. This is equivalent to the first element in the `shape` attribute." + ] + }, + { + "cell_type": "code", + "execution_count": 218, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 218, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(y_arr)" + ] + }, + { + "cell_type": "code", + "execution_count": 219, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 219, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(y_vec)" + ] + }, + { + "cell_type": "code", + "execution_count": 220, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "execution_count": 220, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(X_arr)" + ] + }, + { + "cell_type": "code", + "execution_count": 221, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "9" + ] + }, + "execution_count": 221, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(X_mat)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "The `transpose()` method also exists for arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 222, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1, 4, 7],\n", + " [2, 5, 8],\n", + " [3, 6, 9]])" + ] + }, + "execution_count": 222, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_arr.transpose()" + ] + }, + { + "cell_type": "code", + "execution_count": 223, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Matrix(((1.000, 4.000, 7.000,), (2.000, 5.000, 8.000,), (3.000, 6.000, 9.000,)))" + ] + }, + "execution_count": 223, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_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 on arrays, an entry-wise multiplication is performed." + ] + }, + { + "cell_type": "code", + "execution_count": 224, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([14, 32, 50])" + ] + }, + "execution_count": 224, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_arr.dot(y_arr)" + ] + }, + { + "cell_type": "code", + "execution_count": 225, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[ 1, 4, 9],\n", + " [ 4, 10, 18],\n", + " [ 7, 16, 27]])" + ] + }, + "execution_count": 225, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_arr * y_arr" + ] + }, + { + "cell_type": "code", + "execution_count": 226, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((14.000, 32.000, 50.000))" + ] + }, + "execution_count": 226, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "X_mat * y_vec" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Scalar multiplication, however, works as expected." + ] + }, + { + "cell_type": "code", + "execution_count": 227, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([10, 20, 30])" + ] + }, + "execution_count": 227, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 * y_arr" + ] + }, + { + "cell_type": "code", + "execution_count": 228, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Vector((10.000, 20.000, 30.000))" + ] + }, + "execution_count": 228, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "10 * y_vec" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "Because we implemented our classes to support the sequence protocol, numpy's one-dimensional arrays are actually able to work with them. Note that the `*` operator is applied on a per-entry basis." + ] + }, + { + "cell_type": "code", + "execution_count": 229, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([2., 4., 6.])" + ] + }, + "execution_count": 229, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_arr + y_vec" + ] + }, + { + "cell_type": "code", + "execution_count": 230, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "array([1., 4., 9.])" + ] + }, + "execution_count": 230, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "y_arr * y_vec" + ] + }, + { + "cell_type": "code", + "execution_count": 231, + "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[0mX_arr\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mX_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": [ + "X_arr + X_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." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "## Further Resources" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "source": [ + "A lecture-style **video presentation** of this chapter is integrated below (cf., the [video ](https://www.youtube.com/watch?v=ibDT0uOAOTI&list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f) or the entire [playlist ](https://www.youtube.com/playlist?list=PL-2JV1G3J10lQ2xokyQowcRJI5jjNfW7f))." + ] + }, + { + "cell_type": "code", + "execution_count": 232, + "metadata": { + "slideshow": { + "slide_type": "skip" + } + }, + "outputs": [ + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChALCAgOCggIDRYNDhERExMTCAsWGBYSGBASExIBBQUFCAcIDwkJDxgVERUWFxcYExMYGBgVFRgWFRYWGBcVGxIaEhMXFRoYGBISFRcVFRUVFRUVFRUVGBUSFxIVFf/AABEIAWgB4AMBIgACEQEDEQH/xAAdAAEAAgIDAQEAAAAAAAAAAAAABgcFCAIDBAEJ/8QAURAAAQQBAgMBCQgRAgUEAgMAAQACAwQRBRIGEyExBxQYIjJBVZTVCBUXUVJhk9QjMzVCU1RxcnN0dYGSsbKz05G0FiQ2YrU0gqGiY3YlQ0X/xAAcAQEAAgMBAQEAAAAAAAAAAAAAAgQBAwUHBgj/xAA9EQABAwIDBQUGBAYBBQEAAAABAAIRAyEEEjEFQVFhcRMVU4GRBiIyocHwFjRysRQ1QlLR4fEjM0NigpL/2gAMAwEAAhEDEQA/ANMkREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREREWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXFX4/w/wCtaj7OTwKuKvx/h/1rUfZyItZkWzPgVcVfj/D/AK1qPs5PAq4q/H+H/WtR9nIi1mRbM+BVxV+P8P8ArWo+zk8Crir8f4f9a1H2ciLWZFsz4FXF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hodQ0EBkkco21dQ8qJ4e0HOodmWhZBgrB0X6DItAPDV4q9H8P+q6j7RTw1eKvR/D/quo+0VhZW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/6LQDw1eKvR/D/AKrqPtFPDV4q9H8P+q6j7RRFv+i0A8NXir0fw/6rqPtFPDV4q9H8P+q6j7RRFv8AotAPDV4q9H8P+q6j7RTw1eKvR/D/AKrqPtFEW/6LQDw1eKvR/D/quo+0U8NXir0fw/6rqPtFEW/682q/aJ/0Mn9DloT4avFXo/h/1XUfaK4WPdo8UvY9hoaBh7XNOKuo5w4EHGdR7eqItaURERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERf/2Q==\n", + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 232, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import YouTubeVideo\n", + "YouTubeVideo(\"ibDT0uOAOTI\", width=\"60%\")" + ] + } + ], + "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.7.4" + }, + "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 +}