intro-to-python/11_classes/03_content.ipynb
2020-10-28 17:20:55 +01:00

2412 lines
104 KiB
Text

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"**Note**: Click on \"*Kernel*\" > \"*Restart Kernel and Clear All Outputs*\" in [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) *before* reading this notebook to reset its output. If you cannot run this file on your machine, you may want to open it [in the cloud <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_mb.png\">](https://mybinder.org/v2/gh/webartifex/intro-to-python/develop?urlpath=lab/tree/11_classes/03_content.ipynb)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"# Chapter 11: Classes & Instances (continued)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The implementations of our `Vector` and `Matrix` classes so far do not know any of the rules of [linear algebra <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Linear_algebra). In this third part of the chapter, we add a couple of typical vector and matrix operations, for example, vector addition or matrix-vector multiplication, and some others. Before we do so, we briefly talk about how we can often model the *same* underlying data with a *different* data type."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Representations of Data"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": [
"> \"If you change the way you look at things, the things you look at change.\"\n",
"> -- philosopher and personal coach [Dr. Wayne Dyer <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Wayne_Dyer)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Sometimes, it is helpful to view a `Vector` as a `Matrix` with either one row or one column. On the contrary, such a `Matrix` can always be interpreted as a `Vector` again. Changing the representation of the same underlying data (i.e., the `_entries`) can be viewed as \"changing\" an object's data type, for which, however, there is no built-in syntax.\n",
"\n",
"Thus, we implement the `.as_matrix()` and `.as_vector()` methods below that create *new* `Matrix` or `Vector` instances out of existing `Vector` or `Matrix` instances, respectively. Internally, both methods rely on the sequence protocol again (i.e., `for x in self`). Also, `.as_matrix()` interprets the `Vector` instance as a column vector by default (i.e., the `column=True` flag)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Vector:\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(float(x) for x in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(repr(x) for x in self)\n",
" return f\"Vector(({args}))\"\n",
"\n",
" def __iter__(self):\n",
" return iter(self._entries)\n",
"\n",
" def as_matrix(self, *, column=True):\n",
" if column:\n",
" return Matrix([x] for x in self)\n",
" return Matrix([(x for x in self)])"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Matrix:\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(tuple(float(x) for x in r) for r in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
" return f\"Matrix(({args}))\"\n",
"\n",
" @property\n",
" def n_rows(self):\n",
" return len(self._entries)\n",
"\n",
" @property\n",
" def n_cols(self):\n",
" return len(self._entries[0])\n",
"\n",
" def __iter__(self): # adapted for brevity; uses parts of .entries()\n",
" return (self._entries[r][c] for r in range(self.n_rows) for c in range(self.n_cols))\n",
"\n",
" def as_vector(self):\n",
" if not (self.n_rows == 1 or self.n_cols == 1):\n",
" raise RuntimeError(\"one dimension (m or n) must be 1\")\n",
" return Vector(x for x in self)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((1.0, 2.0, 3.0))"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Let's interpret `v` as a column vector and create a matrix with dimension $3 \\times 1$."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"m = v.as_matrix()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((1.0,), (2.0,), (3.0,)))"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(3, 1)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m.n_rows, m.n_cols"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"By chaining `.as_matrix()` and `.as_vector()` we get a *new* `Vector` instance back that is equivalent to the given `v`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((1.0, 2.0, 3.0))"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.as_matrix().as_vector()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In the same way, we can also interpret `v` as a row vector and create a $1 \\times 3$ matrix."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"m = v.as_matrix(column=False)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((1.0, 2.0, 3.0,)))"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"(1, 3)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m.n_rows, m.n_cols"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((1.0, 2.0, 3.0))"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v.as_matrix(column=False).as_vector()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Interpreting a matrix as a vector only works if one of the two dimensions, $m$ or $n$, is $1$. If this requirement is not satisfied, we get the `RuntimeError` raised in `.as_vector()` above."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "RuntimeError",
"evalue": "one dimension (m or n) must be 1",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-14-625d8ddab141>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-2-3a6f1abfe70b>\u001b[0m in \u001b[0;36mas_vector\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mas_vector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 23\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mor\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 24\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"one dimension (m or n) must be 1\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 25\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mRuntimeError\u001b[0m: one dimension (m or n) must be 1"
]
}
],
"source": [
"m.as_vector()"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Operator Overloading"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"By implementing special methods such as `.__add__()`, `.__sub__()`, `.__mul__()`, and some others, we can make user-defined data types emulate how numeric types operate with each other (cf., [reference <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](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 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Operator_overloading)** as first mentioned in the context of `str` concatenation in [Chapter 1 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/01_elements/00_content.ipynb#Operator-Overloading)."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Arithmetic Operators"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To understand the protocol behind arithmetic operators, we first look at the simple case of how an `int` object and a `float` object are added. The expression `1 + 2.0` is \"translated\" by Python into a method invocation of the form `1.__add__(2.0)`. This is why all the special methods behind binary operators take two arguments, `self` and, by convention, `other`. To allow binary operators to work with objects of *different* data types, Python expects the `.__add__()` method on the `1` object to return `NotImplemented` if it does not know how to deal with the `2.0` object and then proceeds by invoking the *reverse* special method `2.0.__radd__(1)`. With this protocol, one can create *new* data types that know how to execute arithmetic operations with *existing* data types *without* having to change the latter. By convention, the result of a binary operation should always be a *new* instance object and *not* a mutation of an existing one."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"3.0"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1 + 2.0"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Before implementing the arithmetic operators, we must first determine what other data types are allowed to interact with our `Vector` and `Matrix` instances and also how the two interact with each other. Conceptually, this is the same as to ask how strict we want the rules from linear algebra to be enforced in our model world. For example, while it is obvious that two vectors with the same number of entries may be added or subtracted, we could also allow a scalar value to be added to a vector. That seems awkward at first because it is an illegal operation in linear algebra. However, for convenience in our programs, we could interpret any scalar as a \"constants\" vector of the \"right size\" and add it to each entry in a `Vector`. This idea can be generalized into what is called **[broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html)** in [numpy](https://docs.scipy.org/doc/numpy/index.html). We often see \"dirty hacks\" like this in code. They are no bugs but features supposed to make the user of a library more productive.\n",
"\n",
"In this chapter, we model the following binary arithmetic operations:"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"- **Addition** / **Subtraction**\n",
" - `Vector` with `Vector` (if number of entries match; commutative)\n",
" - `Matrix` with `Matrix` (if dimensions $m$ and $n$ match; commutative)\n",
" - `Matrix` / `Vector` with scalar (the scalar is broadcasted; non-commutative for subtraction)\n",
"- **Multiplication**\n",
" - `Vector` with `Vector` ([dot product <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](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 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Scalar_multiplication); commutative)\n",
" - `Matrix` with `Matrix` ([matrix-matrix multiplication <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](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 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](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 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](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() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance) function in most of the methods below. We use it to check if the `other` argument passed in is a `Vector` or `Matrix` instance or a scalar."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(m, Vector)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(v, Vector)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To check if `other` is a scalar, we need to specify what data type constitutes a scalar. We use a goose typing strategy as explained in [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/02_content.ipynb#Goose-Typing): Any object that behaves like a `numbers.Number` from the [numbers <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/numbers.html) module in the [standard library <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](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() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#isinstance) also confirms that it is a `numbers.Number` in the abstract sense."
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(1, int)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"import numbers"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"isinstance(1, numbers.Number)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Now with all the preparation work done, let's look at a \"minimal\" implementation of `Vector` that supports all the arithmetic operations specified above. *None* of the special methods inside the `Vector` class is aware that the `Matrix` class exists! Thus, all operations involving at least one `Matrix` instance are implemented only in the `Matrix` class."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"code_folding": [],
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Vector:\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(float(x) for x in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(repr(x) for x in self)\n",
" return f\"Vector(({args}))\"\n",
"\n",
" def __len__(self):\n",
" return len(self._entries)\n",
"\n",
" def __iter__(self):\n",
" return iter(self._entries)\n",
"\n",
" def __add__(self, other):\n",
" if isinstance(other, Vector): # vector addition\n",
" if len(self) != len(other):\n",
" raise ValueError(\"vectors must be of the same length\")\n",
" return Vector(x + y for (x, y) in zip(self, other))\n",
" elif isinstance(other, numbers.Number): # broadcasting addition\n",
" return Vector(x + other for x in self)\n",
" return NotImplemented\n",
"\n",
" def __radd__(self, other):\n",
" return self + other\n",
"\n",
" def __sub__(self, other):\n",
" if isinstance(other, Vector): # vector subtraction\n",
" if len(self) != len(other):\n",
" raise ValueError(\"vectors must be of the same length\")\n",
" return Vector(x - y for (x, y) in zip(self, other))\n",
" elif isinstance(other, numbers.Number): # broadcasting subtraction\n",
" return Vector(x - other for x in self)\n",
" return NotImplemented\n",
"\n",
" def __rsub__(self, other):\n",
" # Reverse vector subtraction is already handled in __sub__().\n",
" if isinstance(other, numbers.Number): # broadcasting subtraction\n",
" return Vector(other - x for x in self)\n",
" return NotImplemented\n",
"\n",
" def __mul__(self, other):\n",
" if isinstance(other, Vector): # dot product\n",
" if len(self) != len(other):\n",
" raise ValueError(\"vectors must be of the same length\")\n",
" return sum(x * y for (x, y) in zip(self, other))\n",
" elif isinstance(other, numbers.Number): # scalar multiplication\n",
" return Vector(x * other for x in self)\n",
" return NotImplemented\n",
"\n",
" def __rmul__(self, other):\n",
" return self * other\n",
"\n",
" def __truediv__(self, other):\n",
" if isinstance(other, numbers.Number):\n",
" return self * (1 / other)\n",
" return NotImplemented\n",
"\n",
" def as_matrix(self, *, column=True):\n",
" if column:\n",
" return Matrix([x] for x in self)\n",
" return Matrix([(x for x in self)])"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [],
"source": [
"w = Vector([4, 5])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`.__mul__()` implements both scalar multiplication and the dot product of two `Vector`s. As both operations are commutative, `.__rmul__()` dispatches to `.__mul__()` via the `self * other` expression."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((2.0, 4.0, 6.0))"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2 * v"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((3.0, 6.0, 9.0))"
]
},
"execution_count": 25,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v * 3"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"14.0"
]
},
"execution_count": 26,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v * v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"If two `Vector`s do *not* have a matching number of entries, a `ValueError` is raised."
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "vectors must be of the same length",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-27-21fab2d5f12e>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-21-e7b5dd8e219a>\u001b[0m in \u001b[0;36m__mul__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 45\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# dot product\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 46\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 47\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors must be of the same length\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 48\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0msum\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0my\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 49\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# scalar multiplication\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mValueError\u001b[0m: vectors must be of the same length"
]
}
],
"source": [
"v * w"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`.__truediv__()` implements the ordinary division operator `/` while `.__floordiv__()` would implement the integer division operator `//`. Here, `.__truediv__()` dispatches to `.__mul__()` after inverting the `other` argument via the `self * (1 / other)` expression."
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((0.3333333333333333, 0.6666666666666666, 1.0))"
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v / 3"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"`.__add__()` and `.__sub__()` implement vector addition and subtraction according to standard linear algebra rules, meaning that both `Vector`s must have the same number of entries or a `ValueError` is raised. Furthermore, both methods are able to broadcast the `other` argument to the dimension of a `Vector` and then execute either vector addition or subtraction. As addition is commutative, `.__radd__()` dispatches to `.__add__()`. For now, we have to explicitly implement `.__rsub__()`. Further below, we see how it can be re-factored to be commutative."
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((2.0, 4.0, 6.0))"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v + v"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((0.0, 0.0, 0.0))"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v - v"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "vectors must be of the same length",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-31-490ee3f2b9e8>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-21-e7b5dd8e219a>\u001b[0m in \u001b[0;36m__add__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 18\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__class__\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# vector addition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 20\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors must be of the same length\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 21\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0my\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 22\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mnumbers\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;31m# broadcasting addition\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mValueError\u001b[0m: vectors must be of the same length"
]
}
],
"source": [
"v + w"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((101.0, 102.0, 103.0))"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v + 100"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((99.0, 98.0, 97.0))"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"100 - v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For `Matrix` instances, the implementation is a bit more involved as we need to distinguish between matrix-matrix, matrix-vector, vector-matrix, and scalar multiplication and check for compatible dimensions. To review the underlying rules, check this [article <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Matrix_multiplication) or watch the video below."
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAUDBAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChALCAgOCggIDRUNDhERExMTCAsWGBYSGBASExIBBQUFCAcIDwkJDhIODw8SEhIVEhISFRISEhISEhIVEhISFhISEhISEhISEhISEhISEhISEhISEhISEhISEhIeEv/AABEIAWgB4AMBIgACEQEDEQH/xAAdAAEAAgIDAQEAAAAAAAAAAAAABQgGBwEDBAIJ/8QAURAAAQQBAgMCCQcIBggGAgMAAQACAwQFBhESEyEHMQgUFSI0QVF0swkyVGFxktQjNkJSdYGRtCQ1cqGxwRY3Q2J2grK1M1Njc4PRRKIXJWT/xAAbAQEBAQEBAQEBAAAAAAAAAAAAAgMBBAYHBf/EADMRAQACAQMBBQYEBgMAAAAAAAABEQIDITESBEFRYXEFMoGRsfAGE9HxFCIjQsHhM1Jy/9oADAMBAAIRAxEAPwCmSIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICKXt4ZkUkkT71QPje6NwDbxHExxa7Yir1G4K6vJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VPJ0P0+p9y9+FQRqKS8nQ/T6n3L34VdtTDMlkjiZeqF8j2xtBbeA4nuDW7k1eg3IQdWqPTrvvdn4z1GqS1R6dd97s/Geo1AREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQFJaX9Ope91vjMUapLS/p1L3ut8ZiBqj06773Z+M9RqktUenXfe7PxnqNQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBSWl/TqXvdb4zFGqS0v6dS97rfGYgao9Ou+92fjPUapLVHp133uz8Z6jUBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRqktL+nUve63xmIGqPTrvvdn4z1GqS1R6dd97s/Geo1AREQEREBERAREQEREBERBy0E9y7Z6ksY3fHIwb7bvY5o39gJHepDRx//saHvtX48a/VztR0dVz+Ju4m2PydyEsa/YOdBMNnQWGD9eOQMd9fDsehKD8jUUrq3AWcXet464wx2qc8tednXbjicW8TCQOOJw2c13c5rmkdCvPgcVYvWq9OrG6azamjrwRN24pJZXhkbBv0G7iOp6DvQdENSV+/BG94HQljHOAPsJaOhXU4EEgggg7EHoQR3gj2r9Z+xrQ1fTeFpYivwuNePexM0bGzbk8+zYO/XZ0hdsCT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uDm9CNd9ofbzPb1fV1Xiq7qctavXhFew4StlayORliKXl7cUMjZXs6bEDYjY7baURBbzJeFNpmR7cu3SLHakZG3l2pxUfHFM1vAx/jgbz3tYOgPLa7YbAt7xqjth7cp8vqPF6lx0L8fcxtKpCGvc2RpsQTWZpSAO+u/xlzOE9S3ffvWmkQW4n8JnSWQMORzOjmWc3CxoEzWU54XOi6xnnz7SBgPUB7Hlm/Qnbc6u7Ou2etjdZWdUSYpsUFnxvfHUHhrYTZjDd2PlGznFwL3HZoLpHEBo2aNMIg2/gO1yvW12/Vzqczq7rt2z4mJGc7htVJ6zW8wjg4gZg4/2SFzle12vNrxurhTmFYXK1nxMyM5/DBUiqlvMA4OImMu/eAtPog2x2r64l1bq+DI4mCWpZtz4yrRjkkY6VtthhhgfxNHCN5uEjvVlPlB9eOp4ejgGSDxnKPbYu8HQeKVHNcBw77tbLa4SO/pWkCp92Sa0dp7L1cuypBclp810UNhz2x8ySJ8Qk3jIPE3jJH1gH1Ar0dsvaHc1RlpstcayJ744YYoIi4xV4YWcLY4y/wA7YvMkh3/Sld9iDDEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRqktL+nUve63xmIGqPTrvvdn4z1Grvv2XTSyzOADpZHyODdw0OkcXENBJO259q6EBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAUlpf06l73W+MxRq76Fl0MsUzQC6KRkjQ7ctLo3BwDgCDtuPag6EREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQf/2Q==\n",
"text/html": [
"\n",
" <iframe\n",
" width=\"60%\"\n",
" height=\"300\"\n",
" src=\"https://www.youtube.com/embed/OMA2Mwo0aZg\"\n",
" frameborder=\"0\"\n",
" allowfullscreen\n",
" ></iframe>\n",
" "
],
"text/plain": [
"<IPython.lib.display.YouTubeVideo at 0x7fa62c6bfa90>"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import YouTubeVideo\n",
"YouTubeVideo(\"OMA2Mwo0aZg\", width=\"60%\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"To summarize the video, the multiplication of two matrices $\\bf{A}$ and $\\bf{B}$ with dimensions $m$ by $n$ and $n$ by $p$ yields a matrix $\\bf{C}$ with dimensions $m$ and $p$. To obtain an entry $c_{ij}$ of matrix $\\bf{C}$ where $i$ and $j$ represent the index labels of the rows and columns, we have to calculate the dot product of the $i$th row vector of $\\bf{A}$ with the $j$th column vector of $\\bf{B}$. So, it makes a difference if we multiply $\\bf{A}$ with $\\bf{B}$ from the right or left.\n",
"\n",
"When multiplying a `Matrix` with a `Vector`, we follow the convention that a `Vector` on the left is interpreted as a row vector and a `Vector` on the right as a column vector. The `Vector`'s length must match the `Matrix`'s corresponding dimension. As row and column vectors can be viewed as a `Matrix` as well, matrix-vector multiplication is a special case of matrix-matrix multiplication."
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"In the revised `Matrix` class, the `.__add__()`, `.__radd__()`, `.__sub__()`, `.__rsub__()`, and `.__truediv__()` methods follow the same logic as in the `Vector` class above.\n",
"\n",
"Besides implementing scalar multiplication, `.__mul__()` and `.__rmul__()` are responsible for converting `Vector`s into `Matrix`s and back. In particular, all the different ways of performing a multiplication are reduced into *one* generic form, which is a matrix-matrix multiplication. That is achieved by the `._matrix_multiply()` method, another implementation detail."
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Matrix:\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(tuple(float(x) for x in r) for r in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(\"(\" + \", \".join(repr(c) for c in r) + \",)\" for r in self._entries)\n",
" return f\"Matrix(({args}))\"\n",
"\n",
" @property\n",
" def n_rows(self):\n",
" return len(self._entries)\n",
"\n",
" @property\n",
" def n_cols(self):\n",
" return len(self._entries[0])\n",
"\n",
" def rows(self):\n",
" return (Vector(r) for r in self._entries)\n",
"\n",
" def cols(self):\n",
" return (\n",
" Vector(self._entries[r][c] for r in range(self.n_rows)) for c in range(self.n_cols)\n",
" )\n",
"\n",
" def __iter__(self): # adapted for brevity; uses parts of entries()\n",
" return (self._entries[r][c] for r in range(self.n_rows) for c in range(self.n_cols))\n",
"\n",
" def __add__(self, other):\n",
" if isinstance(other, Matrix): # matrix addition\n",
" if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n",
" raise ValueError(\"matrices must have the same dimensions\")\n",
" return Matrix((s_col + o_col for (s_col, o_col) in zip(s_row, o_row))\n",
" for (s_row, o_row) in zip(self._entries, other._entries))\n",
" elif isinstance(other, numbers.Number): # broadcasting addition\n",
" return Matrix((c + other for c in r) for r in self._entries)\n",
" return NotImplemented\n",
"\n",
" def __radd__(self, other):\n",
" if isinstance(other, Vector):\n",
" raise TypeError(\"vectors and matrices cannot be added\")\n",
" return self + other\n",
"\n",
" def __sub__(self, other):\n",
" if isinstance(other, Matrix): # matrix subtraction\n",
" if (self.n_rows != other.n_rows) or (self.n_cols != other.n_cols):\n",
" raise ValueError(\"matrices must have the same dimensions\")\n",
" return Matrix((s_col - o_col for (s_col, o_col) in zip(s_row, o_row))\n",
" for (s_row, o_row) in zip(self._entries, other._entries))\n",
" elif isinstance(other, numbers.Number): # broadcasting subtraction\n",
" return Matrix((c - other for c in r) for r in self._entries)\n",
" return NotImplemented\n",
"\n",
" def __rsub__(self, other):\n",
" if isinstance(other, Vector):\n",
" raise TypeError(\"vectors and matrices cannot be subtracted\")\n",
" # Reverse matrix subtraction is already handled in __sub__().\n",
" if isinstance(other, numbers.Number): # broadcasting subtraction\n",
" return Matrix((other - c for c in r) for r in self._entries)\n",
" return NotImplemented\n",
" \n",
" def _matrix_multiply(self, other):\n",
" if self.n_cols != other.n_rows:\n",
" raise ValueError(\"matrices must have compatible dimensions\")\n",
" return Matrix((rv * cv for cv in other.cols()) for rv in self.rows())\n",
"\n",
" def __mul__(self, other):\n",
" if isinstance(other, numbers.Number):\n",
" return Matrix((x * other for x in r) for r in self._entries)\n",
" elif isinstance(other, Vector):\n",
" return self._matrix_multiply(other.as_matrix()).as_vector()\n",
" elif isinstance(other, Matrix):\n",
" return self._matrix_multiply(other)\n",
" return NotImplemented\n",
"\n",
" def __rmul__(self, other):\n",
" if isinstance(other, numbers.Number):\n",
" return self * other\n",
" elif isinstance(other, Vector):\n",
" return other.as_matrix(column=False)._matrix_multiply(self).as_vector()\n",
" return NotImplemented\n",
"\n",
" def __truediv__(self, other):\n",
" if isinstance(other, numbers.Number):\n",
" return self * (1 / other)\n",
" return NotImplemented\n",
"\n",
" def as_vector(self):\n",
" if not (self.n_rows == 1 or self.n_cols == 1):\n",
" raise RuntimeError(\"one dimension (m or n) must be 1\")\n",
" return Vector(x for x in self)"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"m = Matrix([(1, 2, 3), (4, 5, 6), (7, 8, 9)])\n",
"n = Matrix([(10, 11, 12), (13, 14, 15)])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Scalar multiplication, addition, and subtraction work as before."
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((10.0, 20.0, 30.0,), (40.0, 50.0, 60.0,), (70.0, 80.0, 90.0,)))"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"10 * m"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((1.0, 2.0, 3.0,), (4.0, 5.0, 6.0,), (7.0, 8.0, 9.0,)))"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"(2 * m + m * 3) / 5"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((0.0, 0.0, 0.0,), (0.0, 0.0, 0.0,), (0.0, 0.0, 0.0,)))"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m - m"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Matrix-matrix multiplication works if the dimensions are compatible ..."
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((138.0, 171.0, 204.0,), (174.0, 216.0, 258.0,)))"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n * m"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... and results in a `ValueError` otherwise."
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "matrices must have compatible dimensions",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-41-b57a49d097c9>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-35-def82d7b791b>\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<ipython-input-35-def82d7b791b>\u001b[0m in \u001b[0;36m_matrix_multiply\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 65\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"matrices must have compatible dimensions\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 66\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcols\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mValueError\u001b[0m: matrices must have compatible dimensions"
]
}
],
"source": [
"m * n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The same holds for matrix-vector and vector-matrix multiplication. These operations always return `Vector` instances in line with standard linear algebra. If a `Vector`'s length is not compatible with the respective dimension of a `Matrix`, we receive a `ValueError`."
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((14.0, 32.0, 50.0))"
]
},
"execution_count": 42,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m * v"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((30.0, 36.0, 42.0))"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v * m"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((68.0, 86.0))"
]
},
"execution_count": 44,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"n * v"
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"ename": "ValueError",
"evalue": "matrices must have compatible dimensions",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-45-9faf2ee0ae54>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-35-def82d7b791b>\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<ipython-input-35-def82d7b791b>\u001b[0m in \u001b[0;36m_matrix_multiply\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 63\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m_matrix_multiply\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 64\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_cols\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mn_rows\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 65\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"matrices must have compatible dimensions\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 66\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mMatrix\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mrv\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mcv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcols\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mrv\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 67\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mValueError\u001b[0m: matrices must have compatible dimensions"
]
}
],
"source": [
"v * n"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Lastly, the broadcasting addition and subtraction also work for our `Matrix` instances."
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((101.0, 102.0, 103.0,), (104.0, 105.0, 106.0,), (107.0, 108.0, 109.0,)))"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"m + 100"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Matrix(((99.0, 98.0, 97.0,), (96.0, 95.0, 94.0,), (93.0, 92.0, 91.0,)))"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"100 - m"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We do not allow addition or subtraction of matrices with vectors and raise a `TypeError` instead. Alternatively, we could have implemented broadcasting here as well, just like [numpy](https://www.numpy.org/) does."
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "vectors and matrices cannot be added",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-48-356d02c93f11>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-21-12852bd26164>\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<ipython-input-35-f4e0bb37caf6>\u001b[0m in \u001b[0;36m__radd__\u001b[0;34m(self, other)\u001b[0m\n\u001b[1;32m 40\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__radd__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 41\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mother\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mVector\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 42\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vectors and matrices cannot be added\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 43\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mother\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: vectors and matrices cannot be added"
]
}
],
"source": [
"m + v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"### Relational Operators"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"As we have seen before, two different `Vector`s with the same `._entries` do *not* compare equal. The reason is that for user-defined types Python by default only assumes two instances to be equal if they are actually the same object. This is, of course, semantically wrong for our `Vector`s and `Matrix`s."
]
},
{
"cell_type": "code",
"execution_count": 49,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])\n",
"w = Vector([1, 2, 3])"
]
},
{
"cell_type": "code",
"execution_count": 50,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 50,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v == w"
]
},
{
"cell_type": "code",
"execution_count": 51,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v == v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"We implement the `.__eq__()` (cf., [reference <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/reference/datamodel.html#object.__eq__)) method to control how the comparison operator `==` is carried out. For brevity, we show this only for the `Vector` class. The `.__eq__()` method exits early as soon as the first pair of entries does not match. Also, for reasons discussed in [Chapter 5 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/05_numbers/01_content.ipynb#Imprecision), we compare the absolute difference of two corresponding entries to a very small `zero_threshold` that is stored as a class attribute shared among all `Vector` instances. If the `Vector`s differ in their numbers of entries, we fail loudly and raise a `ValueError`."
]
},
{
"cell_type": "code",
"execution_count": 52,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Vector:\n",
"\n",
" zero_threshold = 1e-12\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(float(x) for x in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(repr(x) for x in self)\n",
" return f\"Vector(({args}))\"\n",
"\n",
" def __len__(self):\n",
" return len(self._entries)\n",
"\n",
" def __iter__(self):\n",
" return iter(self._entries)\n",
"\n",
" def __eq__(self, other):\n",
" if isinstance(other, Vector):\n",
" if len(self) != len(other):\n",
" raise ValueError(\"vectors must be of the same length\")\n",
" for x, y in zip(self, other):\n",
" if abs(x - y) > self.zero_threshold:\n",
" return False # exit early if two corresponding entries differ\n",
" return True\n",
" return NotImplemented"
]
},
{
"cell_type": "code",
"execution_count": 53,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])\n",
"w = Vector([1, 2, 3])"
]
},
{
"cell_type": "code",
"execution_count": 54,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 54,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v == w"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Besides `.__eq__()`, there are other special methods to implement the `!=`, `<`, `>`, `<=`, and `>=` operators, the last four of which are not semantically meaningful in the context of linear algebra.\n",
"\n",
"Python implicitly creates the `!=` for us in that it just inverts the result of `.__eq__()`."
]
},
{
"cell_type": "code",
"execution_count": 55,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])\n",
"w = Vector([1, 2, 4])"
]
},
{
"cell_type": "code",
"execution_count": 56,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 56,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v == w"
]
},
{
"cell_type": "code",
"execution_count": 57,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 57,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v != w"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": [
"## Number Emulation"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Our `Vector` and `Matrix` classes do not fully behave like a `numbers.Number` in the abstract sense. Besides the not yet talked about but useful unary `+` and `-` operators, numbers in Python usually support being passed to built-in functions like [abs() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#abs), [bin() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#bin), [bool() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#bool), [complex() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#complex), [float() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#float), [hex() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#hex), [int() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#int), and others (cf., the [reference <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types) for an exhaustive list).\n",
"\n",
"To see that our classes lack simple but expected behaviors, let's try to invert the signs of all entries in the vector `v` ..."
]
},
{
"cell_type": "code",
"execution_count": 58,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((1.0, 2.0, 3.0))"
]
},
"execution_count": 58,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"v"
]
},
{
"cell_type": "code",
"execution_count": 59,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "bad operand type for unary -: 'Vector'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-59-c01df863c5f8>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m-\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: bad operand type for unary -: 'Vector'"
]
}
],
"source": [
"-v"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"... or pass `v` to [abs() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#abs)."
]
},
{
"cell_type": "code",
"execution_count": 60,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "TypeError",
"evalue": "bad operand type for abs(): 'Vector'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-60-09d4e6f30397>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mabs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mv\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: bad operand type for abs(): 'Vector'"
]
}
],
"source": [
"abs(v)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"For our example, we decide to implement the unary `+` and `-` operators, [abs() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#abs) as the Euclidean / Frobenius norm (i.e., the [magnitude <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_wiki.png\">](https://en.wikipedia.org/wiki/Magnitude_%28mathematics%29#Euclidean_vector_space)), [bool() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#bool) to check if that norm is greater than `0` or not, and [float() <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_py.png\">](https://docs.python.org/3/library/functions.html#float) to obtain a single scalar if the vector or matrix consists of only one entry. To achieve that, we implement the `.__pos__()`, `.__neg__()`, `.__abs__()`, `.__bool__()`, and `.__float__()` methods. For brevity, we do this only for the `Vector` class."
]
},
{
"cell_type": "code",
"execution_count": 61,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"import math"
]
},
{
"cell_type": "code",
"execution_count": 62,
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"source": [
"def norm(vec_or_mat):\n",
" \"\"\"Calculate the Frobenius or Euclidean norm of a matrix or vector.\"\"\"\n",
" return math.sqrt(sum(x ** 2 for x in vec_or_mat))"
]
},
{
"cell_type": "code",
"execution_count": 63,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"class Vector:\n",
"\n",
" def __init__(self, data):\n",
" self._entries = tuple(float(x) for x in data)\n",
" # ...\n",
"\n",
" def __repr__(self):\n",
" args = \", \".join(repr(x) for x in self)\n",
" return f\"Vector(({args}))\"\n",
"\n",
" def __iter__(self):\n",
" return iter(self._entries)\n",
"\n",
" def __len__(self):\n",
" return len(self._entries)\n",
"\n",
" def __pos__(self):\n",
" return self\n",
"\n",
" def __neg__(self):\n",
" return Vector(-x for x in self)\n",
"\n",
" def __abs__(self):\n",
" return norm(self)\n",
"\n",
" def __bool__(self):\n",
" return bool(abs(self))\n",
"\n",
" def __float__(self):\n",
" if len(self) != 1:\n",
" raise RuntimeError(\"vector must have exactly one entry to become a scalar\")\n",
" return self._entries[0]"
]
},
{
"cell_type": "code",
"execution_count": 64,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"v = Vector([1, 2, 3])\n",
"w = Vector([3, 4])"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The unary `+` operator is not only conceptually an identity operator but simply returns the instance itself."
]
},
{
"cell_type": "code",
"execution_count": 65,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((1.0, 2.0, 3.0))"
]
},
"execution_count": 65,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"+v"
]
},
{
"cell_type": "code",
"execution_count": 66,
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 66,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"+v is v"
]
},
{
"cell_type": "code",
"execution_count": 67,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"Vector((-3.0, -4.0))"
]
},
"execution_count": 67,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"-w"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"The magnitude of a `Vector` is only `0` if *all* entries are `0`."
]
},
{
"cell_type": "code",
"execution_count": 68,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"5.0"
]
},
"execution_count": 68,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"abs(w)"
]
},
{
"cell_type": "code",
"execution_count": 69,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"z = Vector([0, 0])"
]
},
{
"cell_type": "code",
"execution_count": 70,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"0.0"
]
},
"execution_count": 70,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"abs(z)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Only an all `0`s `Vector` is `False` in a boolean context. As mentioned in [Chapter 3 <img height=\"12\" style=\"display: inline-block\" src=\"../static/link/to_nb.png\">](https://nbviewer.jupyter.org/github/webartifex/intro-to-python/blob/develop/03_conditionals/00_content.ipynb#Truthy-vs.-Falsy), commonly we view an *empty* sequence as falsy; however, as we do not allow `Vector`s without any entries, we choose the all `0`s alternative. In that regard, the `Vector` class does not behave like the built-in sequence types."
]
},
{
"cell_type": "code",
"execution_count": 71,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 71,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(v)"
]
},
{
"cell_type": "code",
"execution_count": 72,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 72,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"bool(z)"
]
},
{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": [
"Lastly, single entry `Vector`s can be casted as scalars."
]
},
{
"cell_type": "code",
"execution_count": 73,
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"outputs": [],
"source": [
"s = Vector([42])"
]
},
{
"cell_type": "code",
"execution_count": 74,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"data": {
"text/plain": [
"42.0"
]
},
"execution_count": 74,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"float(s)"
]
},
{
"cell_type": "code",
"execution_count": 75,
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"outputs": [
{
"ename": "RuntimeError",
"evalue": "vector must have exactly one entry to become a scalar",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-75-8369cec552f3>\u001b[0m in \u001b[0;36m<module>\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<ipython-input-63-67b90fc89e7c>\u001b[0m in \u001b[0;36m__float__\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0m__float__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 31\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"vector must have exactly one entry to become a scalar\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 32\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_entries\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mRuntimeError\u001b[0m: vector must have exactly one entry to become a scalar"
]
}
],
"source": [
"float(v)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"livereveal": {
"auto_select": "code",
"auto_select_fragment": true,
"scroll": true,
"theme": "serif"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": false,
"sideBar": true,
"skip_h1_title": true,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "384px"
},
"toc_section_display": false,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}