{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "[Back to Overview](overview.ipynb)\n", "\n", "# An Intro to OOP: Ordering Polyhedrons\n", "#### by Kirby Urner\n", "\n", "Lets dive into Object Oriented Programming using paradigm \"math objects\" that are easily accessible because visualizable. \n", "\n", "A lot of us grow up playing with blocks as kids, be these real, such as Legos, or virtual such as in Minecraft. Blocks are polyhedrons and they're not always your classic brick shape. Indeed, in this demo, we'll tend to use tetrahedrons in place of hexahedrons, as our constituitive components.\n", "\n", "In Python, a superclass or parent class called ```Polyhedron``` might implement methods all the subclasses have in common. As a general rule, when linear dimensions change by a scale factor, the corresponding volume of the shape changes as a 3rd power of that scale factor. Our initial modeling will center around this math fact.\n", "\n", "\"Synergetics\n", "\n", "These are not necessarily the volumes you're used to starting with, however the ratios are all correct given the necessary stipulations. In other words, these volume ratios depend on sizing the polyhedrons relative to one another in a specific way, and setting the regular tetrahedron's volume to one.\n", "\n", "\"4fold\"\n", "\n", "The octahedron, tetrahedron and cuboctahedron have the same edge lengths (D). The cube's face diagonals are D, while the rhombic dodecahedron's long face diagonals are likewise D. This letter D stands not just for Diagonal, but for Diameter. Think of four unit radius spheres (R=1) packed into a Tetrahedron of volume 1.\n", "\n", "\"Tetrahedron\"\n", "\n", "\n", "\"Units\n", "\n", "Sketch from my [Martian Math storyboard](https://flic.kr/s/aHsjrM3Fho).\n", "\n", "The R-edged Cube (above) is not the volume 3 cube, but the one we might consider \"unit volume\" in an XYZ system where R is the control length. \n", "\n", "The D-edged Tetrahedron, in contrast, has a slightly smaller volume, $\\sqrt{8/9}$ smaller to be precise. \n", "\n", "We're treating this D-edged Tetrahedron as our alternative unit of volume, and back that up with an alternative model of 3rd powering i.e. one based on a growing / shrinking tetrahedron instead of a cube e.g. $2^{3}$ is a 2-edged tetrahedron of volume 8 and so on.\n", "\n", "For more information on this volumes hierarchy see:\n", "\n", "* [Volumes (1 of 2)](http://grunch.net/synergetics/volumes.html)\n", "\n", "* [Volumes (2 of 2)](http://grunch.net/synergetics/volumes2.html)\n", "\n", "* [Bridging the Chasm...](https://medium.com/@kirbyurner/bridging-the-chasm-new-england-transcendentalism-in-the-1900s-1dfa4c2950d0)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(('tetrahedron', 1), ('octahedron', 4), ('cube', 3), ('cuboctahedron', 20))" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ ">>> polyvols = {\"tetrahedron\":1, \n", " \"octahedron\":4, \n", " \"cube\":3,\n", " \"cuboctahedron\":20}\n", "\n", ">>> vols_tuples = tuple(polyvols.items())\n", ">>> vols_tuples" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default, the ```sorted``` function looks at the leftmost element of a tuple or other iterable, then breaks any ties by using the next element to the right and so on." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('cube', 3), ('cuboctahedron', 20), ('octahedron', 4), ('tetrahedron', 1)]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ ">>> sorted(vols_tuples) # alphabetical, then by volume if tied" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[(1, 2, 4), (1, 3, 4), (2, 1, 2), (2, 1, 3)]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted([(2,1,3), (2,1,2), (1,3,4), (1,2,4)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... however we have the means to override that default.\n", "\n", "Using a lambda expression for the named argument ```key```, we tell Python to fish up element [1] from each element of ```vols_tuples```, meaning the integer volume." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('tetrahedron', 1), ('cube', 3), ('octahedron', 4), ('cuboctahedron', 20)]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ ">>> sorted(vols_tuples, key=lambda t: t[1]) # by volume, get to use lambda" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Python docs suggest another approach: using ```itemgetter``` and/or ```attrgetter``` from the operator module. These take what item or attribute to get, and return a function ready to apply to whatever argument, in this case to the tuples." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from operator import itemgetter\n", "getvol = itemgetter(1)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('tetrahedron', 1), ('cube', 3), ('octahedron', 4), ('cuboctahedron', 20)]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "sorted(vols_tuples, key=getvol)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lastly, if the objects we want to sort implement the comparison operators, then ```sorted``` will have a clue.\n", "\n", "For more information on sorting:\n", "\n", "* [How to Sort](https://docs.python.org/3/howto/sorting.html)\n", "* [GeeksforGeeks](https://www.geeksforgeeks.org/sort-in-python/)\n", "\n", "In the cell below, ```Polyhedron``` is the parent (base) class for several specific polyhedron types with default volumes. After creating a tuple with one of each (an instance of each specific polyhedron), we're able to sort them directly, with no need for ```key=```.\n", "\n", "Remember about [\"special names\"](overview.ipynb).\n", "\n", "### Special Names ( ```__ribs__``` )\n", "\n", "sd\"Image\n", "\n", "You'll find some excellent overview of the magic methods in this essay by Rafe Kettler: [A Guide to Python's Magic Methods](https://rszalski.github.io/magicmethods/). He's mostly looking at Python 2.7, so does not pick up on the ``__next__`` method, however you'll be able to fill in the blanks thanks to this course." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Tetrahedron(v=1), Cube(v=3), Octahedron(v=4), Icosahedron(v=18.512295868219162), Cuboctahedron(v=20)]\n" ] } ], "source": [ "from math import hypot\n", "root2 = pow(2,1/2)\n", "φ = (5**(1/2) + 1)/2 # golden mean\n", "\n", "class Polyhedron:\n", "\n", " @staticmethod\n", " def version():\n", " return \"version 1.0\"\n", " \n", " def __lt__(self, other):\n", " return self.volume < other.volume\n", " \n", " def __gt__(self, other):\n", " return self.volume > other.volume\n", " \n", " def __eq__(self, other):\n", " return self.volume == other.volume\n", " \n", " def scale(self, factor):\n", " return type(self)(v=self.volume * factor**3, \n", " e=self.edge * factor)\n", " \n", " def __repr__(self):\n", " return \"{}(v={})\".format(type(self).__name__, self.volume)\n", " \n", "class Tetrahedron(Polyhedron):\n", " \"Self dual, space-filler with octahedron\"\n", " \n", " def __init__(self, v=1, e=1):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", " \n", "class Cube(Polyhedron):\n", " \"Dual of Octahedron, space-filler\"\n", " \n", " def __init__(self, v=3, e=root2):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (12, 8, 6)\n", " \n", "class Octahedron(Polyhedron):\n", " \"Dual of Cube, space-filler with tetrahedron\"\n", " \n", " def __init__(self, v=4, e=2):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (12, 6, 8)\n", "\n", "class RhDodecahedron(Polyhedron):\n", " \"Dual of Cuboctahedron, space-filler\"\n", " \n", " def __init__(self, v=6, e=hypot(1, root2/2)):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (24, 14, 12)\n", "\n", "class Icosahedron(Polyhedron):\n", " \"Jitterbugs with Cuboctahedron\"\n", " \n", " def __init__(self, v= 5 * root2 * φ**2, e=2):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (30, 12, 20)\n", " \n", "class Cuboctahedron(Polyhedron):\n", " \"Dual of Rh Dodecahedron\"\n", " \n", " def __init__(self, v=20, e=2):\n", " self.volume = v\n", " self.edge = e\n", " self.edges, self.vertexes, self.faces = (24, 12, 14)\n", " \n", "mypolys = (Icosahedron(), Tetrahedron(), Cuboctahedron(), \n", " Octahedron(), Cube()) # create instances\n", "volume_order = sorted(mypolys)\n", "print(volume_order)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Cuboctahedron(v=20), Icosahedron(v=18.512295868219162), Octahedron(v=4), Cube(v=3), Tetrahedron(v=1)]\n" ] } ], "source": [ "rev_order = reversed(sorted(mypolys))\n", "print(list(rev_order))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below we're testing the scale function. Surface area, if implemented, would grow and shrink as a 2nd power of the scale factor. Volume changes as a 3rd power.\n", "\n", "For more on \"power laws\" see [*Scale* by Geoffrey West](https://www.santafe.edu/news-center/news/geoffrey-wests-long-anticipated-book-scale-emerges) of the Santa Fe Institute." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = Tetrahedron()\n", "t.volume" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "27" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t2 = t.scale(3)\n", "t2.volume" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t2 > t" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "20" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "c = Cuboctahedron()\n", "c.volume" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2.5" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "small_c = c.scale(1/2)\n", "small_c.volume" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "18.512295868219162" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "i = Icosahedron()\n", "i.volume" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1.8512295868219169" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# checking S factor computations\n", "s_factor = (c.volume/i.volume)\n", "e_factor = s_factor ** (1/3)\n", "SmallGuy = c.scale((1/e_factor)**3) \n", "SmallGuy.edge" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Everything is working great, why fix what ain't broke? Because we're learning Python, and learn by fixing, even not-broken code.\n", "\n", "For example, from Rafe's docs we learn about ```functools.total_ordering``` which, when used as a decorator, lets us get away with defining only two of the compartor operators, and decorating will do the rest.\n", "\n", "Lets try it..." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "from functools import total_ordering\n", "from inspect import getmro \n", "\n", "@total_ordering\n", "class Polyhedron:\n", " \n", " @staticmethod\n", " def version():\n", " return \"version 1.1\"\n", " \n", " def __lt__(self, other):\n", " return self.volume < other.volume\n", " \n", " def __eq__(self, other):\n", " return self.volume == other.volume\n", " \n", " def scale(self, factor):\n", " return type(self)(v=self.volume * factor**3)\n", " \n", " def __repr__(self):\n", " return \"{}(v={})\".format(type(self).__name__, self.volume) " ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(__main__.Tetrahedron, __main__.Polyhedron, object)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = Tetrahedron()\n", "getmro(Tetrahedron)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'version 1.0'" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = Tetrahedron()\n", "getmro(Tetrahedron)[1].version()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Oops. What we're seeing is that even though Polyhedron is redefined, its subclasses, defined above, still point back to the earlier version as their parent class. \n", "\n", "For a test of the new code base, we'll want to redefine the subclasses once again, plus why not add an icosahedron for completeness, along with some of the basic modules? \n", "\n", "Its volume, compared to the Cuboctahedron's of same edge length (volume 20), is $5(\\sqrt[2]{2})\\phi^{2}$. \n", "\n", "Note use of embedded $\\LaTeX$. \n", "\n", "[Some tips](https://www.andy-roberts.net/writing/latex/mathematics_1).\n", "\n", "## Basic Modules" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "class S(Polyhedron):\n", " \"Self dual, space-filler with octahedron\"\n", " \n", " def __init__(self, v=(φ **-5)/2):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", " \n", "class E(Polyhedron):\n", " \"Tetrahedral sliver of the Rhombic Triacontahedron\"\n", " \n", " def __init__(self, v=(2**(1/2)/8) * (φ ** -3)):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", "\n", "class A(Polyhedron):\n", " \"Tetrahedral sliver of the Tetrahedron\"\n", " \n", " def __init__(self, v= (1/24)):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", " \n", "class B(Polyhedron):\n", " \"Tetrahedral sliver of the Octahedron - less A\"\n", " \n", " def __init__(self, v= (1/24)):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", " \n", "s_factor = S().volume/E().volume # S Factor. S:E :: Cubocta:Icosa" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\"module_studies\"" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1.618033988749895\n", "1.0803630269509057\n" ] } ], "source": [ "class Tetrahedron(Polyhedron):\n", " \"24 A modules\"\n", " \n", " def __init__(self, v=1):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (6, 4, 4)\n", " \n", "class Cube(Polyhedron):\n", " \"Tetrahedron + 4 1/8th Octahedrons\"\n", " \n", " def __init__(self, v=3):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (12, 8, 6)\n", " \n", "class Octahedron(Polyhedron):\n", " \"48 A modules + 48 B modules\"\n", " \n", " def __init__(self, v=4):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (12, 6, 8)\n", "\n", "class RhDodecahedron(Polyhedron):\n", " \"48 AAB i.e. 48 Mites, Mite = a spacefilling tetrahedron\"\n", " \n", " def __init__(self, v=6):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (24, 14, 12)\n", "\n", "class Icosahedron(Polyhedron):\n", " \"Related to Cuboctahedron through Jitterbug Transformation\"\n", " \n", " def __init__(self, v = 20 * 1/s_factor):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (30, 12, 20)\n", " \n", "class Cuboctahedron(Polyhedron):\n", " \"Defined by 12 unit radius balls around a nuclear ball\"\n", " \n", " def __init__(self, v=20):\n", " self.volume = v\n", " self.edges, self.vertexes, self.faces = (24, 12, 14)\n", "\n", "print(φ)\n", "print(s_factor)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To help you get oriented regarding the S Module, consider how an icosahedron fits inside the volume 4 octahedron with eight of its faces flush with those of the octahedron. The volume difference between these two shapes carves up into 24 S Modules.\n", "\n", "\"S\n", "\n", "If you squint at the above, you'll see volumes 24 times what we would usually expect as the A module is set to unit, with all other sizes adjusted accordingly. More typically, we assess the A module to have a volume of $1/24$." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'version 1.1'" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t = Tetrahedron()\n", "getmro(Tetrahedron)[1].version()" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "t2 = t.scale(3)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t2 <= t" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t2 > t" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The above expression is especially interesting because nowhere in the latest version of ```Polyhedron``` did we specifically define any ```__gt__``` method, and yet here we are, using that operator: proof the the decorator is doing its job." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "18.512295868219162" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "icosa = Icosahedron()\n", "icosa.volume" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bonus Topic\n", "\n", "The ```enum.Enum``` type might be useful for defining our Polyhedrons. Lets try:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "from enum import Enum\n", "\n", "class Volume(Enum):\n", " A = 1/24\n", " B = 1/24\n", " E = (2**(1/2)/8) * (φ ** -3)\n", " S = (φ **-5)/2\n", " Tetrahedron = 1\n", " Cube = 3\n", " Octahedron = 4\n", " RhDodecahedron = 6\n", " Icosahedron = 5 * 2**(1/2) * φ ** 2\n", " Cuboctahedron = 20\n", " \n", " @classmethod\n", " def table(cls):\n", " print(\"{:^40}\".format(\"VOLUMES TABLE\"))\n", " print(\"_\" * 40)\n", " print(\" Name | Volume\")\n", " print(\"_\" * 40)\n", " for name, member in cls.__members__.items():\n", " print(\"| {:20} | {:10.6f}\".format(name, member.value))" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " VOLUMES TABLE \n", "________________________________________\n", " Name | Volume\n", "________________________________________\n", "| A | 0.041667\n", "| B | 0.041667\n", "| E | 0.041731\n", "| S | 0.045085\n", "| Tetrahedron | 1.000000\n", "| Cube | 3.000000\n", "| Octahedron | 4.000000\n", "| RhDodecahedron | 6.000000\n", "| Icosahedron | 18.512296\n", "| Cuboctahedron | 20.000000\n" ] } ], "source": [ "Volume.table()" ] } ], "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.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }