{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pybinding as pb\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"pb.pltutils.use_style()\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Composite shapes\n",
"\n",
"The basic usage of shapes was explained in the [Finite size](http://docs.pybinding.site/page/advanced/../tutorial/finite.html) section of the tutorial. An overview of all the classes and function is available in the [Shapes](http://docs.pybinding.site/page/advanced/../api.html#shapes-api) API reference. This section show how multiple of those shapes can be composed to quickly create intricate systems.\n",
"\n",
"[Download this page as a Jupyter notebook](http://docs.pybinding.site/page/_downloads/7f69e1d6d5cb697611966fb13688c083/shapes.ipynb)\n",
"\n",
"## Moving shapes\n",
"\n",
"All shapes have a [`with_offset()`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Polygon.html#pybinding.Polygon.with_offset) method which simply translates the shape by a vector:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"shape = pb.rectangle(2, 2)\n",
"translated_shape = shape.with_offset([1, -1])\n",
"shape.plot()\n",
"translated_shape.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This applies to any kind of shape, including user-defined freeform shapes:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def circle(radius):\n",
" def contains(x, y, z):\n",
" return np.sqrt(x**2 + y**2) < radius\n",
" return pb.FreeformShape(contains, width=[2*radius, 2*radius])\n",
"\n",
"shape = circle(1)\n",
"translated_shape = shape.with_offset([1, 0])\n",
"shape.plot()\n",
"translated_shape.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that [`Polygon`](http://docs.pybinding.site/page/advanced/../_api/pybinding.Polygon.html#pybinding.Polygon) and [`FreeformShape`](http://docs.pybinding.site/page/advanced/../_api/pybinding.FreeformShape.html#pybinding.FreeformShape) are presented differently in the plots. For polygons, a line which connects all vertices is plotted. Freeform shapes are shown as a lightly shaded silhouette which is filled in by calling the `contains` function and placing dark pixels at positions where it returned `True`.\n",
"\n",
"## Using set operations\n",
"\n",
"In the examples above we placed 2 shapes so that they overlap, but those were only plots. In order to create a composite shape, we can use logical and arithmetic operator. For example, addition:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"s1 = pb.rectangle(2.3, 2.15)\n",
"s2 = s1.with_offset([1.12, -1.05])\n",
"\n",
"composite_shape = s1 + s2\n",
"composite_shape.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that even though we have combined two polygons, the composite shape is plotted in the style of a freeform shape. This is intentional to allow making completely generic shapes.\n",
"\n",
"The `+` operator creates a union of the two shapes and the result can be used with a model:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pybinding.repository import graphene\n",
"\n",
"model = pb.Model(graphene.monolayer(), composite_shape)\n",
"model.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Shapes are composed in terms of set operations (e.g. unions, intersections) and the syntax mirrors that of Python’s builtin `set`. The available operators and their results are shown in the code and figure below. Note that the `+` and `|` operators perform the same function (union). Both are available simply for convenience. Apart from `-`, all the operators are symmetric."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"grid = plt.GridSpec(3, 2, hspace=0.4)\n",
"plt.figure(figsize=(6.7, 8))\n",
"\n",
"titles_and_shapes = [\n",
" (\"Union: s1 + s2\", s1 + s2),\n",
" (\"Union: s1 | s2 (alternative notation)\", s1 | s2),\n",
" (\"Intersection: s1 & s2\", s1 & s2),\n",
" (\"Symmetric difference: s1 ^ s2\", s1 ^ s2),\n",
" (\"Difference: s1 - s2\", s1 - s2),\n",
" (\"Difference: s2 - s1\", s2 - s1)\n",
"]\n",
"\n",
"for g, (title, shape) in zip(grid, titles_and_shapes):\n",
" plt.subplot(g, title=title)\n",
" s1.plot()\n",
" s2.plot()\n",
" model = pb.Model(graphene.monolayer(), shape)\n",
" model.shape.plot()\n",
" model.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This isn’t limited to just two operands. Any number of shapes can be freely combined:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from math import pi\n",
"\n",
"rectangle = pb.rectangle(x=6, y=1)\n",
"hexagon = pb.regular_polygon(num_sides=6, radius=1.92, angle=pi/6)\n",
"circle = pb.circle(radius=0.6)\n",
"\n",
"model = pb.Model(\n",
" graphene.monolayer(),\n",
" (rectangle + hexagon) ^ circle\n",
")\n",
"model.shape.plot()\n",
"model.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Additional examples\n",
"\n",
"Circular rings are easy to create even with a [`FreeformShape`](http://docs.pybinding.site/page/advanced/../_api/pybinding.FreeformShape.html#pybinding.FreeformShape), but composites make it trivial to create rings as the difference of any two shapes:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"outer = pb.regular_polygon(num_sides=6, radius=1.4)\n",
"inner = pb.regular_polygon(num_sides=6, radius=0.8)\n",
"model = pb.Model(graphene.bilayer(), outer - inner)\n",
"model.shape.plot()\n",
"model.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of course, we can also go a bit wild:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"plt.figure(figsize=(6.7, 2.6))\n",
"\n",
"circle = pb.circle(radius=2)\n",
"triangle = pb.regular_polygon(num_sides=3, radius=2, angle=pi / 6).with_offset([1.4, 0])\n",
"pm = pb.Model(graphene.monolayer(), circle - triangle)\n",
"pm.plot()\n",
"\n",
"dot = pb.circle(radius=0.8)\n",
"for x in [3.55, 6.25, 8.95]:\n",
" pd = pb.Model(graphene.bilayer(), dot.with_offset([x, 0]))\n",
" pd.plot()"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 4
}