{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# The structure of a mesh" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "To explain the structure of a mesh, we create a helper function:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from sigmaepsilon.mesh import PointData, PolyData\n", "from sigmaepsilon.mesh.cells import T3, Q4, L2\n", "import numpy as np\n", "\n", "\n", "def generate_mesh(to_standard_form:bool=False):\n", "\n", " coords_T3 = np.array([\n", " [0, 0, 0],\n", " [1, 0, 0],\n", " [1, 1, 0],\n", " [0, 1, 0],\n", " ], dtype=float)\n", "\n", " topology_T3 = np.array([\n", " [0, 1, 2],\n", " [0, 2, 3],\n", " ], dtype=int)\n", "\n", " coords_Q4 = np.array([\n", " [2, 0, 0],\n", " [3, 0, 0],\n", " [3, 1, 0],\n", " [2, 1, 0],\n", " ], dtype=float)\n", "\n", " topology_Q4 = np.array([\n", " [0, 1, 2, 3],\n", " ], dtype=int)\n", "\n", " coords_L2 = np.array([\n", " [1, 0, 0],\n", " [2, 1, 0],\n", " [1, 1, 0],\n", " [2, 0, 0],\n", " ], dtype=float)\n", "\n", " topology_L2 = np.array([\n", " [0, 1],\n", " [2, 3]\n", " ], dtype=int)\n", "\n", " pd_T3 = PointData(coords=coords_T3)\n", " cd_T3 = T3(topo=topology_T3)\n", "\n", " pd_Q4 = PointData(coords=coords_Q4)\n", " cd_Q4 = Q4(topo=topology_Q4)\n", "\n", " pd_L2 = PointData(coords=coords_L2)\n", " cd_L2 = L2(topo=topology_L2)\n", "\n", " mesh = PolyData()\n", " mesh[\"2d\", \"triangles\"] = PolyData(pd_T3, cd_T3)\n", " mesh[\"2d\", \"quads\"] = PolyData(pd_Q4, cd_Q4)\n", " mesh[\"lines\"] = PolyData(pd_L2, cd_L2)\n", " \n", " if to_standard_form:\n", " mesh.to_standard_form()\n", " \n", " return mesh\n", "\n", "\n", "mesh = generate_mesh()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## The database model" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "A ``PolyData`` object is essentially a dictionary, equipped with one or two Awkward arrays to store data attached to the points and the cells. Instances walk and talk like a dictionary but the behaviour is extended to cover nested definitions." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "isinstance(mesh, dict)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['2d', 'lines']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(mesh.keys())" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[PolyData({'triangles': PolyData({}), 'quads': PolyData({})}), PolyData({})]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(mesh.values())" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the `values` call returns only two items, but the mesh has three blocks. To loop through the subdictionaries (called blocks) with cell data, you can use the ``cellblocks`` method of any ``PolyData`` instance. Every block has an address and a parent except the root object, that has no parent and address." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['2d', 'triangles']\n", "['2d', 'quads']\n", "['lines']\n" ] } ], "source": [ "for block in mesh.cellblocks(inclusive=True):\n", " print(block.address)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The parameter ``inclusive`` means to start parsing the structure of the mesh with the instance the call was made upon. In this case it makes no difference, as the root instance of the mesh has no attached cells." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['2d', 'triangles']\n", "['2d', 'quads']\n", "['lines']\n" ] } ], "source": [ "for block in mesh.pointblocks(inclusive=True):\n", " print(block.address)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Now bring the mesh into a standard form and repeat the above queries:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PolyData({'2d': PolyData({'triangles': PolyData({}), 'quads': PolyData({})}), 'lines': PolyData({})})" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.to_standard_form()" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['2d', 'triangles']\n", "['2d', 'quads']\n", "['lines']\n" ] } ], "source": [ "for block in mesh.cellblocks(inclusive=True):\n", " print(block.address)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[]\n" ] } ], "source": [ "for block in mesh.pointblocks(inclusive=True):\n", " print(block.address)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "An empty list is returned, since the root of the mesh does host a point cloud, but it doesn't have an address. To see if a block has an attached point or cell related data,use the `pointdata` and `celldata` properties (you can also use `mesh.pd` and `mesh.cd`):" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "mesh = generate_mesh()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{_x: [0, 0, 0], _activity: True, _id: 0},\n",
       " {_x: [1, 0, 0], _activity: True, _id: 1},\n",
       " {_x: [1, 1, 0], _activity: True, _id: 2},\n",
       " {_x: [0, 1, 0], _activity: True, _id: 3}]\n",
       "------------------------------------------\n",
       "type: 4 * {\n",
       "    _x: 3 * float64,\n",
       "    _activity: bool,\n",
       "    _id: int32\n",
       "}
" ], "text/plain": [ "" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].pointdata" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sigmaepsilon.mesh.data.pointdata.PointData" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(mesh[\"2d\", \"triangles\"].pointdata)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{_nodes: [0, 1, 2], _id: 0},\n",
       " {_nodes: [0, 2, 3], _id: 1}]\n",
       "-----------------------------\n",
       "type: 2 * {\n",
       "    _nodes: 3 * int32,\n",
       "    _id: int32\n",
       "}
" ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "sigmaepsilon.mesh.cells.t3.T3" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(mesh[\"2d\", \"triangles\"].celldata)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "`PointData` and `CellData` instances are wrapper objects that wrap Awkward arrays. The databases can be accessed using the `db` property:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "awkward.highlevel.Array" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(mesh[\"2d\", \"triangles\"].pointdata.db)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "awkward.highlevel.Array" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(mesh[\"2d\", \"triangles\"].celldata.db)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "`PointData` and `CellData` instances are actually represented by their wraooed data objects:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{_nodes: [0, 1, 2], _id: 0},\n",
       " {_nodes: [0, 2, 3], _id: 1}]\n",
       "-----------------------------\n",
       "type: 2 * {\n",
       "    _nodes: 3 * int32,\n",
       "    _id: int32\n",
       "}
" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "In the representation we can see the fields of the database. The fields are also accessible using the `fields` property of the data object:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['_nodes', '_id']" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db.fields" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Field names starting with an underscore are internal variables crucial for the object to work properly. Overriding these fields might break the behaviour of the mesh. Besides these reserved field names, you can attach arbitrary data to the databases:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "db = mesh[\"2d\", \"triangles\"].celldata.db\n", "number_of_cells = len(db)\n", "db[\"random_data\"] = np.random.rand(number_of_cells)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{_nodes: [0, 1, 2], _id: 0, random_data: 0.479},\n",
       " {_nodes: [0, 2, 3], _id: 1, random_data: 0.56}]\n",
       "-------------------------------------------------\n",
       "type: 2 * {\n",
       "    _nodes: 3 * int32,\n",
       "    _id: int32,\n",
       "    random_data: float64\n",
       "}
" ], "text/plain": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The newly attached data is now accessible as an Awkward array:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[0.479,\n",
       " 0.56]\n",
       "-----------------\n",
       "type: 2 * float64
" ], "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db.random_data" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "or a NumPy array" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([0.47853953, 0.55976835])" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db.random_data.to_numpy()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The data is also available like the database was a dictionary:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[0.479,\n",
       " 0.56]\n",
       "-----------------\n",
       "type: 2 * float64
" ], "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh[\"2d\", \"triangles\"].celldata.db[\"random_data\"]" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "When bringing a mesh to a standard form, the Awkward library is smart enough to handle missing data. Let say we attach some random data to one of the point cloud of the mesh before briging it to standard form." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": [ "db = mesh[\"2d\", \"triangles\"].pointdata.db\n", "number_of_points = len(db)\n", "db[\"random_data\"] = np.random.rand(number_of_points)" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PolyData({'2d': PolyData({'triangles': PolyData({}), 'quads': PolyData({})}), 'lines': PolyData({})})" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.to_standard_form()" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
[{random_data: 0.931, _activity: True, _id: 0, _x: [0, ..., 0]},\n",
       " {random_data: 0.981, _activity: True, _id: 1, _x: [1, ..., 0]},\n",
       " {random_data: 0.234, _activity: True, _id: 2, _x: [1, ..., 0]},\n",
       " {random_data: 0.589, _activity: True, _id: 3, _x: [0, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 4, _x: [2, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 5, _x: [3, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 6, _x: [3, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 7, _x: [2, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 8, _x: [1, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 9, _x: [2, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 10, _x: [1, ..., 0]},\n",
       " {random_data: nan, _activity: True, _id: 11, _x: [2, ..., 0]}]\n",
       "----------------------------------------------------------------\n",
       "type: 12 * {\n",
       "    random_data: float64,\n",
       "    _activity: bool,\n",
       "    _id: int32,\n",
       "    _x: 3 * float64\n",
       "}
" ], "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.pointdata.db" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "You can turn `PointData` and `CellData` instances to other well known data formats (see the API reference for a full list of supported formats):" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
random_data
entry
00.930941
10.980779
20.234165
30.589487
4NaN
5NaN
6NaN
7NaN
8NaN
9NaN
10NaN
11NaN
\n", "
" ], "text/plain": [ " random_data\n", "entry \n", "0 0.930941\n", "1 0.980779\n", "2 0.234165\n", "3 0.589487\n", "4 NaN\n", "5 NaN\n", "6 NaN\n", "7 NaN\n", "8 NaN\n", "9 NaN\n", "10 NaN\n", "11 NaN" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.pointdata.to_dataframe(fields=[\"random_data\"])" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Root, source and parent" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "mesh = generate_mesh()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "The root is the top level `PolyData` instance in the mesh. The root of the root object is itself." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2668746377872, 2668746377872, 2668746377872)" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(mesh), id(mesh.root), id(mesh[\"2d\", \"triangles\"].root)" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "To tell if an instance is a root or not use the `is_root` method:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, False)" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.is_root(), mesh[\"2d\", \"triangles\"].is_root()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Every block of cells in a mesh -except the root- has a parent, which is the containing `PolyData` instance. The parent of the root instance is `None`." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2668746378832, 2668746378832)" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(mesh[\"2d\"]), id(mesh[\"2d\", \"triangles\"].parent)" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.parent is None" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, False)" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.is_root(), mesh[\"2d\", \"triangles\"].is_root()" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "Every block with attached cell data has a source, that hosts the pointcloud the indices of the topology of the cells of the block are referring to." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(2668746378352, 2668746378352)" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "id(mesh[\"2d\", \"triangles\"]), id(mesh[\"2d\", \"triangles\"].source())" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "To tell if a `PolyData` hosts point related data, you can use the `is_source` method of the instance (remember that the mesh is decentralized at the moment):" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(False, True)" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.is_source(), mesh[\"2d\", \"triangles\"].is_source()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After bringing the mesh to a standard form:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "PolyData({'2d': PolyData({'triangles': PolyData({}), 'quads': PolyData({})}), 'lines': PolyData({})})" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.to_standard_form()" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(True, False)" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "mesh.is_source(), mesh[\"2d\", \"triangles\"].is_source()" ] } ], "metadata": { "kernelspec": { "display_name": ".sigmaepsilon.mesh", "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.10" }, "orig_nbformat": 4 }, "nbformat": 4, "nbformat_minor": 2 }