{"id":45,"date":"2020-12-22T17:54:58","date_gmt":"2020-12-22T16:54:58","guid":{"rendered":"https:\/\/bowfinger.de\/blog\/?p=45"},"modified":"2020-12-24T15:07:03","modified_gmt":"2020-12-24T14:07:03","slug":"breaking-changes-fenics-and-meshio4-0-0","status":"publish","type":"post","link":"https:\/\/bowfinger.de\/blog\/2020\/12\/breaking-changes-fenics-and-meshio4-0-0\/","title":{"rendered":"Breaking Changes: Fenics and Meshio>=4.0.0"},"content":{"rendered":"\n<pre class=\"wp-block-verse\">Package versions this was tested with (2020-12-22):\ngmsh 4.7.1\nfenics 2019.1.0:latest\nmeshio 4.3.7\npygmsh 7.1.5\n\nCheck your versions with the code provided <a href=\"#checkcode\" data-type=\"internal\" data-id=\"#checkcode\">here<\/a>.<\/pre>\n\n\n\n<p><a href=\"https:\/\/github.com\/nschloe\/meshio\" data-type=\"URL\" data-id=\"https:\/\/github.com\/nschloe\/meshio\">Meshio<\/a> is a great piece of software if you&#8217;re into converting meshes, or for that matter, if you&#8217;re using <a href=\"https:\/\/www.fenicsproject.org\" data-type=\"URL\">Fenics<\/a> for finite elements calculations. However, the problem I experienced with both packages is their rapid development, consequently breaking changes and sometimes a lack of up-to-date documentation.<\/p>\n\n\n\n<p>So, the Fenics community decided to embrace XDMF as mesh format of choice (good idea) but as usual with new stuff it&#8217;s WIP. So, there&#8217;s an ongoing thread on the <a href=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/112\" data-type=\"URL\" data-id=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/112\">discussion group<\/a> which is extensive, but frankly hard to follow. After a day of fiddeling, I realised that a lot of example code discussed in the <a href=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/112\" data-type=\"URL\" data-id=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/112\">thread<\/a> broke with Meshio version 4.0.0 and newer. It&#8217;s a subtle change in Meshio, which makes it non-obvious.<\/p>\n\n\n\n<p>So, with no futher ado, here are code blocks that<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Generate a simple mesh using <a href=\"https:\/\/gmsh.info\" data-type=\"URL\" data-id=\"https:\/\/gmsh.info\">Gmsh<\/a><ol><li>A geo file<\/li><li>A code block using Gmsh&#8217;s python API<\/li><\/ol><\/li><li>Convert <code>msh<\/code>-file to two XDMF files (mesh and boundary markers) for Fenics to digest<\/li><li>Import of the XDMF files into Fenics and a minimal use-case<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Generate a simple mesh using Gmsh<\/h2>\n\n\n\n<p>The three methods shown below are equivalent. Which one to choose depends on your workflow. It should be noted that getting the Gmsh Python API is not hard but still a bit tricky depending on context.<\/p>\n\n\n\n<p>The goal is to build a rectangle of 5&nbsp;mm \u00d7 2.5&nbsp;mm with two physical groups (the boundary and the inner area).<\/p>\n\n\n\n<p>Let&#8217;s start with the most static but straight-forward aproach: The geo-file straight from Gmsh&#8217;s UI. We&#8217;ll use the OpenCASCADE kernel (because, why not?):<\/p>\n\n\n\n<pre title=\"A .geo file\" class=\"wp-block-code\"><code lang=\"\" class=\" line-numbers\">SetFactory(\"OpenCASCADE\");\nRectangle(1) = {-1.3, 0.5, 0, 5e-3, 2.5e-3, 0};\nPhysical Surface(2) = {1};\nPhysical Curve(1) = {1,2,3,4};<\/code><\/pre>\n\n\n\n<p>Save this file and generate a <code>.msh<\/code> file using the following command (we&#8217;re forcing a 2D mesh with <code>-2<\/code> and gmsh format version 4.1 using <code>-format msh41<\/code> here):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">gmsh -2 -format msh41 test.geo test.msh<\/code><\/pre>\n\n\n\n<p>And last but not least using Gmsh&#8217;s Python API it looks like this:<\/p>\n\n\n\n<pre title=\"Geometry generation using Gmsh Python API\" class=\"wp-block-code\"><code lang=\"python\" class=\"language-python line-numbers\">import gmsh\nimport numpy as np\n\ngmsh.initialize()\ngmsh.model.add(\"test\")\ngmsh.logger.start()\n\nr1 = gmsh.model.occ.addRectangle(0, 0, 0, 5e-3, 2.5e-3)\ngmsh.model.occ.synchronize()\n\npg1 = gmsh.model.addPhysicalGroup(2, [1], tag=2) # inner surface\npg2 = gmsh.model.addPhysicalGroup(1, [1, 2, 3, 4], tag=1) # outer wall\n\ngmsh.model.mesh.generate(dim=2)\n\ngmsh.write(\"test.msh\")<\/code><\/pre>\n\n\n\n<p>This example is closely based on an example in gmsh&#8217;s docs (<a href=\"https:\/\/gmsh.info\/doc\/texinfo\/gmsh.html#t16\" data-type=\"URL\" data-id=\"https:\/\/gmsh.info\/doc\/texinfo\/gmsh.html#t16\">t16.geo<\/a>, <a href=\"https:\/\/gitlab.onelab.info\/gmsh\/gmsh\/blob\/gmsh_4_7_1\/tutorial\/python\/t16.py\" data-type=\"URL\" data-id=\"https:\/\/gitlab.onelab.info\/gmsh\/gmsh\/blob\/gmsh_4_7_1\/tutorial\/python\/t16.py\">t16.py<\/a>). I would not say I&#8217;d know what <code>gmsh.initialize<\/code>, <code>gmsh.model.add<\/code>, <code>gmsh.logger.start<\/code> and <code>gmsh.model.occ.synchronize<\/code> actually do.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Convert <code>msh<\/code>-file to two XDMF files (mesh and boundary markers) for Fenics to digest<\/h2>\n\n\n\n<p>As I understand it, Fenics currently requires separate files for mesh and &#8211; let&#8217;s say &#8211; mesh annotations (like boundary names\/numbers etc.). So, what the discussion came down to is converting the mesh as obtained from gmsh into two <code>xdmf<\/code>-files: <code>test_mesh.xdmf<\/code> and <code>test_boundaries.xdmf<\/code>.<\/p>\n\n\n\n<p>The code below is shamelessly stolen mostly from <a href=\"https:\/\/fenicsproject.discourse.group\/u\/dokken\">dokken<\/a>&#8216;s <a href=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/104?u=cweickhmann\" data-type=\"URL\" data-id=\"https:\/\/fenicsproject.discourse.group\/t\/transitioning-from-mesh-xml-to-mesh-xdmf-from-dolfin-convert-to-meshio\/412\/104?u=cweickhmann\">post<\/a> and Frankenstein-copy-pasted together with other contributions from forum users.<\/p>\n\n\n\n<pre title=\"Splitting test.msh into two xdmf-files\" class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import meshio\n\n# Let's introduce some symbolic names:\ninfile_mesh = \"test.msh\"\noutfile_mesh = \"test_mesh.xdmf\"\noutfile_boundary = \"test_boundaries.xdmf\"\n\n# read input from infile\ninmsh = meshio.read(infile_mesh)\n\n# delete third (obj=2) column (axis=1), this strips the z-component\n# outpoints = np.delete(arr=inmsh.points, obj=2, axis=1), create (two dimensional!) triangle mesh file\noutmsh = meshio.Mesh(points=outpoints,\n                      cells=[('triangle', inmsh.get_cells_type(\"triangle\"))],\n                      cell_data={'Subdomain': [inmsh.cell_data_dict['gmsh:physical']['triangle']]},\n                      field_data=inmsh.field_data)\n\n# write mesh to file\nmeshio.write(outfile_mesh, outmsh)\n\n# create (two dimensional!) boundary data file\noutboundary = meshio.Mesh(points=outpoints,\n                           cells=[('line', inmsh.get_cells_type('line') )],\n                           cell_data={'Boundary': [inmsh.cell_data_dict['gmsh:physical']['line']]},\n                           field_data=inmsh.field_data)\n\n# write boundary data to file\nmeshio.write(filename=outfile_boundary, mesh=outboundary)<\/code><\/pre>\n\n\n\n<p>So, after running this code snipped, you should see two files: <code>test_mesh.xdmf<\/code> and <code>test_boundaries.xdmf<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Import of the XDMF files into Fenics and a minimal use-case<\/h2>\n\n\n\n<p>Well, minimal may be something different. But since I&#8217;m using this approach for RF-related eigenvalue problems (EVP), this is as close to minimal as it gets:<\/p>\n\n\n\n<pre title=\"&quot;Minimal&quot; Use-Case in Fenics\" class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import dolfin as do\nimport numpy as np\nimport matplotlib.pyplot as plt # for plotting mesh\/results\n\n# Import Mesh\nmesh = do.Mesh()\nwith do.XDMFFile(outfile_mesh) as meshfile, \\\n        do.XDMFFile(outfile_boundary) as boundaryfile:\n    meshfile.read(mesh)\n    mvc = do.MeshValueCollection(\"size_t\", mesh, 2)\n    boundaryfile.read(mvc, \"Boundary\")\n    outerwall = do.MeshFunction(\"size_t\", mesh, mvc)\n\n# Uncomment, if you want to see the mesh\n# do.plot(mesh); plt.show()\n\n# Generate Function Space\nFE = do.FiniteElement(\"RTE\", mesh.ufl_cell(), 1)\nFS = do.FunctionSpace(mesh, FE)\n\n# Use markers for boundary conditions\nbcs = [\n     do.DirichletBC(FS, do.Constant((0.0, 0.0)), outerwall, 1),\n     ]\n\n# Trial and Test functions\nE = do.TrialFunction(FS)\nEE = do.TestFunction(FS)\n\n# Helmholtz EVP (A<em>e = -k_co^2<\/em>B*e)\na = do.inner(do.curl(E), do.curl(EE))*do.dx\nb = do.inner(E, EE)*do.dx\n\n# For EVP make use of PETSc and SLEPc\ndummy = E[0]*do.dx\nA = do.PETScMatrix()\nB = do.PETScMatrix()\n\n# Assemble System\ndo.assemble_system(a, dummy, bcs, A_tensor=A)\ndo.assemble_system(b, dummy, bcs, A_tensor=B)\n# Apply Boundaries\n[bc.apply(B) for bc in bcs]\n[bc.apply(A) for bc in bcs]\n\n# Let SLEPc solve that generalised EVP\nsolver = do.SLEPcEigenSolver(A, B)\nsolver.parameters[\"solver\"] = \"krylov-schur\"\nsolver.parameters[\"tolerance\"] = 1e-16\nsolver.parameters[\"problem_type\"] = \"gen_hermitian\"\nsolver.parameters[\"spectrum\"] = \"target magnitude\"\nsolver.parameters[\"spectral_transform\"] = \"shift-and-invert\"\nsolver.parameters[\"spectral_shift\"] = -(2.<em>np.pi\/10e-3)<\/em>*2\n\nneigs = 10\nsolver.solve(neigs)\n\nprint(f\"Found {solver.get_number_converged():d} solutions.\")\n\n# Return the computed eigenvalues in a sorted array\ncomputed_eigenvalues = []\n\nfor i in range(min(neigs, solver.get_number_converged())):\n    r, _, fieldRe, fieldIm = solver.get_eigenpair(i) # ignore the imaginary part\n    f = do.Function(FS)\n    f.vector()[:] = fieldRe\n\n    <code>if np.abs(r) &gt; 1.1:<\/code>\n    <code># With r = k_co^2, find <code>k_co<\/code> = sqrt(r)<\/code>\n    <code><code>k_co<\/code> = np.sqrt(r)<\/code>\n    <code>do.plot(f); plt.title(f\"{gamma:f}\"); plt.show()<\/code>\n    c<code>omputed_eigenvalues.append(r)<\/code>\n\nprint(\"All computed eigenvalues:\")\nprint(np.sort(np.array(computed_eigenvalues)))<\/code><\/pre>\n\n\n\n<p>This gives me a 3 modes for this 5 mm by 2.5 mm waveguide and a bunch of spurious modes. That are filtered out because their <code>k_co^2<\/code> is 1 or smaller.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>This used to be easier (let&#8217;s say back in 2016). I&#8217;m fairly sure this is not the most efficient way (both programmatically and numerically) and I&#8217;m sure as well that there are better ways to solve this problem. For instance, instead of a generic triangular mesh a more structured triangular mesh or even a quad mesh would produce better solutions. Also, a higher order finite element should produce &#8220;smoother&#8221; solutions.<\/p>\n\n\n\n<p>I also wanted to add a code block for pygmsh, but it turned out that there are a few catches with physical labels, how they&#8217;re numbered and labelled, and how to handle them in Fenics that I could not figure out. So, there&#8217;s <a href=\"https:\/\/bowfinger.de\/blog\/2020\/12\/breaking-changes-2-pygmsh-fenics-and-meshio4-0-0\/\" data-type=\"post\" data-id=\"87\">a separate post on that here<\/a>.<\/p>\n\n\n\n<p>I am happy and thankful for any feedback!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"checkcode\">How to check your package versions<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"python\" class=\"language-python\">import fenics\nimport meshio\nimport gmsh\nimport pygmsh\n\nprint(\"fenics:\", fenics.__version__,\n      \"meshio:\", meshio.__version__,\n      \"gmsh:\", gmsh.__version__,\n      \"pygmsh:\", pygmsh.__version__)<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Meshio and Fenics are great pieces of software. However, both packages are in active development and consequently breaking changes and sometimes a lack of up-to-date documentation are usual. Here&#8217;s my attempt to give newbies like me some code to start from&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"templates\/template-full-width.php","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[5],"tags":[8,7,6,10,9,11,12],"class_list":["post-45","post","type-post","status-publish","format-standard","hentry","category-fem","tag-dolfin","tag-fenics","tag-finite-elements","tag-gmsh","tag-meshio","tag-pygmsh","tag-python"],"_links":{"self":[{"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/posts\/45","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/comments?post=45"}],"version-history":[{"count":39,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/posts\/45\/revisions"}],"predecessor-version":[{"id":98,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/posts\/45\/revisions\/98"}],"wp:attachment":[{"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/media?parent=45"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/categories?post=45"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bowfinger.de\/blog\/wp-json\/wp\/v2\/tags?post=45"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}