Curved-element meshing and Palace 3D photonics example - #131
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for patterned dielectric layers and high-order geometric mesh elements in Palace simulations. Key features include the ability to disable synthetic dielectric regions, the implementation of spline and bspline curve fitting for dielectric boundaries, and the integration of high-order elements into the meshing and visualization pipelines. A new example notebook for a directional coupler is provided; however, feedback indicates that the current validation logic incorrectly triggers a ValueError for purely dielectric simulations, which is a critical issue for photonic component modeling.
| "cell_type": "code", | ||
| "execution_count": 7, | ||
| "id": "59a9840b", | ||
| "metadata": {}, | ||
| "outputs": [ | ||
| { | ||
| "ename": "ValueError", | ||
| "evalue": "Mesh validation failed:\nValidation: FAILED\nErrors:\n - No conductor surfaces in mesh. Check that conductor layers have polygons and correct layer_type.\n - config.json has no Conductivity or PEC boundaries.\nWarnings:\n - Volumes: ['air', 'core']", | ||
| "output_type": "error", | ||
| "traceback": [ | ||
| "\u001b[31m---------------------------------------------------------------------------\u001b[39m", | ||
| "\u001b[31mValueError\u001b[39m Traceback (most recent call last)", | ||
| "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[7]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m results = sim.run()\n", | ||
| "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/gsim/src/gsim/palace/driven.py:128\u001b[39m, in \u001b[36mDrivenSim.run\u001b[39m\u001b[34m(self, parent_dir, verbose, wait)\u001b[39m\n\u001b[32m 115\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Run the driven sim on GDSFactory+ cloud.\u001b[39;00m\n\u001b[32m 116\u001b[39m \n\u001b[32m 117\u001b[39m \u001b[33;03mThin wrapper over :meth:`PalaceSimMixin.run` that narrows the\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 124\u001b[39m \u001b[33;03m ``job_id`` string when ``wait=False``.\u001b[39;00m\n\u001b[32m 125\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 126\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mgsim\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mpalace\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresults\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m SParams \u001b[38;5;28;01mas\u001b[39;00m _SParams\n\u001b[32m--> \u001b[39m\u001b[32m128\u001b[39m result = \u001b[30;43msuper\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mrun\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mparent_dir\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mverbose\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mverbose\u001b[39;49m\u001b[30;43m,\u001b[39;49m\u001b[30;43m \u001b[39;49m\u001b[30;43mwait\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43mwait\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 129\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(result, (_SParams, \u001b[38;5;28mstr\u001b[39m)):\n\u001b[32m 130\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m result\n", | ||
| "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/gsim/src/gsim/palace/base.py:1430\u001b[39m, in \u001b[36mPalaceSimMixin.run\u001b[39m\u001b[34m(self, parent_dir, verbose, wait)\u001b[39m\n\u001b[32m 1390\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mrun\u001b[39m(\n\u001b[32m 1391\u001b[39m \u001b[38;5;28mself\u001b[39m,\n\u001b[32m 1392\u001b[39m parent_dir: \u001b[38;5;28mstr\u001b[39m | Path | \u001b[38;5;28;01mNone\u001b[39;00m = \u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[32m (...)\u001b[39m\u001b[32m 1395\u001b[39m wait: \u001b[38;5;28mbool\u001b[39m = \u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[32m 1396\u001b[39m ) -> SParams | \u001b[38;5;28mdict\u001b[39m[\u001b[38;5;28mstr\u001b[39m, Path] | \u001b[38;5;28mstr\u001b[39m:\n\u001b[32m 1397\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33;03m\"\"\"Run simulation on GDSFactory+ cloud.\u001b[39;00m\n\u001b[32m 1398\u001b[39m \n\u001b[32m 1399\u001b[39m \u001b[33;03m Requires mesh() to be called first. Automatically calls\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 1428\u001b[39m \u001b[33;03m >>> print(results[\"eig.csv\"])\u001b[39;00m\n\u001b[32m 1429\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1430\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mupload\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43mverbose\u001b[39;49m\u001b[30;43m=\u001b[39;49m\u001b[30;43;01mFalse\u001b[39;49;00m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1431\u001b[39m \u001b[38;5;28mself\u001b[39m.start(verbose=verbose != \u001b[33m\"\u001b[39m\u001b[33mquiet\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 1432\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m wait:\n", | ||
| "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/gsim/src/gsim/palace/base.py:1322\u001b[39m, in \u001b[36mPalaceSimMixin.upload\u001b[39m\u001b[34m(self, verbose)\u001b[39m\n\u001b[32m 1308\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Prepare config, upload to the cloud. Does NOT start execution.\u001b[39;00m\n\u001b[32m 1309\u001b[39m \n\u001b[32m 1310\u001b[39m \u001b[33;03mRequires :meth:`set_output_dir` and :meth:`mesh` to have been\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 1318\u001b[39m \u001b[33;03m or :func:`gsim.wait_for_results`.\u001b[39;00m\n\u001b[32m 1319\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m 1320\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mgsim\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m gcloud\n\u001b[32m-> \u001b[39m\u001b[32m1322\u001b[39m tmp = \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43m_prepare_upload_dir\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1323\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 1324\u001b[39m \u001b[38;5;28mself\u001b[39m._job_id = gcloud.upload(tmp, \u001b[33m\"\u001b[39m\u001b[33mpalace\u001b[39m\u001b[33m\"\u001b[39m, verbose=verbose)\n", | ||
| "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/gsim/src/gsim/palace/base.py:1295\u001b[39m, in \u001b[36mPalaceSimMixin._prepare_upload_dir\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1292\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mOutput directory not set. Call set_output_dir() first.\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 1294\u001b[39m \u001b[38;5;66;03m# Always (re)generate config.json to reflect current driven settings\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m1295\u001b[39m \u001b[30;43mself\u001b[39;49m\u001b[30;43m.\u001b[39;49m\u001b[30;43mwrite_config\u001b[39;49m\u001b[30;43m(\u001b[39;49m\u001b[30;43m)\u001b[39;49m\n\u001b[32m 1297\u001b[39m \u001b[38;5;66;03m# Copy input files to a temp dir so we don't destroy the user's directory\u001b[39;00m\n\u001b[32m 1298\u001b[39m tmp = Path(tempfile.mkdtemp(prefix=\u001b[33m\"\u001b[39m\u001b[33mpalace_\u001b[39m\u001b[33m\"\u001b[39m))\n", | ||
| "\u001b[36mFile \u001b[39m\u001b[32m~/Desktop/gsim/src/gsim/palace/base.py:1272\u001b[39m, in \u001b[36mPalaceSimMixin.write_config\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1270\u001b[39m validation = \u001b[38;5;28mself\u001b[39m.validate_mesh()\n\u001b[32m 1271\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m validation.valid:\n\u001b[32m-> \u001b[39m\u001b[32m1272\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mMesh validation failed:\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;132;01m{\u001b[39;00mvalidation\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 1274\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m config_path\n", | ||
| "\u001b[31mValueError\u001b[39m: Mesh validation failed:\nValidation: FAILED\nErrors:\n - No conductor surfaces in mesh. Check that conductor layers have polygons and correct layer_type.\n - config.json has no Conductivity or PEC boundaries.\nWarnings:\n - Volumes: ['air', 'core']" | ||
| ] | ||
| } | ||
| ], | ||
| "source": [ | ||
| "results = sim.run()" | ||
| ] |
There was a problem hiding this comment.
This cell fails with a ValueError during mesh validation, specifically complaining about the absence of conductor surfaces. This seems to indicate that the validation logic in PalaceSimMixin.validate_mesh() doesn't correctly handle purely dielectric simulations, which is common for photonic components like this directional coupler.
Since this notebook serves as an example for the new curved-element meshing feature, it's critical that it runs successfully. The validation should likely be relaxed for simulations that only use wave ports on dielectric materials.
joamatab
left a comment
There was a problem hiding this comment.
LGTM - curved-element meshing and Palace 3D photonics
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #131 +/- ##
==========================================
+ Coverage 58.17% 58.97% +0.79%
==========================================
Files 61 61
Lines 7778 8087 +309
Branches 1487 1570 +83
==========================================
+ Hits 4525 4769 +244
- Misses 2804 2826 +22
- Partials 449 492 +43 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
We didn't get better performance than Meep because the resulting FEM linear system turns out to require a huge number of GMRES iterations (I think it is ill-conditioned due to high dielectric constrast). Using direct solvers (MUMPS, also supported by this PR) is a promising alternative, and results in very good performance for small examples, but RAM memory gets depleted very quickly, so this would need to run on a larger server (perhaps HPC for large photonic problems). |
|
I think we can merge this PR, even if the Photonic example didn't turn out to work right now on the current hardware, because there are some features that could benefit other working cases, and we might want to try again running 3d FEM photonics in the future, on a system with massive RAM. |
This PR implements spline-fitting to replace curve loops, which enables curved-element meshing.
This is potentially interesting for several PDKs with curved features (like QPDK) but maybe most interesting for 3D photonics, for which Palace could lead to better performance than Meep.