diff --git a/Untitled.ipynb b/Untitled.ipynb
new file mode 100644
index 0000000000..0e4602f07e
--- /dev/null
+++ b/Untitled.ipynb
@@ -0,0 +1,495 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "82d68e8b-dfc7-473b-8bd8-8984e93ca9c6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
10:56:59 UTC WARNING: 'UniformHeatSource' is deprecated and will be \n",
+ " discontinued. You can use 'HeatSource' instead. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:56:59 UTC\u001b[0m\u001b[2;36m \u001b[0m\u001b[31mWARNING: \u001b[0m\u001b[32m'UniformHeatSource'\u001b[0m\u001b[31m is deprecated and will be \u001b[0m\n",
+ "\u001b[2;36m \u001b[0m\u001b[31mdiscontinued. You can use \u001b[0m\u001b[32m'HeatSource'\u001b[0m\u001b[31m instead. \u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " WARNING: Setting up deprecated 'HeatSimulation'. Consider defining \n",
+ " 'HeatChargeSimulation' instead. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[31mWARNING: Setting up deprecated \u001b[0m\u001b[32m'HeatSimulation'\u001b[0m\u001b[31m. Consider defining \u001b[0m\n",
+ "\u001b[2;36m \u001b[0m\u001b[32m'HeatChargeSimulation'\u001b[0m\u001b[31m instead. \u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:57:00 UTC Created task 'Thermally tuned waveguide_HEAT' with task_id \n",
+ " 'he-21a543a2-0231-4d59-8088-5f82e869af1a' and task_type 'HEAT'. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:57:00 UTC\u001b[0m\u001b[2;36m \u001b[0mCreated task \u001b[32m'Thermally tuned waveguide_HEAT'\u001b[0m with task_id \n",
+ "\u001b[2;36m \u001b[0m\u001b[32m'he-21a543a2-0231-4d59-8088-5f82e869af1a'\u001b[0m and task_type \u001b[32m'HEAT'\u001b[0m. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Tidy3D's Heat solver is currently in the beta stage. Cost of Heat \n",
+ " simulations is subject to change in the future. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0mTidy3D's Heat solver is currently in the beta stage. Cost of Heat \n",
+ "\u001b[2;36m \u001b[0msimulations is subject to change in the future. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " View task using web UI at \n",
+ " 'https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-\n",
+ " 4d59-8088-5f82e869af1a'. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0mView task using web UI at \n",
+ "\u001b[2;36m \u001b[0m\u001b]8;id=826431;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32m'https://tidy3d.simulation.cloud/workbench?\u001b[0m\u001b]8;;\u001b\\\u001b]8;id=631601;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32mtaskId\u001b[0m\u001b]8;;\u001b\\\u001b]8;id=826431;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32m=\u001b[0m\u001b]8;;\u001b\\\u001b]8;id=237611;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32mhe\u001b[0m\u001b]8;;\u001b\\\u001b]8;id=826431;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32m-21a543a2-0231-\u001b[0m\u001b]8;;\u001b\\\n",
+ "\u001b[2;36m \u001b[0m\u001b]8;id=826431;https://tidy3d.simulation.cloud/workbench?taskId=he-21a543a2-0231-4d59-8088-5f82e869af1a\u001b\\\u001b[32m4d59-8088-5f82e869af1a'\u001b[0m\u001b]8;;\u001b\\. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Task folder: 'default'. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0mTask folder: \u001b]8;id=871903;https://tidy3d.simulation.cloud/folders/9b36e144-ddb6-41f8-8dd8-30b62b26a870\u001b\\\u001b[32m'default'\u001b[0m\u001b]8;;\u001b\\. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "711c37b223764b68932972f94ff4da3c",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Output()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n"
+ ],
+ "text/plain": []
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:57:02 UTC Maximum FlexCredit cost: 0.025. Minimum cost depends on task \n",
+ " execution details. Use 'web.real_cost(task_id)' to get the billed \n",
+ " FlexCredit cost after a simulation run. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:57:02 UTC\u001b[0m\u001b[2;36m \u001b[0mMaximum FlexCredit cost: \u001b[1;36m0.025\u001b[0m. Minimum cost depends on task \n",
+ "\u001b[2;36m \u001b[0mexecution details. Use \u001b[32m'web.real_cost\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtask_id\u001b[0m\u001b[32m)\u001b[0m\u001b[32m'\u001b[0m to get the billed \n",
+ "\u001b[2;36m \u001b[0mFlexCredit cost after a simulation run. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:57:04 UTC status = queued \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:57:04 UTC\u001b[0m\u001b[2;36m \u001b[0mstatus = queued \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " To cancel the simulation, use 'web.abort(task_id)' or \n",
+ " 'web.delete(task_id)' or abort/delete the task in the web UI. \n",
+ " Terminating the Python script will not stop the job running on the \n",
+ " cloud. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0mTo cancel the simulation, use \u001b[32m'web.abort\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtask_id\u001b[0m\u001b[32m)\u001b[0m\u001b[32m'\u001b[0m or \n",
+ "\u001b[2;36m \u001b[0m\u001b[32m'web.delete\u001b[0m\u001b[32m(\u001b[0m\u001b[32mtask_id\u001b[0m\u001b[32m)\u001b[0m\u001b[32m'\u001b[0m or abort/delete the task in the web UI. \n",
+ "\u001b[2;36m \u001b[0mTerminating the Python script will not stop the job running on the \n",
+ "\u001b[2;36m \u001b[0mcloud. \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "fef1e855c1d1405e9848ed7c0b9e1304",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Output()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:57:53 UTC status = preprocess \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:57:53 UTC\u001b[0m\u001b[2;36m \u001b[0mstatus = preprocess \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n"
+ ],
+ "text/plain": []
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:58:04 UTC starting up solver \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:58:04 UTC\u001b[0m\u001b[2;36m \u001b[0mstarting up solver \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " running solver \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0mrunning solver \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:58:28 UTC status = queued \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:58:28 UTC\u001b[0m\u001b[2;36m \u001b[0mstatus = queued \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "62ccbc963acd4d79ad0d1d782384cd20",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ "Output()"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:59:05 UTC status = preprocess \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:59:05 UTC\u001b[0m\u001b[2;36m \u001b[0mstatus = preprocess \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "10:59:14 UTC status = running \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m10:59:14 UTC\u001b[0m\u001b[2;36m \u001b[0mstatus = running \n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "11:00:14 UTC ERROR: Error running task he-21a543a2-0231-4d59-8088-5f82e869af1a! \n",
+ " Error message could not be obtained, please contact customer \n",
+ " support. \n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[2;36m11:00:14 UTC\u001b[0m\u001b[2;36m \u001b[0m\u001b[1;31mERROR: Error running task he-\u001b[0m\u001b[93m21a543a2-0231-4d59-8088-5f82e869af1a\u001b[0m\u001b[1;31m! \u001b[0m\n",
+ "\u001b[2;36m \u001b[0m\u001b[1;31mError message could not be obtained, please contact customer \u001b[0m\n",
+ "\u001b[2;36m \u001b[0m\u001b[1;31msupport. \u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n"
+ ],
+ "text/plain": []
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "ename": "WebError",
+ "evalue": "Error running task he-21a543a2-0231-4d59-8088-5f82e869af1a! Error message could not be obtained, please contact customer support.",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
+ "\u001b[31mWebError\u001b[39m Traceback (most recent call last)",
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 109\u001b[39m\n\u001b[32m 90\u001b[39m wafer = td.Structure(\n\u001b[32m 91\u001b[39m geometry = td.Box(center = [\u001b[32m0\u001b[39m, \u001b[32m0\u001b[39m, -\u001b[32m0.25\u001b[39m], size = [td.inf, td.inf, \u001b[32m0.5\u001b[39m]), \n\u001b[32m 92\u001b[39m name = \u001b[33m'\u001b[39m\u001b[33mwafer\u001b[39m\u001b[33m'\u001b[39m, \n\u001b[32m 93\u001b[39m medium = Si\n\u001b[32m 94\u001b[39m )\n\u001b[32m 97\u001b[39m sim = td.HeatSimulation(\n\u001b[32m 98\u001b[39m center = [\u001b[32m0\u001b[39m, \u001b[32m0\u001b[39m, \u001b[32m2.15\u001b[39m], \n\u001b[32m 99\u001b[39m size = [\u001b[32m18\u001b[39m, \u001b[32m0.04\u001b[39m, \u001b[32m7.3\u001b[39m], \n\u001b[32m (...)\u001b[39m\u001b[32m 107\u001b[39m structures = [box_clad, core, heater, wafer],\n\u001b[32m 108\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m109\u001b[39m sim_data = \u001b[43mweb\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43msim\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtask_name\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mThermally tuned waveguide_HEAT\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpath\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m./data/sim_data.hdf5\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m~/tidy3d/tidy3d/web/api/autograd/autograd.py:222\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(simulation, task_name, folder_name, path, callback_url, verbose, progress_callback_upload, progress_callback_download, solver_version, worker_group, simulation_type, parent_tasks, local_gradient, max_num_adjoint_per_fwd, reduce_simulation, pay_type, priority)\u001b[39m\n\u001b[32m 203\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m is_valid_for_autograd(simulation):\n\u001b[32m 204\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m _run(\n\u001b[32m 205\u001b[39m simulation=simulation,\n\u001b[32m 206\u001b[39m task_name=task_name,\n\u001b[32m (...)\u001b[39m\u001b[32m 219\u001b[39m pay_type=pay_type,\n\u001b[32m 220\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m222\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mrun_webapi\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 223\u001b[39m \u001b[43m \u001b[49m\u001b[43msimulation\u001b[49m\u001b[43m=\u001b[49m\u001b[43msimulation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 224\u001b[39m \u001b[43m \u001b[49m\u001b[43mtask_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtask_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 225\u001b[39m \u001b[43m \u001b[49m\u001b[43mfolder_name\u001b[49m\u001b[43m=\u001b[49m\u001b[43mfolder_name\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 226\u001b[39m \u001b[43m \u001b[49m\u001b[43mpath\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpath\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 227\u001b[39m \u001b[43m \u001b[49m\u001b[43mcallback_url\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcallback_url\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 228\u001b[39m \u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[43m=\u001b[49m\u001b[43mverbose\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 229\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogress_callback_upload\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprogress_callback_upload\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 230\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogress_callback_download\u001b[49m\u001b[43m=\u001b[49m\u001b[43mprogress_callback_download\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 231\u001b[39m \u001b[43m \u001b[49m\u001b[43msolver_version\u001b[49m\u001b[43m=\u001b[49m\u001b[43msolver_version\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 232\u001b[39m \u001b[43m \u001b[49m\u001b[43mworker_group\u001b[49m\u001b[43m=\u001b[49m\u001b[43mworker_group\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 233\u001b[39m \u001b[43m \u001b[49m\u001b[43msimulation_type\u001b[49m\u001b[43m=\u001b[49m\u001b[43msimulation_type\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 234\u001b[39m \u001b[43m \u001b[49m\u001b[43mparent_tasks\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparent_tasks\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 235\u001b[39m \u001b[43m \u001b[49m\u001b[43mreduce_simulation\u001b[49m\u001b[43m=\u001b[49m\u001b[43mreduce_simulation\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 236\u001b[39m \u001b[43m \u001b[49m\u001b[43mpay_type\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpay_type\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 237\u001b[39m \u001b[43m \u001b[49m\u001b[43mpriority\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpriority\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 238\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m~/tidy3d/tidy3d/web/api/connect_util.py:36\u001b[39m, in \u001b[36mwait_for_connection..decorator..web_fn_wrapped\u001b[39m\u001b[34m(*args, **kwargs)\u001b[39m\n\u001b[32m 34\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m (time.time() - time_start) < wait_time_sec:\n\u001b[32m 35\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m36\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mweb_fn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 37\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (ConnErr, \u001b[38;5;167;01mConnectionError\u001b[39;00m, NewConnectionError, ReadTimeout, JSONDecodeError):\n\u001b[32m 38\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m warned_previously:\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m~/tidy3d/tidy3d/web/api/webapi.py:186\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(simulation, task_name, folder_name, path, callback_url, verbose, progress_callback_upload, progress_callback_download, solver_version, worker_group, simulation_type, parent_tasks, reduce_simulation, pay_type, priority)\u001b[39m\n\u001b[32m 167\u001b[39m task_id = upload(\n\u001b[32m 168\u001b[39m simulation=simulation,\n\u001b[32m 169\u001b[39m task_name=task_name,\n\u001b[32m (...)\u001b[39m\u001b[32m 177\u001b[39m reduce_simulation=reduce_simulation,\n\u001b[32m 178\u001b[39m )\n\u001b[32m 179\u001b[39m start(\n\u001b[32m 180\u001b[39m task_id,\n\u001b[32m 181\u001b[39m solver_version=solver_version,\n\u001b[32m (...)\u001b[39m\u001b[32m 184\u001b[39m priority=priority,\n\u001b[32m 185\u001b[39m )\n\u001b[32m--> \u001b[39m\u001b[32m186\u001b[39m \u001b[43mmonitor\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtask_id\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mverbose\u001b[49m\u001b[43m=\u001b[49m\u001b[43mverbose\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 187\u001b[39m data = load(\n\u001b[32m 188\u001b[39m task_id=task_id, path=path, verbose=verbose, progress_callback=progress_callback_download\n\u001b[32m 189\u001b[39m )\n\u001b[32m 190\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(simulation, ModeSolver):\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m~/tidy3d/tidy3d/web/api/webapi.py:620\u001b[39m, in \u001b[36mmonitor\u001b[39m\u001b[34m(task_id, verbose)\u001b[39m\n\u001b[32m 618\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m console.status(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m[bold green]Finishing \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtask_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m...\u001b[39m\u001b[33m\"\u001b[39m, spinner=\u001b[33m\"\u001b[39m\u001b[33mrunner\u001b[39m\u001b[33m\"\u001b[39m):\n\u001b[32m 619\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m status \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m break_statuses:\n\u001b[32m--> \u001b[39m\u001b[32m620\u001b[39m new_status = \u001b[43mget_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtask_id\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 621\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m new_status != status:\n\u001b[32m 622\u001b[39m status = new_status\n",
+ "\u001b[36mFile \u001b[39m\u001b[32m~/tidy3d/tidy3d/web/api/webapi.py:461\u001b[39m, in \u001b[36mget_status\u001b[39m\u001b[34m(task_id)\u001b[39m\n\u001b[32m 457\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m:\n\u001b[32m 458\u001b[39m \u001b[38;5;66;03m# If the error message could not be obtained, raise a generic error message\u001b[39;00m\n\u001b[32m 459\u001b[39m error_msg = \u001b[33m\"\u001b[39m\u001b[33mError message could not be obtained, please contact customer support.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m461\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m WebError(\u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mError running task \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtask_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m! \u001b[39m\u001b[38;5;132;01m{\u001b[39;00merror_msg\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m)\n\u001b[32m 462\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m status\n",
+ "\u001b[31mWebError\u001b[39m: Error running task he-21a543a2-0231-4d59-8088-5f82e869af1a! Error message could not be obtained, please contact customer support."
+ ]
+ }
+ ],
+ "source": [
+ "from __future__ import annotations\n",
+ "\n",
+ "import tidy3d as td\n",
+ "import tidy3d.web as web\n",
+ "\n",
+ "# import need be changed in some cases\n",
+ "\n",
+ "\n",
+ "w_sim = 16\n",
+ "h_buffer = 1\n",
+ "h_clad = 2.8\n",
+ "h_box = 2\n",
+ "h_wafer = 0.5\n",
+ "w_core = 0.5\n",
+ "h_core = 0.22\n",
+ "h_heater = 0.14\n",
+ "w_heater = 2\n",
+ "d_heater = 2\n",
+ "z_wafer = -h_wafer / 2\n",
+ "z_core = h_box + h_core / 2\n",
+ "z_heater = h_box + h_core + d_heater + h_heater / 2\n",
+ "box_clad_max = h_box + h_clad\n",
+ "box_clad_min = -h_wafer / 2\n",
+ "h_box_clad = box_clad_max - box_clad_min\n",
+ "z_box_clad = (box_clad_max + box_clad_min) / 2\n",
+ "z_sim = (h_box + h_clad - h_wafer) / 2\n",
+ "h_sim = h_box + h_clad + h_wafer + 2 * h_buffer\n",
+ "current = 7.4e-3\n",
+ "dl_min = h_heater / 3\n",
+ "dl_max = 4 * dl_min\n",
+ "wvl_um = 1.55\n",
+ "freq0 = td.C_0 / wvl_um\n",
+ "heat_sim_buffer = 2\n",
+ "air = td.Medium(\n",
+ " name=\"air\",\n",
+ " heat_spec=td.FluidSpec(),\n",
+ ")\n",
+ "\n",
+ "uniformheatsource_0 = td.UniformHeatSource(\n",
+ " name=\"uniformheatsource_0\", structures=[\"heater\"], rate=0.000303\n",
+ ")\n",
+ "\n",
+ "temp_mnt = td.TemperatureMonitor(\n",
+ " name=\"temp_mnt\",\n",
+ " size=[td.inf, 0, td.inf],\n",
+ ")\n",
+ "\n",
+ "temp_mnt_structured = td.TemperatureMonitor(\n",
+ " name=\"temp_mnt_structured\",\n",
+ " size=[td.inf, 0, td.inf],\n",
+ " unstructured=True,\n",
+ " conformal=False,\n",
+ ")\n",
+ "\n",
+ "SiO2 = td.PerturbationMedium(\n",
+ " name=\"SiO2\",\n",
+ " heat_spec=td.SolidSpec(conductivity=0.00000138),\n",
+ " permittivity=2.085136,\n",
+ " perturbation_spec=td.IndexPerturbation(\n",
+ " delta_n=td.ParameterPerturbation(\n",
+ " heat=td.LinearHeatPerturbation(temperature_ref=300, coeff=0.00001),\n",
+ " ),\n",
+ " freq=193414493734202.56,\n",
+ " ),\n",
+ ")\n",
+ "box_clad = td.Structure(\n",
+ " geometry=td.Box(center=[0, 0, 2.275], size=[td.inf, td.inf, 5.05]), name=\"box_clad\", medium=SiO2\n",
+ ")\n",
+ "\n",
+ "Si = td.PerturbationMedium(\n",
+ " name=\"Si\",\n",
+ " heat_spec=td.SolidSpec(conductivity=0.000148),\n",
+ " permittivity=12.09439729,\n",
+ " perturbation_spec=td.IndexPerturbation(\n",
+ " delta_n=td.ParameterPerturbation(\n",
+ " heat=td.LinearHeatPerturbation(temperature_ref=300, coeff=0.000186),\n",
+ " ),\n",
+ " freq=193414493734202.56,\n",
+ " ),\n",
+ ")\n",
+ "core = td.Structure(\n",
+ " geometry=td.Box(center=[0, 0, 2.11], size=[0.5, td.inf, 0.22]), name=\"core\", medium=Si\n",
+ ")\n",
+ "\n",
+ "TiN = td.PECMedium(\n",
+ " name=\"TiN\",\n",
+ " heat_spec=td.SolidSpec(conductivity=0.000028),\n",
+ ")\n",
+ "heater = td.Structure(\n",
+ " geometry=td.Box(center=[0, 0, 4.29], size=[2, td.inf, 0.14]), name=\"heater\", medium=TiN\n",
+ ")\n",
+ "\n",
+ "wafer = td.Structure(\n",
+ " geometry=td.Box(center=[0, 0, -0.25], size=[td.inf, td.inf, 0.5]), name=\"wafer\", medium=Si\n",
+ ")\n",
+ "\n",
+ "\n",
+ "sim = td.HeatSimulation(\n",
+ " center=[0, 0, 2.15],\n",
+ " size=[18, 0.04, 7.3],\n",
+ " symmetry=[1, 0, 0],\n",
+ " boundary_spec=[\n",
+ " td.HeatBoundarySpec(\n",
+ " placement=td.MediumMediumInterface(mediums=[\"Si\", \"air\"]),\n",
+ " condition=td.TemperatureBC(\n",
+ " temperature=300,\n",
+ " ),\n",
+ " ),\n",
+ " td.HeatBoundarySpec(\n",
+ " placement=td.MediumMediumInterface(mediums=[\"SiO2\", \"air\"]),\n",
+ " condition=td.HeatFluxBC(\n",
+ " flux=0,\n",
+ " ),\n",
+ " ),\n",
+ " ],\n",
+ " grid_spec=td.DistanceUnstructuredGrid(\n",
+ " dl_interface=0.046666,\n",
+ " dl_bulk=0.186664,\n",
+ " distance_interface=0.139998,\n",
+ " distance_bulk=4,\n",
+ " non_refined_structures=[\"wafer\"],\n",
+ " relative_min_dl=0,\n",
+ " ),\n",
+ " version=\"2.9.0\",\n",
+ " medium=air,\n",
+ " sources=[uniformheatsource_0],\n",
+ " monitors=[temp_mnt, temp_mnt_structured],\n",
+ " structures=[box_clad, core, heater, wafer],\n",
+ ")\n",
+ "sim_data = web.run(sim, task_name=\"Thermally tuned waveguide_HEAT\", path=\"./data/sim_data.hdf5\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6d7371d4-74ce-46a8-9c79-d6a4dda8d32c",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "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.11.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/faq b/docs/faq
index 96107d9f4a..90eb3869d0 160000
--- a/docs/faq
+++ b/docs/faq
@@ -1 +1 @@
-Subproject commit 96107d9f4ad82789a4b92a7df898684dddd90f25
+Subproject commit 90eb3869d0fe7d3cb8f7917ab657931a1083e1ed
diff --git a/pyproject.toml b/pyproject.toml
index b4be4d48de..f11d67048a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tidy3d"
-version = "2.9.0"
+version = "2.10.0rc1"
description = "A fast FDTD solver"
authors = ["Tyler Hughes "]
license = "LGPLv2+"
diff --git a/schemas/Simulation.json b/schemas/Simulation.json
index be73014743..ef9a437cd1 100644
--- a/schemas/Simulation.json
+++ b/schemas/Simulation.json
@@ -9635,7 +9635,7 @@
},
"PointDipole": {
"title": "PointDipole",
- "description": "Uniform current source with a zero size. The source corresponds to an infinitesimal antenna\nwith a fixed current density, and is slightly different from a related definition that is used\nin some contexts, namely an oscillating electric or magnetic dipole. The two are related through\na factor of ``omega ** 2`` in the power normalization, where ``omega`` is the angular frequency\nof the oscillation. This is discussed further in our\n`source normalization <../../faq/docs/faq/How-are-results-normalized.html>`_ FAQ page.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Tuple[Literal[0], Literal[0], Literal[0]] = (0, 0, 0)\n [units = um]. Size in x, y, and z directions, constrained to ``(0, 0, 0)``.\nsource_time : Union[GaussianPulse, ContinuousWave, CustomSourceTime]\n Specification of the source time-dependence.\ninterpolate : bool = True\n Handles reverse-interpolation of zero-size dimensions of the source. If ``False``, the source data is snapped to the nearest Yee grid point. If ``True``, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation.\nconfine_to_bounds : bool = False\n If ``True``, any source amplitudes which, after discretization, fall beyond the bounding box of the source are zeroed out, but only along directions where the source has a non-zero extent. The bounding box is inclusive. Should be set ```True`` when the current source is being used to excite a current in a conductive material.\npolarization : Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Specifies the direction and type of current component.\n\n..\n TODO add image of how it looks like based on sim 1.\n\nExample\n-------\n>>> from tidy3d import GaussianPulse\n>>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)\n>>> pt_dipole = PointDipole(center=(1,2,3), source_time=pulse, polarization='Ex')\n\nSee Also\n--------\n\n**Notebooks**\n * `Particle swarm optimization of quantum emitter light extraction to free space <../../notebooks/BullseyeCavityPSO.html>`_\n * `Adjoint optimization of quantum emitter light extraction to an integrated waveguide <../../notebooks/AdjointPlugin12LightExtractor.html>`_",
+ "description": "Uniform current source with a zero size. The source corresponds to an infinitesimal antenna\nwith a fixed current density, and is slightly different from a related definition that is used\nin some contexts, namely an oscillating electric or magnetic dipole. The two are related through\na factor of ``omega ** 2`` in the power normalization, where ``omega`` is the angular frequency\nof the oscillation. This is discussed further in our\n`source normalization <../../faq/docs/faq/How-are-results-normalized.html>`_ FAQ page.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Tuple[typing_extensions.Literal[0], typing_extensions.Literal[0], typing_extensions.Literal[0]] = (0, 0, 0)\n [units = um]. Size in x, y, and z directions, constrained to ``(0, 0, 0)``.\nsource_time : Union[GaussianPulse, ContinuousWave, CustomSourceTime]\n Specification of the source time-dependence.\ninterpolate : bool = True\n Handles reverse-interpolation of zero-size dimensions of the source. If ``False``, the source data is snapped to the nearest Yee grid point. If ``True``, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation.\nconfine_to_bounds : bool = False\n If ``True``, any source amplitudes which, after discretization, fall beyond the bounding box of the source are zeroed out, but only along directions where the source has a non-zero extent. The bounding box is inclusive. Should be set ```True`` when the current source is being used to excite a current in a conductive material.\npolarization : Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Specifies the direction and type of current component.\n\n..\n TODO add image of how it looks like based on sim 1.\n\nExample\n-------\n>>> from tidy3d import GaussianPulse\n>>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)\n>>> pt_dipole = PointDipole(center=(1,2,3), source_time=pulse, polarization='Ex')\n\nSee Also\n--------\n\n**Notebooks**\n * `Particle swarm optimization of quantum emitter light extraction to free space <../../notebooks/BullseyeCavityPSO.html>`_\n * `Adjoint optimization of quantum emitter light extraction to an integrated waveguide <../../notebooks/AdjointPlugin12LightExtractor.html>`_",
"type": "object",
"properties": {
"attrs": {
diff --git a/schemas/TerminalComponentModeler.json b/schemas/TerminalComponentModeler.json
index 939db7ce95..d3515300c5 100644
--- a/schemas/TerminalComponentModeler.json
+++ b/schemas/TerminalComponentModeler.json
@@ -1,6 +1,6 @@
{
"title": "TerminalComponentModeler",
- "description": "Tool for modeling two-terminal multiport devices and computing port parameters\nwith lumped and wave ports.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : Simulation\n Simulation describing the device without any sources present.\nports : Tuple[Union[LumpedPort, CoaxialLumpedPort, WavePort], ...] = ()\n Collection of lumped and wave ports associated with the network. For each port, one simulation will be run with a source that is associated with the port.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n [units = Hz]. Array or list of frequencies at which to compute port parameters.\nremove_dc_component : bool = True\n Whether to remove the DC component in the Gaussian pulse spectrum. If ``True``, the Gaussian pulse is modified at low frequencies to zero out the DC component, which is usually desirable so that the fields will decay. However, for broadband simulations, it may be better to have non-vanishing source power near zero frequency. Setting this to ``False`` results in an unmodified Gaussian pulse spectrum which can have a nonzero DC component.\nfolder_name : str = default\n Name of the folder for the tasks on web.\nverbose : bool = False\n Whether the :class:`.AbstractComponentModeler` should print status and progressbars.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\npath_dir : str = .\n Base directory where data and batch will be downloaded.\nsolver_version : Optional[str] = None\n batch_cached : Optional[Batch] = None\n Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\nradiation_monitors : Tuple[DirectivityMonitor, ...] = ()\n Facilitates the calculation of figures-of-merit for antennas. These monitor will be included in every simulation and record the radiated fields. ",
+ "description": "Tool for modeling two-terminal multiport devices and computing port parameters\nwith lumped and wave ports.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : str = \n Simulation describing the device without any sources present.\nsimulation : Simulation\n Simulation describing the device without any sources present.\nports : Tuple[Union[LumpedPort, CoaxialLumpedPort, WavePort], ...] = ()\n Collection of lumped and wave ports associated with the network. For each port, one simulation will be run with a source that is associated with the port.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n [units = Hz]. Array or list of frequencies at which to compute port parameters.\nremove_dc_component : bool = True\n Whether to remove the DC component in the Gaussian pulse spectrum. If ``True``, the Gaussian pulse is modified at low frequencies to zero out the DC component, which is usually desirable so that the fields will decay. However, for broadband simulations, it may be better to have non-vanishing source power near zero frequency. Setting this to ``False`` results in an unmodified Gaussian pulse spectrum which can have a nonzero DC component.\nfolder_name : str = default\n Name of the folder for the tasks on web.\nverbose : bool = False\n Whether the :class:`.AbstractComponentModeler` should print status and progressbars.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\npath_dir : str = .\n Base directory where data and batch will be downloaded.\nsolver_version : Optional[str] = None\n batch_cached : Optional[Any] = None\n DEPRECATED: Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\nradiation_monitors : Tuple[DirectivityMonitor, ...] = ()\n Facilitates the calculation of figures-of-merit for antennas. These monitor will be included in every simulation and record the radiated fields. ",
"type": "object",
"properties": {
"attrs": {
@@ -9,6 +9,12 @@
"default": {},
"type": "object"
},
+ "name": {
+ "title": "Simulation",
+ "description": "Simulation describing the device without any sources present.",
+ "default": "",
+ "type": "string"
+ },
"simulation": {
"title": "Simulation",
"description": "Simulation describing the device without any sources present.",
@@ -89,12 +95,7 @@
},
"batch_cached": {
"title": "Batch (Cached)",
- "description": "Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.",
- "allOf": [
- {
- "$ref": "#/definitions/Batch"
- }
- ]
+ "description": "DEPRECATED: Optional field to specify ``batch``. Only used as a workaround internally so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded from ``.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors."
},
"type": {
"title": "Type",
@@ -8983,7 +8984,7 @@
},
"PointDipole": {
"title": "PointDipole",
- "description": "Uniform current source with a zero size. The source corresponds to an infinitesimal antenna\nwith a fixed current density, and is slightly different from a related definition that is used\nin some contexts, namely an oscillating electric or magnetic dipole. The two are related through\na factor of ``omega ** 2`` in the power normalization, where ``omega`` is the angular frequency\nof the oscillation. This is discussed further in our\n`source normalization <../../faq/docs/faq/How-are-results-normalized.html>`_ FAQ page.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Tuple[Literal[0], Literal[0], Literal[0]] = (0, 0, 0)\n [units = um]. Size in x, y, and z directions, constrained to ``(0, 0, 0)``.\nsource_time : Union[GaussianPulse, ContinuousWave, CustomSourceTime]\n Specification of the source time-dependence.\ninterpolate : bool = True\n Handles reverse-interpolation of zero-size dimensions of the source. If ``False``, the source data is snapped to the nearest Yee grid point. If ``True``, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation.\nconfine_to_bounds : bool = False\n If ``True``, any source amplitudes which, after discretization, fall beyond the bounding box of the source are zeroed out, but only along directions where the source has a non-zero extent. The bounding box is inclusive. Should be set ```True`` when the current source is being used to excite a current in a conductive material.\npolarization : Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Specifies the direction and type of current component.\n\n..\n TODO add image of how it looks like based on sim 1.\n\nExample\n-------\n>>> from tidy3d import GaussianPulse\n>>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)\n>>> pt_dipole = PointDipole(center=(1,2,3), source_time=pulse, polarization='Ex')\n\nSee Also\n--------\n\n**Notebooks**\n * `Particle swarm optimization of quantum emitter light extraction to free space <../../notebooks/BullseyeCavityPSO.html>`_\n * `Adjoint optimization of quantum emitter light extraction to an integrated waveguide <../../notebooks/AdjointPlugin12LightExtractor.html>`_",
+ "description": "Uniform current source with a zero size. The source corresponds to an infinitesimal antenna\nwith a fixed current density, and is slightly different from a related definition that is used\nin some contexts, namely an oscillating electric or magnetic dipole. The two are related through\na factor of ``omega ** 2`` in the power normalization, where ``omega`` is the angular frequency\nof the oscillation. This is discussed further in our\n`source normalization <../../faq/docs/faq/How-are-results-normalized.html>`_ FAQ page.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Tuple[typing_extensions.Literal[0], typing_extensions.Literal[0], typing_extensions.Literal[0]] = (0, 0, 0)\n [units = um]. Size in x, y, and z directions, constrained to ``(0, 0, 0)``.\nsource_time : Union[GaussianPulse, ContinuousWave, CustomSourceTime]\n Specification of the source time-dependence.\ninterpolate : bool = True\n Handles reverse-interpolation of zero-size dimensions of the source. If ``False``, the source data is snapped to the nearest Yee grid point. If ``True``, equivalent source data is applied on the surrounding Yee grid points to emulate placement at the specified location using linear interpolation.\nconfine_to_bounds : bool = False\n If ``True``, any source amplitudes which, after discretization, fall beyond the bounding box of the source are zeroed out, but only along directions where the source has a non-zero extent. The bounding box is inclusive. Should be set ```True`` when the current source is being used to excite a current in a conductive material.\npolarization : Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Specifies the direction and type of current component.\n\n..\n TODO add image of how it looks like based on sim 1.\n\nExample\n-------\n>>> from tidy3d import GaussianPulse\n>>> pulse = GaussianPulse(freq0=200e12, fwidth=20e12)\n>>> pt_dipole = PointDipole(center=(1,2,3), source_time=pulse, polarization='Ex')\n\nSee Also\n--------\n\n**Notebooks**\n * `Particle swarm optimization of quantum emitter light extraction to free space <../../notebooks/BullseyeCavityPSO.html>`_\n * `Adjoint optimization of quantum emitter light extraction to an integrated waveguide <../../notebooks/AdjointPlugin12LightExtractor.html>`_",
"type": "object",
"properties": {
"attrs": {
@@ -19114,6470 +19115,6 @@
"direction"
],
"additionalProperties": false
- },
- "HeatSource": {
- "title": "HeatSource",
- "description": "Adds a volumetric heat source (heat sink if negative values\nare provided) to specific structures in the scene.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\nstructures : Tuple[str, ...]\n Names of structures where to apply heat source.\nrate : Union[float, SpatialDataArray]\n [units = W/um^3]. Volumetric rate of heating or cooling (if negative) in units of W/um^3.\n\nExample\n-------\n>>> heat_source = HeatSource(rate=1, structures=[\"box\"])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "name": {
- "title": "Name",
- "description": "Optional name for the source.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "HeatSource",
- "enum": [
- "HeatSource"
- ],
- "type": "string"
- },
- "structures": {
- "title": "Target Structures",
- "description": "Names of structures where to apply heat source.",
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "rate": {
- "title": "Volumetric Heat Rate",
- "description": "Volumetric rate of heating or cooling (if negative) in units of W/um^3.",
- "units": "W/um^3",
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "DataArray",
- "type": "xr.DataArray",
- "properties": {
- "_dims": {
- "title": "_dims",
- "type": "Tuple[str, ...]"
- }
- },
- "required": [
- "_dims"
- ]
- }
- ]
- }
- },
- "required": [
- "structures",
- "rate"
- ],
- "additionalProperties": false
- },
- "HeatFromElectricSource": {
- "title": "HeatFromElectricSource",
- "description": "Volumetric heat source generated from an electric simulation.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\n\nNotes\n-----\n\n If a :class`HeatFromElectricSource` is specified as a source, appropriate boundary\n conditions for an electric simulation must be provided, since such a simulation\n will be executed before the heat simulation can run.\n\nExample\n-------\n>>> heat_source = HeatFromElectricSource()",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "name": {
- "title": "Name",
- "description": "Optional name for the source.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "HeatFromElectricSource",
- "enum": [
- "HeatFromElectricSource"
- ],
- "type": "string"
- }
- },
- "additionalProperties": false
- },
- "UniformHeatSource": {
- "title": "UniformHeatSource",
- "description": "Volumetric heat source. This class is deprecated. You can use\n'HeatSource' instead.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n Optional name for the source.\nstructures : Tuple[str, ...]\n Names of structures where to apply heat source.\nrate : Union[float, SpatialDataArray]\n [units = W/um^3]. Volumetric rate of heating or cooling (if negative) in units of W/um^3.\n\nExample\n-------\n>>> heat_source = UniformHeatSource(rate=1, structures=[\"box\"]) # doctest: +SKIP",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "name": {
- "title": "Name",
- "description": "Optional name for the source.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "UniformHeatSource",
- "enum": [
- "UniformHeatSource"
- ],
- "type": "string"
- },
- "structures": {
- "title": "Target Structures",
- "description": "Names of structures where to apply heat source.",
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "rate": {
- "title": "Volumetric Heat Rate",
- "description": "Volumetric rate of heating or cooling (if negative) in units of W/um^3.",
- "units": "W/um^3",
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "DataArray",
- "type": "xr.DataArray",
- "properties": {
- "_dims": {
- "title": "_dims",
- "type": "Tuple[str, ...]"
- }
- },
- "required": [
- "_dims"
- ]
- }
- ]
- }
- },
- "required": [
- "structures",
- "rate"
- ],
- "additionalProperties": false
- },
- "StructureBoundary": {
- "title": "StructureBoundary",
- "description": "Placement of boundary conditions on the structure's boundary.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nstructure : str\n Name of the structure.\n\nExample\n-------\n>>> bc_placement = StructureBoundary(structure=\"box\")",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "StructureBoundary",
- "enum": [
- "StructureBoundary"
- ],
- "type": "string"
- },
- "structure": {
- "title": "Structure Name",
- "description": "Name of the structure.",
- "type": "string"
- }
- },
- "required": [
- "structure"
- ],
- "additionalProperties": false
- },
- "StructureStructureInterface": {
- "title": "StructureStructureInterface",
- "description": "Placement of boundary conditions between two structures.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nstructures : Tuple[str, str]\n Names of two structures.\n\nExample\n-------\n>>> bc_placement = StructureStructureInterface(structures=[\"box\", \"sphere\"])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "StructureStructureInterface",
- "enum": [
- "StructureStructureInterface"
- ],
- "type": "string"
- },
- "structures": {
- "title": "Structures",
- "description": "Names of two structures.",
- "type": "array",
- "minItems": 2,
- "maxItems": 2,
- "items": [
- {
- "type": "string"
- },
- {
- "type": "string"
- }
- ]
- }
- },
- "required": [
- "structures"
- ],
- "additionalProperties": false
- },
- "MediumMediumInterface": {
- "title": "MediumMediumInterface",
- "description": "Placement of boundary conditions between two mediums.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nmediums : Tuple[str, str]\n Names of two mediums.\n\nExample\n-------\n>>> bc_placement = MediumMediumInterface(mediums=[\"dieletric\", \"metal\"])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "MediumMediumInterface",
- "enum": [
- "MediumMediumInterface"
- ],
- "type": "string"
- },
- "mediums": {
- "title": "Mediums",
- "description": "Names of two mediums.",
- "type": "array",
- "minItems": 2,
- "maxItems": 2,
- "items": [
- {
- "type": "string"
- },
- {
- "type": "string"
- }
- ]
- }
- },
- "required": [
- "mediums"
- ],
- "additionalProperties": false
- },
- "SimulationBoundary": {
- "title": "SimulationBoundary",
- "description": "Placement of boundary conditions on the simulation box boundary.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsurfaces : Tuple[Literal['x-', 'x+', 'y-', 'y+', 'z-', 'z+'], ...] = ('x-', 'x+', 'y-', 'y+', 'z-', 'z+')\n Surfaces of simulation domain where to apply boundary conditions.\n\nExample\n-------\n>>> bc_placement = SimulationBoundary(surfaces=[\"x-\", \"x+\"])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SimulationBoundary",
- "enum": [
- "SimulationBoundary"
- ],
- "type": "string"
- },
- "surfaces": {
- "title": "Surfaces",
- "description": "Surfaces of simulation domain where to apply boundary conditions.",
- "default": [
- "x-",
- "x+",
- "y-",
- "y+",
- "z-",
- "z+"
- ],
- "type": "array",
- "items": {
- "enum": [
- "x-",
- "x+",
- "y-",
- "y+",
- "z-",
- "z+"
- ],
- "type": "string"
- }
- }
- },
- "additionalProperties": false
- },
- "StructureSimulationBoundary": {
- "title": "StructureSimulationBoundary",
- "description": "Placement of boundary conditions on the simulation box boundary covered by the structure.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nstructure : str\n Name of the structure.\nsurfaces : Tuple[Literal['x-', 'x+', 'y-', 'y+', 'z-', 'z+'], ...] = ('x-', 'x+', 'y-', 'y+', 'z-', 'z+')\n Surfaces of simulation domain where to apply boundary conditions.\n\nExample\n-------\n>>> bc_placement = StructureSimulationBoundary(structure=\"box\", surfaces=[\"y-\", \"y+\"])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "StructureSimulationBoundary",
- "enum": [
- "StructureSimulationBoundary"
- ],
- "type": "string"
- },
- "structure": {
- "title": "Structure Name",
- "description": "Name of the structure.",
- "type": "string"
- },
- "surfaces": {
- "title": "Surfaces",
- "description": "Surfaces of simulation domain where to apply boundary conditions.",
- "default": [
- "x-",
- "x+",
- "y-",
- "y+",
- "z-",
- "z+"
- ],
- "type": "array",
- "items": {
- "enum": [
- "x-",
- "x+",
- "y-",
- "y+",
- "z-",
- "z+"
- ],
- "type": "string"
- }
- }
- },
- "required": [
- "structure"
- ],
- "additionalProperties": false
- },
- "TemperatureBC": {
- "title": "TemperatureBC",
- "description": "Constant temperature thermal boundary conditions.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ntemperature : PositiveFloat\n [units = K]. Temperature value in units of K.\n\nExample\n-------\n>>> import tidy3d as td\n>>> bc = td.TemperatureBC(temperature=300)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "TemperatureBC",
- "enum": [
- "TemperatureBC"
- ],
- "type": "string"
- },
- "temperature": {
- "title": "Temperature",
- "description": "Temperature value in units of K.",
- "units": "K",
- "exclusiveMinimum": 0,
- "type": "number"
- }
- },
- "required": [
- "temperature"
- ],
- "additionalProperties": false
- },
- "HeatFluxBC": {
- "title": "HeatFluxBC",
- "description": "Constant flux thermal boundary conditions.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nflux : float\n [units = W/um^2]. Heat flux value in units of W/um^2.\n\nExample\n-------\n>>> import tidy3d as td\n>>> bc = td.HeatFluxBC(flux=1)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "HeatFluxBC",
- "enum": [
- "HeatFluxBC"
- ],
- "type": "string"
- },
- "flux": {
- "title": "Heat Flux",
- "description": "Heat flux value in units of W/um^2.",
- "units": "W/um^2",
- "type": "number"
- }
- },
- "required": [
- "flux"
- ],
- "additionalProperties": false
- },
- "ConvectionBC": {
- "title": "ConvectionBC",
- "description": "Convective thermal boundary conditions.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nambient_temperature : PositiveFloat\n [units = K]. Ambient temperature value in units of K.\ntransfer_coeff : NonNegativeFloat\n [units = W/(um^2*K)]. Heat flux value in units of W/(um^2*K).\n\nExample\n-------\n>>> import tidy3d as td\n>>> bc = td.ConvectionBC(ambient_temperature=300, transfer_coeff=1)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "ConvectionBC",
- "enum": [
- "ConvectionBC"
- ],
- "type": "string"
- },
- "ambient_temperature": {
- "title": "Ambient Temperature",
- "description": "Ambient temperature value in units of K.",
- "units": "K",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "transfer_coeff": {
- "title": "Heat Transfer Coefficient",
- "description": "Heat flux value in units of W/(um^2*K).",
- "units": "W/(um^2*K)",
- "minimum": 0,
- "type": "number"
- }
- },
- "required": [
- "ambient_temperature",
- "transfer_coeff"
- ],
- "additionalProperties": false
- },
- "DCVoltageSource": {
- "title": "DCVoltageSource",
- "description": "DC voltage source in volts.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n voltage : ArrayLike[dtype=float, ndim=1]\n [units = V]. DC voltage usually used as source in 'VoltageBC' boundary conditions.\nunits : Literal['V'] = V\n \nNotes\n-----\n\n This voltage refers to potential above the equivalent simulation ground. Currently, electrical ports\n are not defined.\n\nExamples\n--------\n>>> import tidy3d as td\n>>> voltages = [-0.5, 0, 1, 2, 3, 4]\n>>> voltage_source = td.DCVoltageSource(voltage=voltages)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "name": {
- "title": "Name",
- "type": "string"
- },
- "voltage": {
- "title": "Voltage",
- "description": "DC voltage usually used as source in 'VoltageBC' boundary conditions.",
- "units": "V",
- "type": "ArrayLike"
- },
- "units": {
- "title": "Units",
- "default": "V",
- "enum": [
- "V"
- ],
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "DCVoltageSource",
- "enum": [
- "DCVoltageSource"
- ],
- "type": "string"
- }
- },
- "required": [
- "voltage"
- ],
- "additionalProperties": false
- },
- "VoltageBC": {
- "title": "VoltageBC",
- "description": "Constant electric potential (voltage) :math:`= \\text{V}` boundary condition.\nSets a potential at the specified boundary.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsource : DCVoltageSource\n [units = V]. Electric potential to be applied at the specified boundary.\n\nNotes\n-----\n\n In charge simulations it also accepts an array of voltages.\n In this case, a solution for each of these voltages will\n be computed.\n\nExample\n-------\n>>> import tidy3d as td\n>>> voltage_source = td.DCVoltageSource(voltage=1)\n>>> voltage_bc = td.VoltageBC(source=voltage_source)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "VoltageBC",
- "enum": [
- "VoltageBC"
- ],
- "type": "string"
- },
- "source": {
- "title": "Voltage",
- "description": "Electric potential to be applied at the specified boundary.",
- "units": "V",
- "allOf": [
- {
- "$ref": "#/definitions/DCVoltageSource"
- }
- ]
- }
- },
- "required": [
- "source"
- ],
- "additionalProperties": false
- },
- "DCCurrentSource": {
- "title": "DCCurrentSource",
- "description": "DC current source in amperes.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nname : Optional[str] = None\n current : FiniteFloat\n [units = A]. DC current usually used as source in 'CurrentBC' boundary conditions.\nunits : Literal['A'] = A\n \nExample\n-------\n>>> import tidy3d as td\n>>> current_source = td.DCCurrentSource(current=0.4)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "name": {
- "title": "Name",
- "type": "string"
- },
- "current": {
- "title": "Current",
- "description": "DC current usually used as source in 'CurrentBC' boundary conditions.",
- "units": "A",
- "type": "number"
- },
- "units": {
- "title": "Units",
- "default": "A",
- "enum": [
- "A"
- ],
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "DCCurrentSource",
- "enum": [
- "DCCurrentSource"
- ],
- "type": "string"
- }
- },
- "required": [
- "current"
- ],
- "additionalProperties": false
- },
- "CurrentBC": {
- "title": "CurrentBC",
- "description": "Current boundary conditions.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsource : DCCurrentSource\n [units = A/um^2]. A current source\n\nExample\n-------\n>>> import tidy3d as td\n>>> current_source = td.DCCurrentSource(current=1)\n>>> current_bc = CurrentBC(source=current_source)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "CurrentBC",
- "enum": [
- "CurrentBC"
- ],
- "type": "string"
- },
- "source": {
- "title": "Current Source",
- "description": "A current source",
- "units": "A/um^2",
- "allOf": [
- {
- "$ref": "#/definitions/DCCurrentSource"
- }
- ]
- }
- },
- "required": [
- "source"
- ],
- "additionalProperties": false
- },
- "InsulatingBC": {
- "title": "InsulatingBC",
- "description": "Insulation boundary condition.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\n\nNotes\n-----\n\n Ensures the electric potential to the normal :math:`\\nabla \\psi \\cdot \\mathbf{n} = 0` as well as the\n surface recombination current density :math:`J_s = \\mathbf{J} \\cdot \\mathbf{n} = 0` are set to zero where\n the current density is :math:`\\mathbf{J_n}` and the normal vector is :math:`\\mathbf{n}`\n\nExample\n-------\n>>> bc = InsulatingBC()",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "InsulatingBC",
- "enum": [
- "InsulatingBC"
- ],
- "type": "string"
- }
- },
- "additionalProperties": false
- },
- "HeatChargeBoundarySpec": {
- "title": "HeatChargeBoundarySpec",
- "description": "Heat-Charge boundary conditions specification.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nplacement : Union[StructureBoundary, StructureStructureInterface, MediumMediumInterface, SimulationBoundary, StructureSimulationBoundary]\n Location to apply boundary conditions.\ncondition : Union[TemperatureBC, HeatFluxBC, ConvectionBC, VoltageBC, CurrentBC, InsulatingBC]\n Boundary conditions to apply at the selected location.\n\nExample\n-------\n>>> import tidy3d as td\n>>> bc_v1 = td.HeatChargeBoundarySpec(\n... condition=td.VoltageBC(source=td.DCVoltageSource(voltage=0)),\n... placement=td.StructureBoundary(structure=\"contact_left\"),\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "placement": {
- "title": "Boundary Conditions Placement",
- "description": "Location to apply boundary conditions.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "StructureBoundary": "#/definitions/StructureBoundary",
- "StructureStructureInterface": "#/definitions/StructureStructureInterface",
- "MediumMediumInterface": "#/definitions/MediumMediumInterface",
- "SimulationBoundary": "#/definitions/SimulationBoundary",
- "StructureSimulationBoundary": "#/definitions/StructureSimulationBoundary"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/StructureBoundary"
- },
- {
- "$ref": "#/definitions/StructureStructureInterface"
- },
- {
- "$ref": "#/definitions/MediumMediumInterface"
- },
- {
- "$ref": "#/definitions/SimulationBoundary"
- },
- {
- "$ref": "#/definitions/StructureSimulationBoundary"
- }
- ]
- },
- "condition": {
- "title": "Boundary Conditions",
- "description": "Boundary conditions to apply at the selected location.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "TemperatureBC": "#/definitions/TemperatureBC",
- "HeatFluxBC": "#/definitions/HeatFluxBC",
- "ConvectionBC": "#/definitions/ConvectionBC",
- "VoltageBC": "#/definitions/VoltageBC",
- "CurrentBC": "#/definitions/CurrentBC",
- "InsulatingBC": "#/definitions/InsulatingBC"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/TemperatureBC"
- },
- {
- "$ref": "#/definitions/HeatFluxBC"
- },
- {
- "$ref": "#/definitions/ConvectionBC"
- },
- {
- "$ref": "#/definitions/VoltageBC"
- },
- {
- "$ref": "#/definitions/CurrentBC"
- },
- {
- "$ref": "#/definitions/InsulatingBC"
- }
- ]
- },
- "type": {
- "title": "Type",
- "default": "HeatChargeBoundarySpec",
- "enum": [
- "HeatChargeBoundarySpec"
- ],
- "type": "string"
- }
- },
- "required": [
- "placement",
- "condition"
- ],
- "additionalProperties": false
- },
- "HeatBoundarySpec": {
- "title": "HeatBoundarySpec",
- "description": "Heat BC specification. DEPRECIATED.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nplacement : Union[StructureBoundary, StructureStructureInterface, MediumMediumInterface, SimulationBoundary, StructureSimulationBoundary]\n Location to apply boundary conditions.\ncondition : Union[TemperatureBC, HeatFluxBC, ConvectionBC, VoltageBC, CurrentBC, InsulatingBC]\n Boundary conditions to apply at the selected location.\n\nWarning\n-------\n Included backward-compatibility only.\n\nExample\n--------\n>>> import tidy3d as td\n>>> bc_spec = td.HeatBoundarySpec(\n... placement=td.SimulationBoundary(),\n... condition=td.ConvectionBC(ambient_temperature=300, transfer_coeff=1),\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "placement": {
- "title": "Boundary Conditions Placement",
- "description": "Location to apply boundary conditions.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "StructureBoundary": "#/definitions/StructureBoundary",
- "StructureStructureInterface": "#/definitions/StructureStructureInterface",
- "MediumMediumInterface": "#/definitions/MediumMediumInterface",
- "SimulationBoundary": "#/definitions/SimulationBoundary",
- "StructureSimulationBoundary": "#/definitions/StructureSimulationBoundary"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/StructureBoundary"
- },
- {
- "$ref": "#/definitions/StructureStructureInterface"
- },
- {
- "$ref": "#/definitions/MediumMediumInterface"
- },
- {
- "$ref": "#/definitions/SimulationBoundary"
- },
- {
- "$ref": "#/definitions/StructureSimulationBoundary"
- }
- ]
- },
- "condition": {
- "title": "Boundary Conditions",
- "description": "Boundary conditions to apply at the selected location.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "TemperatureBC": "#/definitions/TemperatureBC",
- "HeatFluxBC": "#/definitions/HeatFluxBC",
- "ConvectionBC": "#/definitions/ConvectionBC",
- "VoltageBC": "#/definitions/VoltageBC",
- "CurrentBC": "#/definitions/CurrentBC",
- "InsulatingBC": "#/definitions/InsulatingBC"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/TemperatureBC"
- },
- {
- "$ref": "#/definitions/HeatFluxBC"
- },
- {
- "$ref": "#/definitions/ConvectionBC"
- },
- {
- "$ref": "#/definitions/VoltageBC"
- },
- {
- "$ref": "#/definitions/CurrentBC"
- },
- {
- "$ref": "#/definitions/InsulatingBC"
- }
- ]
- },
- "type": {
- "title": "Type",
- "default": "HeatBoundarySpec",
- "enum": [
- "HeatBoundarySpec"
- ],
- "type": "string"
- }
- },
- "required": [
- "placement",
- "condition"
- ],
- "additionalProperties": false
- },
- "TemperatureMonitor": {
- "title": "TemperatureMonitor",
- "description": "Temperature monitor.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : bool = False\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\ninterval : PositiveInt = 1\n Sampling rate of the monitor: number of time steps between each measurement. Set ``interval`` to 1 for the highest possible resolution in time. Higher integer values down-sample the data by measuring every ``interval`` time steps. This can be useful for reducing data storage as needed by the application.NOTE: this is only relevant for unsteady (transient) Heat simulations. ",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "TemperatureMonitor",
- "enum": [
- "TemperatureMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": false,
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- },
- "interval": {
- "title": "Interval",
- "description": "Sampling rate of the monitor: number of time steps between each measurement. Set ``interval`` to 1 for the highest possible resolution in time. Higher integer values down-sample the data by measuring every ``interval`` time steps. This can be useful for reducing data storage as needed by the application.NOTE: this is only relevant for unsteady (transient) Heat simulations. ",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "SteadyPotentialMonitor": {
- "title": "SteadyPotentialMonitor",
- "description": "Electric potential (:math:`\\psi`) monitor.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : bool = False\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\n\nExample\n-------\n>>> import tidy3d as td\n>>> voltage_monitor_z0 = td.SteadyPotentialMonitor(\n... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name=\"voltage_z0\", unstructured=True,\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SteadyPotentialMonitor",
- "enum": [
- "SteadyPotentialMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": false,
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "SteadyFreeCarrierMonitor": {
- "title": "SteadyFreeCarrierMonitor",
- "description": "Free-carrier monitor for Charge simulations.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : Literal[True] = True\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\n\nExample\n-------\n>>> import tidy3d as td\n>>> voltage_monitor_z0 = td.SteadyFreeCarrierMonitor(\n... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name=\"voltage_z0\", unstructured=True,\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SteadyFreeCarrierMonitor",
- "enum": [
- "SteadyFreeCarrierMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "SteadyEnergyBandMonitor": {
- "title": "SteadyEnergyBandMonitor",
- "description": "Energy bands monitor for Charge simulations.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : Literal[True] = True\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\n\nExample\n-------\n>>> import tidy3d as td\n>>> energy_monitor_z0 = td.SteadyEnergyBandMonitor(\n... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name=\"bands_z0\", unstructured=True,\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SteadyEnergyBandMonitor",
- "enum": [
- "SteadyEnergyBandMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "SteadyElectricFieldMonitor": {
- "title": "SteadyElectricFieldMonitor",
- "description": "Electric field monitor for Charge simulations.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : Literal[True] = True\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\n\nExample\n-------\n>>> import tidy3d as td\n>>> electric_field_monitor_z0 = td.SteadyElectricFieldMonitor(\n... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name=\"electric_field_z0\",\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SteadyElectricFieldMonitor",
- "enum": [
- "SteadyElectricFieldMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "SteadyCapacitanceMonitor": {
- "title": "SteadyCapacitanceMonitor",
- "description": "Capacitance monitor associated with a charge simulation.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : Literal[True] = True\n Return data on the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.\n\nExample\n-------\n>>> import tidy3d as td\n>>> capacitance_global_mnt = td.SteadyCapacitanceMonitor(\n... center=(0, 0.14, 0), size=(td.inf, td.inf, 0), name=\"capacitance_global_mnt\",\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "SteadyCapacitanceMonitor",
- "enum": [
- "SteadyCapacitanceMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return data on the original unstructured grid.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "UniformUnstructuredGrid": {
- "title": "UniformUnstructuredGrid",
- "description": "Uniform grid.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nrelative_min_dl : NonNegativeFloat = 0.001\n The minimal allowed mesh size relative to the largest dimension of the simulation domain.Use ``relative_min_dl=0`` to remove this constraint.\ndl : PositiveFloat\n [units = um]. Grid size for uniform grid generation.\nmin_edges_per_circumference : PositiveFloat = 15\n Enforced minimum number of mesh segments per circumference of an object. Applies to :class:`Cylinder` and :class:`Sphere`, for which the circumference is taken as 2 * pi * radius.\nmin_edges_per_side : PositiveFloat = 2\n Enforced minimum number of mesh segments per any side of an object.\nnon_refined_structures : Tuple[str, ...] = ()\n List of structures for which ``min_edges_per_circumference`` and ``min_edges_per_side`` will not be enforced. The original ``dl`` is used instead.\n\nExample\n-------\n>>> heat_grid = UniformUnstructuredGrid(dl=0.1)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "relative_min_dl": {
- "title": "Relative Mesh Size Limit",
- "description": "The minimal allowed mesh size relative to the largest dimension of the simulation domain.Use ``relative_min_dl=0`` to remove this constraint.",
- "default": 0.001,
- "minimum": 0,
- "type": "number"
- },
- "type": {
- "title": "Type",
- "default": "UniformUnstructuredGrid",
- "enum": [
- "UniformUnstructuredGrid"
- ],
- "type": "string"
- },
- "dl": {
- "title": "Grid Size",
- "description": "Grid size for uniform grid generation.",
- "units": "um",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "min_edges_per_circumference": {
- "title": "Minimum Edges per Circumference",
- "description": "Enforced minimum number of mesh segments per circumference of an object. Applies to :class:`Cylinder` and :class:`Sphere`, for which the circumference is taken as 2 * pi * radius.",
- "default": 15,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "min_edges_per_side": {
- "title": "Minimum Edges per Side",
- "description": "Enforced minimum number of mesh segments per any side of an object.",
- "default": 2,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "non_refined_structures": {
- "title": "Structures Without Refinement",
- "description": "List of structures for which ``min_edges_per_circumference`` and ``min_edges_per_side`` will not be enforced. The original ``dl`` is used instead.",
- "default": [],
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "required": [
- "dl"
- ],
- "additionalProperties": false
- },
- "GridRefinementRegion": {
- "title": "GridRefinementRegion",
- "description": "Refinement region for the unstructured mesh. The cell size is enforced to be constant inside the region.\nThe cell size outside of the region depends on the distance from the region.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\ndl_internal : PositiveFloat\n [units = um]. Mesh cell size inside the refinement region\ntransition_thickness : NonNegativeFloat\n [units = um]. Thickness of a transition layer outside the box where the mesh cell size changes from theinternal size to the external one.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "GridRefinementRegion",
- "enum": [
- "GridRefinementRegion"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "dl_internal": {
- "title": "Internal mesh cell size",
- "description": "Mesh cell size inside the refinement region",
- "units": "um",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "transition_thickness": {
- "title": "Interface Distance",
- "description": "Thickness of a transition layer outside the box where the mesh cell size changes from theinternal size to the external one.",
- "units": "um",
- "minimum": 0,
- "type": "number"
- }
- },
- "required": [
- "size",
- "dl_internal",
- "transition_thickness"
- ],
- "additionalProperties": false
- },
- "GridRefinementLine": {
- "title": "GridRefinementLine",
- "description": "Refinement line for the unstructured mesh. The cell size depends on the distance from the line.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nr1 : Tuple[float, float, float]\n [units = um]. Start point of the line in x, y, and z.\nr2 : Tuple[float, float, float]\n [units = um]. End point of the line in x, y, and z.\ndl_near : PositiveFloat\n [units = um]. Mesh cell size near the line\ndistance_near : NonNegativeFloat\n [units = um]. Distance from the line within which ``dl_near`` is enforced.Typically the same as ``dl_near`` or its multiple.\ndistance_bulk : NonNegativeFloat\n [units = um]. Distance from the line outside of which ``dl_bulk`` is enforced.Typically twice of ``dl_bulk`` or its multiple. Use larger values for a smoother transition from ``dl_near`` to ``dl_bulk``.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "r1": {
- "title": "Start point of the line",
- "description": "Start point of the line in x, y, and z.",
- "units": "um",
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "type": "number"
- },
- {
- "type": "number"
- },
- {
- "type": "number"
- }
- ]
- },
- "r2": {
- "title": "End point of the line",
- "description": "End point of the line in x, y, and z.",
- "units": "um",
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "type": "number"
- },
- {
- "type": "number"
- },
- {
- "type": "number"
- }
- ]
- },
- "dl_near": {
- "title": "Mesh cell size near the line",
- "description": "Mesh cell size near the line",
- "units": "um",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "distance_near": {
- "title": "Near distance",
- "description": "Distance from the line within which ``dl_near`` is enforced.Typically the same as ``dl_near`` or its multiple.",
- "units": "um",
- "minimum": 0,
- "type": "number"
- },
- "distance_bulk": {
- "title": "Bulk distance",
- "description": "Distance from the line outside of which ``dl_bulk`` is enforced.Typically twice of ``dl_bulk`` or its multiple. Use larger values for a smoother transition from ``dl_near`` to ``dl_bulk``.",
- "units": "um",
- "minimum": 0,
- "type": "number"
- },
- "type": {
- "title": "Type",
- "default": "GridRefinementLine",
- "enum": [
- "GridRefinementLine"
- ],
- "type": "string"
- }
- },
- "required": [
- "r1",
- "r2",
- "dl_near",
- "distance_near",
- "distance_bulk"
- ],
- "additionalProperties": false
- },
- "DistanceUnstructuredGrid": {
- "title": "DistanceUnstructuredGrid",
- "description": "Adaptive grid based on distance to material interfaces. Currently not recommended for larger\nsimulations.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nrelative_min_dl : NonNegativeFloat = 0.001\n The minimal allowed mesh size relative to the largest dimension of the simulation domain.Use ``relative_min_dl=0`` to remove this constraint.\ndl_interface : PositiveFloat\n [units = um]. Grid size near material interfaces.\ndl_bulk : PositiveFloat\n [units = um]. Grid size away from material interfaces.\ndistance_interface : NonNegativeFloat\n [units = um]. Distance from interface within which ``dl_interface`` is enforced.Typically the same as ``dl_interface`` or its multiple.\ndistance_bulk : NonNegativeFloat\n [units = um]. Distance from interface outside of which ``dl_bulk`` is enforced.Typically twice of ``dl_bulk`` or its multiple. Use larger values for a smoother transition from ``dl_interface`` to ``dl_bulk``.\nsampling : PositiveFloat = 100\n An internal advanced parameter that defines number of sampling points per surface when computing distance values.\nuniform_grid_mediums : Tuple[str, ...] = ()\n List of mediums for which ``dl_interface`` will be enforced everywhere in the volume.\nnon_refined_structures : Tuple[str, ...] = ()\n List of structures for which ``dl_interface`` will not be enforced. ``dl_bulk`` is used instead.\nmesh_refinements : Tuple[Annotated[Union[tidy3d.components.tcad.grid.GridRefinementRegion, tidy3d.components.tcad.grid.GridRefinementLine], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n List of regions/lines for which the mesh refinement will be applied\n\nExample\n-------\n>>> heat_grid = DistanceUnstructuredGrid(\n... dl_interface=0.1,\n... dl_bulk=1,\n... distance_interface=0.3,\n... distance_bulk=2,\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "relative_min_dl": {
- "title": "Relative Mesh Size Limit",
- "description": "The minimal allowed mesh size relative to the largest dimension of the simulation domain.Use ``relative_min_dl=0`` to remove this constraint.",
- "default": 0.001,
- "minimum": 0,
- "type": "number"
- },
- "type": {
- "title": "Type",
- "default": "DistanceUnstructuredGrid",
- "enum": [
- "DistanceUnstructuredGrid"
- ],
- "type": "string"
- },
- "dl_interface": {
- "title": "Interface Grid Size",
- "description": "Grid size near material interfaces.",
- "units": "um",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "dl_bulk": {
- "title": "Bulk Grid Size",
- "description": "Grid size away from material interfaces.",
- "units": "um",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "distance_interface": {
- "title": "Interface Distance",
- "description": "Distance from interface within which ``dl_interface`` is enforced.Typically the same as ``dl_interface`` or its multiple.",
- "units": "um",
- "minimum": 0,
- "type": "number"
- },
- "distance_bulk": {
- "title": "Bulk Distance",
- "description": "Distance from interface outside of which ``dl_bulk`` is enforced.Typically twice of ``dl_bulk`` or its multiple. Use larger values for a smoother transition from ``dl_interface`` to ``dl_bulk``.",
- "units": "um",
- "minimum": 0,
- "type": "number"
- },
- "sampling": {
- "title": "Surface Sampling",
- "description": "An internal advanced parameter that defines number of sampling points per surface when computing distance values.",
- "default": 100,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "uniform_grid_mediums": {
- "title": "Mediums With Uniform Refinement",
- "description": "List of mediums for which ``dl_interface`` will be enforced everywhere in the volume.",
- "default": [],
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "non_refined_structures": {
- "title": "Structures Without Refinement",
- "description": "List of structures for which ``dl_interface`` will not be enforced. ``dl_bulk`` is used instead.",
- "default": [],
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "mesh_refinements": {
- "title": "Mesh refinement structures",
- "description": "List of regions/lines for which the mesh refinement will be applied",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "GridRefinementRegion": "#/definitions/GridRefinementRegion",
- "GridRefinementLine": "#/definitions/GridRefinementLine"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/GridRefinementRegion"
- },
- {
- "$ref": "#/definitions/GridRefinementLine"
- }
- ]
- }
- }
- },
- "required": [
- "dl_interface",
- "dl_bulk",
- "distance_interface",
- "distance_bulk"
- ],
- "additionalProperties": false
- },
- "ChargeToleranceSpec": {
- "title": "ChargeToleranceSpec",
- "description": "Charge tolerance parameters relevant to multiple simulation analysis types.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nabs_tol : PositiveFloat = 10000000000.0\n Absolute tolerance used as stop criteria when converging towards a solution.\nrel_tol : PositiveFloat = 1e-10\n Relative tolerance used as stop criteria when converging towards a solution.\nmax_iters : PositiveInt = 30\n Indicates the maximum number of iterations to be run. The solver will stop either when this maximum of iterations is met or when the tolerance criteria has been met.\nramp_up_iters : PositiveInt = 1\n In order to help in start up, quantities such as doping are ramped up until they reach their specified value. This parameter determines how many of this iterations it takes to reach full values.\n\nExample\n-------\n>>> import tidy3d as td\n>>> charge_settings = td.ChargeToleranceSpec(abs_tol=1e8, rel_tol=1e-10, max_iters=30)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "abs_tol": {
- "title": "Absolute tolerance.",
- "description": "Absolute tolerance used as stop criteria when converging towards a solution.",
- "default": 10000000000.0,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "rel_tol": {
- "title": "Relative tolerance.",
- "description": "Relative tolerance used as stop criteria when converging towards a solution.",
- "default": 1e-10,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "max_iters": {
- "title": "Maximum number of iterations.",
- "description": "Indicates the maximum number of iterations to be run. The solver will stop either when this maximum of iterations is met or when the tolerance criteria has been met.",
- "default": 30,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "ramp_up_iters": {
- "title": "Ramp-up iterations.",
- "description": "In order to help in start up, quantities such as doping are ramped up until they reach their specified value. This parameter determines how many of this iterations it takes to reach full values.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "type": {
- "title": "Type",
- "default": "ChargeToleranceSpec",
- "enum": [
- "ChargeToleranceSpec"
- ],
- "type": "string"
- }
- },
- "additionalProperties": false
- },
- "IsothermalSteadyChargeDCAnalysis": {
- "title": "IsothermalSteadyChargeDCAnalysis",
- "description": "Configures relevant steady-state DC simulation parameters for a charge simulation.\n\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ntemperature : PositiveFloat = 300\n [units = K]. Lattice temperature. Assumed constant throughout the device. Carriers are assumed to be at thermodynamic equilibrium with the lattice.\ntolerance_settings : ChargeToleranceSpec = ChargeToleranceSpec(attrs={}, abs_tol=10000000000.0, rel_tol=1e-10, max_iters=30, ramp_up_iters=1, type='ChargeToleranceSpec')\n convergence_dv : PositiveFloat = 1.0\n By default, a solution is computed at 0 bias. If a bias different than 0 is requested through a voltage source, the charge solver will start at 0 and increase bias at `convergence_dv` intervals until the required bias is reached. This is, therefore, a convergence parameter in DC computations.\nfermi_dirac : bool = False\n Determines whether Fermi-Dirac statistics are used. When False, Boltzmann statistics will be used. This can provide more accurate results in situations where very high doping may lead the pseudo-Fermi energy level to approach either the conduction or valence energy bands.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "temperature": {
- "title": "Temperature",
- "description": "Lattice temperature. Assumed constant throughout the device. Carriers are assumed to be at thermodynamic equilibrium with the lattice.",
- "default": 300,
- "units": "K",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "tolerance_settings": {
- "title": "Tolerance settings",
- "default": {
- "attrs": {},
- "abs_tol": 10000000000.0,
- "rel_tol": 1e-10,
- "max_iters": 30,
- "ramp_up_iters": 1,
- "type": "ChargeToleranceSpec"
- },
- "allOf": [
- {
- "$ref": "#/definitions/ChargeToleranceSpec"
- }
- ]
- },
- "convergence_dv": {
- "title": "Bias step.",
- "description": "By default, a solution is computed at 0 bias. If a bias different than 0 is requested through a voltage source, the charge solver will start at 0 and increase bias at `convergence_dv` intervals until the required bias is reached. This is, therefore, a convergence parameter in DC computations.",
- "default": 1.0,
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "fermi_dirac": {
- "title": "Fermi-Dirac statistics",
- "description": "Determines whether Fermi-Dirac statistics are used. When False, Boltzmann statistics will be used. This can provide more accurate results in situations where very high doping may lead the pseudo-Fermi energy level to approach either the conduction or valence energy bands.",
- "default": false,
- "type": "boolean"
- },
- "type": {
- "title": "Type",
- "default": "IsothermalSteadyChargeDCAnalysis",
- "enum": [
- "IsothermalSteadyChargeDCAnalysis"
- ],
- "type": "string"
- }
- },
- "additionalProperties": false
- },
- "UnsteadySpec": {
- "title": "UnsteadySpec",
- "description": "Defines an unsteady specification\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ntime_step : PositiveFloat\n [units = sec]. Time step taken for each iteration of the time integration loop.\ntotal_time_steps : PositiveInt\n Specifies the total number of time steps run during the simulation.\n\nExample\n--------\n>>> import tidy3d as td\n>>> time_spec = td.UnsteadySpec(\n... time_step=0.01,\n... total_time_steps=200,\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "time_step": {
- "title": "Time-step",
- "description": "Time step taken for each iteration of the time integration loop.",
- "units": "sec",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "total_time_steps": {
- "title": "Total time steps",
- "description": "Specifies the total number of time steps run during the simulation.",
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "type": {
- "title": "Type",
- "default": "UnsteadySpec",
- "enum": [
- "UnsteadySpec"
- ],
- "type": "string"
- }
- },
- "required": [
- "time_step",
- "total_time_steps"
- ],
- "additionalProperties": false
- },
- "UnsteadyHeatAnalysis": {
- "title": "UnsteadyHeatAnalysis",
- "description": "Configures relevant unsteady-state heat simulation parameters.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ninitial_temperature : PositiveFloat\n [units = K]. Initial value for the temperature field.\nunsteady_spec : UnsteadySpec\n Time step and total time steps for the unsteady simulation.\n\nExample\n-------\n>>> import tidy3d as td\n>>> time_spec = td.UnsteadyHeatAnalysis(\n... initial_temperature=300,\n... unsteady_spec=td.UnsteadySpec(\n... time_step=0.01,\n... total_time_steps=200,\n... ),\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "initial_temperature": {
- "title": "Initial temperature.",
- "description": "Initial value for the temperature field.",
- "units": "K",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "unsteady_spec": {
- "title": "Unsteady specification",
- "description": "Time step and total time steps for the unsteady simulation.",
- "allOf": [
- {
- "$ref": "#/definitions/UnsteadySpec"
- }
- ]
- },
- "type": {
- "title": "Type",
- "default": "UnsteadyHeatAnalysis",
- "enum": [
- "UnsteadyHeatAnalysis"
- ],
- "type": "string"
- }
- },
- "required": [
- "initial_temperature",
- "unsteady_spec"
- ],
- "additionalProperties": false
- },
- "HeatChargeSimulation": {
- "title": "HeatChargeSimulation",
- "description": "Defines thermoelectric simulations.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nmedium : Union[MultiPhysicsMedium, Medium, AnisotropicMedium, PECMedium, PMCMedium, PoleResidue, Sellmeier, Lorentz, Debye, Drude, FullyAnisotropicMedium, CustomMedium, CustomPoleResidue, CustomSellmeier, CustomLorentz, CustomDebye, CustomDrude, CustomAnisotropicMedium, PerturbationMedium, PerturbationPoleResidue, LossyMetalMedium, Medium2D, AnisotropicMediumFromMedium2D, FluidSpec, SolidSpec, SolidMedium, FluidMedium, ChargeConductorMedium, ChargeInsulatorMedium, SemiconductorMedium] = Medium(attrs={}, name=None, frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, viz_spec=None, heat_spec=None, type='Medium', permittivity=1.0, conductivity=0.0)\n Background medium of simulation, defaults to a standard dispersion-less :class:`Medium` if not specified.\nstructures : Tuple[Structure, ...] = ()\n Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.\nsymmetry : Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1]] = (0, 0, 0)\n Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. Each element can be ``0`` (symmetry off) or ``1`` (symmetry on).\nsources : Tuple[Annotated[Union[tidy3d.components.tcad.source.heat.HeatSource, tidy3d.components.tcad.source.coupled.HeatFromElectricSource, tidy3d.components.tcad.source.heat.UniformHeatSource], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n List of heat and/or charge sources.\nboundary_spec : Tuple[Annotated[Union[tidy3d.components.tcad.boundary.specification.HeatChargeBoundarySpec, tidy3d.components.tcad.boundary.specification.HeatBoundarySpec], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n List of boundary condition specifications.\nmonitors : Tuple[Annotated[Union[tidy3d.components.tcad.monitors.heat.TemperatureMonitor, tidy3d.components.tcad.monitors.charge.SteadyPotentialMonitor, tidy3d.components.tcad.monitors.charge.SteadyFreeCarrierMonitor, tidy3d.components.tcad.monitors.charge.SteadyEnergyBandMonitor, tidy3d.components.tcad.monitors.charge.SteadyElectricFieldMonitor, tidy3d.components.tcad.monitors.charge.SteadyCapacitanceMonitor], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n Monitors in the simulation.\ngrid_spec : Union[UniformUnstructuredGrid, DistanceUnstructuredGrid]\n Grid specification for heat-charge simulation.\nversion : str = 2.9.0\n String specifying the front end version number.\nplot_length_units : Optional[Literal['nm', '\u03bcm', 'um', 'mm', 'cm', 'm']] = \u03bcm\n When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.\nstructure_priority_mode : Literal['equal', 'conductor'] = equal\n This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.\nanalysis_spec : Union[IsothermalSteadyChargeDCAnalysis, UnsteadyHeatAnalysis] = None\n The `analysis_spec` is used to specify the type of simulation. Currently, it is used to specify Charge simulations or transient Heat simulations.\n\nNotes\n-----\n A ``HeatChargeSimulation`` supports different types of simulations. It solves the\n heat and conduction equations using the Finite-Volume (FV) method. This solver\n determines the required computation physics according to the simulation scene definition.\n This is implemented in this way due to the strong multi-physics coupling.\n\nThe ``HeatChargeSimulation`` can solve multiple physics and the intention is to enable close thermo-electrical coupling.\n\nCurrently, this solver supports steady-state heat conduction where :math:`q` is the heat flux, :math:`k`\nis the thermal conductivity, and :math:`T` is the temperature.\n\n .. math::\n\n -\\nabla \\cdot (-k \\nabla T) = q\n\nIt is also possible to run transient heat simulations by specifying ``analysis_spec=UnsteadyHeatAnalysis(...)``. This adds\nthe temporal terms to the above equations:\n\n .. math::\n\n \\frac{\\partial \\rho c_p T}{\\partial t} -\\nabla \\cdot (k \\nabla(T)) = q\n\nwhere :math:`\\rho` is the density and :math:`c_p` is the specific heat capacity of the medium.\n\n\nThe steady-state electrical ``Conduction`` equation depends on the electric conductivity (:math:`\\sigma`) of a\nmedium, and the electric field (:math:`\\mathbf{E} = -\\nabla(\\psi)`) derived from electrical potential (:math:`\\psi`).\nCurrently, in this type of simulation, no current sources or sinks are supported.\n\n .. math::\n\n \\text{div}(\\sigma \\cdot \\nabla(\\psi)) = 0\n\n\nFor further details on what equations are solved in ``Charge`` simulations, refer to the :class:`SemiconductorMedium`.\n\nLet's understand how the physics solving is determined:\n\n .. list-table::\n :widths: 25 75\n :header-rows: 1\n\n * - Simulation Type\n - Example Configuration Settings\n * - ``Heat``\n - The heat equation is solved with specified heat sources,\n boundary conditions, etc. Structures should incorporate materials\n with defined heat properties.\n * - ``Conduction``\n - The electrical conduction equation is solved with\n specified boundary conditions such as ``SteadyVoltageBC``, ``SteadyCurrentBC``, ...\n * - ``Charge``\n - Drift-diffusion equations are solved for structures containing\n a defined :class:`SemiconductorMedium`. Insulators with a\n :class:`ChargeInsulatorMedium` can also be included. For these, only the\n electric potential field is calculated.\n\nExamples\n--------\nTo run a thermal (``Heat`` |:fire:|) simulation with a solid conductive structure:\n\n>>> import tidy3d as td\n>>> heat_sim = td.HeatChargeSimulation(\n... size=(3.0, 3.0, 3.0),\n... structures=[\n... td.Structure(\n... geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),\n... medium=td.Medium(\n... permittivity=2.0,\n... heat_spec=td.SolidSpec(\n... conductivity=1,\n... capacity=1,\n... )\n... ),\n... name=\"box\",\n... ),\n... ],\n... medium=td.Medium(permittivity=3.0, heat_spec=td.FluidSpec()),\n... grid_spec=td.UniformUnstructuredGrid(dl=0.1),\n... sources=[td.HeatSource(rate=1, structures=[\"box\"])],\n... boundary_spec=[\n... td.HeatChargeBoundarySpec(\n... placement=td.StructureBoundary(structure=\"box\"),\n... condition=td.TemperatureBC(temperature=500),\n... )\n... ],\n... monitors=[td.TemperatureMonitor(size=(1, 2, 3), name=\"sample\")],\n... )\n\nTo run a drift-diffusion (``Charge`` |:zap:|) system:\n\n>>> import tidy3d as td\n>>> air = td.FluidMedium(\n... name=\"air\"\n... )\n>>> intrinsic_Si = td.material_library['cSi'].variants['Si_MultiPhysics'].medium.charge\n>>> Si_n = intrinsic_Si.updated_copy(N_d=1e16, name=\"Si_n\")\n>>> Si_p = intrinsic_Si.updated_copy(N_a=1e16, name=\"Si_p\")\n>>> n_side = td.Structure(\n... geometry=td.Box(center=(-0.5, 0, 0), size=(1, 1, 1)),\n... medium=Si_n,\n... name=\"n_side\"\n... )\n>>> p_side = td.Structure(\n... geometry=td.Box(center=(0.5, 0, 0), size=(1, 1, 1)),\n... medium=Si_p,\n... name=\"p_side\"\n... )\n>>> bc_v1 = td.HeatChargeBoundarySpec(\n... condition=td.VoltageBC(source=td.DCVoltageSource(voltage=[-1, 0, 0.5])),\n... placement=td.MediumMediumInterface(mediums=[air.name, Si_n.name]),\n... )\n>>> bc_v2 = td.HeatChargeBoundarySpec(\n... condition=td.VoltageBC(source=td.DCVoltageSource(voltage=0)),\n... placement=td.MediumMediumInterface(mediums=[air.name, Si_p.name]),\n... )\n>>> charge_sim = td.HeatChargeSimulation(\n... structures=[n_side, p_side],\n... medium=td.Medium(heat_spec=td.FluidSpec(), name=\"air\"),\n... monitors=[td.SteadyFreeCarrierMonitor(\n... center=(0, 0, 0), size=(td.inf, td.inf, 0), name=\"charge_mnt\", unstructured=True\n... )],\n... center=(0, 0, 0),\n... size=(3, 3, 3),\n... grid_spec=td.UniformUnstructuredGrid(dl=0.05),\n... boundary_spec=[bc_v1, bc_v2],\n... analysis_spec=td.IsothermalSteadyChargeDCAnalysis(\n... tolerance_settings=td.ChargeToleranceSpec(rel_tol=1e5, abs_tol=3e3, max_iters=400),\n... convergence_dv=10),\n... )\n\n\nCoupling between ``Heat`` and electrical ``Conduction`` simulations is currently limited to 1-way.\nThis is specified by defining a heat source of type :class:`HeatFromElectricSource`. With this coupling, joule heating is\ncalculated as part of the solution to a ``Conduction`` simulation and translated into the ``Heat`` simulation.\n\nTwo common scenarios can use this coupling definition:\n 1. One in which BCs and sources are specified for both ``Heat`` and ``Conduction`` simulations.\n In this case one mesh will be generated and used for both the ``Conduction`` and ``Heat``\n simulations.\n 2. Only heat BCs/sources are provided. In this case, only the ``Heat`` equation will be solved.\n Before the simulation starts, it will try to load the heat source from file so a\n previously run ``Conduction`` simulations must have run previously. Since the Conduction\n and ``Heat`` meshes may differ, an interpolation between them will be performed prior to\n starting the ``Heat`` simulation.\n\nAdditional heat sources can be defined, in which case, they will be added on\ntop of the coupling heat source.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "HeatChargeSimulation",
- "enum": [
- "HeatChargeSimulation"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "medium": {
- "title": "Background Medium",
- "description": "Background medium of simulation, defaults to a standard dispersion-less :class:`Medium` if not specified.",
- "default": {
- "attrs": {},
- "name": null,
- "frequency_range": null,
- "allow_gain": false,
- "nonlinear_spec": null,
- "modulation_spec": null,
- "viz_spec": null,
- "heat_spec": null,
- "type": "Medium",
- "permittivity": 1.0,
- "conductivity": 0.0
- },
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "MultiPhysicsMedium": "#/definitions/MultiPhysicsMedium",
- "Medium": "#/definitions/Medium",
- "AnisotropicMedium": "#/definitions/AnisotropicMedium",
- "PECMedium": "#/definitions/PECMedium",
- "PMCMedium": "#/definitions/PMCMedium",
- "PoleResidue": "#/definitions/PoleResidue",
- "Sellmeier": "#/definitions/Sellmeier",
- "Lorentz": "#/definitions/Lorentz",
- "Debye": "#/definitions/Debye",
- "Drude": "#/definitions/Drude",
- "FullyAnisotropicMedium": "#/definitions/FullyAnisotropicMedium",
- "CustomMedium": "#/definitions/CustomMedium",
- "CustomPoleResidue": "#/definitions/CustomPoleResidue",
- "CustomSellmeier": "#/definitions/CustomSellmeier",
- "CustomLorentz": "#/definitions/CustomLorentz",
- "CustomDebye": "#/definitions/CustomDebye",
- "CustomDrude": "#/definitions/CustomDrude",
- "CustomAnisotropicMedium": "#/definitions/CustomAnisotropicMedium",
- "PerturbationMedium": "#/definitions/PerturbationMedium",
- "PerturbationPoleResidue": "#/definitions/PerturbationPoleResidue",
- "LossyMetalMedium": "#/definitions/LossyMetalMedium",
- "Medium2D": "#/definitions/Medium2D",
- "AnisotropicMediumFromMedium2D": "#/definitions/AnisotropicMediumFromMedium2D",
- "FluidSpec": "#/definitions/FluidSpec",
- "SolidSpec": "#/definitions/SolidSpec",
- "SolidMedium": "#/definitions/SolidMedium",
- "FluidMedium": "#/definitions/FluidMedium",
- "ChargeConductorMedium": "#/definitions/ChargeConductorMedium",
- "ChargeInsulatorMedium": "#/definitions/ChargeInsulatorMedium",
- "SemiconductorMedium": "#/definitions/SemiconductorMedium"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/MultiPhysicsMedium"
- },
- {
- "$ref": "#/definitions/Medium"
- },
- {
- "$ref": "#/definitions/AnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PECMedium"
- },
- {
- "$ref": "#/definitions/PMCMedium"
- },
- {
- "$ref": "#/definitions/PoleResidue"
- },
- {
- "$ref": "#/definitions/Sellmeier"
- },
- {
- "$ref": "#/definitions/Lorentz"
- },
- {
- "$ref": "#/definitions/Debye"
- },
- {
- "$ref": "#/definitions/Drude"
- },
- {
- "$ref": "#/definitions/FullyAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/CustomMedium"
- },
- {
- "$ref": "#/definitions/CustomPoleResidue"
- },
- {
- "$ref": "#/definitions/CustomSellmeier"
- },
- {
- "$ref": "#/definitions/CustomLorentz"
- },
- {
- "$ref": "#/definitions/CustomDebye"
- },
- {
- "$ref": "#/definitions/CustomDrude"
- },
- {
- "$ref": "#/definitions/CustomAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PerturbationMedium"
- },
- {
- "$ref": "#/definitions/PerturbationPoleResidue"
- },
- {
- "$ref": "#/definitions/LossyMetalMedium"
- },
- {
- "$ref": "#/definitions/Medium2D"
- },
- {
- "$ref": "#/definitions/AnisotropicMediumFromMedium2D"
- },
- {
- "$ref": "#/definitions/FluidSpec"
- },
- {
- "$ref": "#/definitions/SolidSpec"
- },
- {
- "$ref": "#/definitions/SolidMedium"
- },
- {
- "$ref": "#/definitions/FluidMedium"
- },
- {
- "$ref": "#/definitions/ChargeConductorMedium"
- },
- {
- "$ref": "#/definitions/ChargeInsulatorMedium"
- },
- {
- "$ref": "#/definitions/SemiconductorMedium"
- }
- ]
- },
- "structures": {
- "title": "Structures",
- "description": "Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/Structure"
- }
- },
- "symmetry": {
- "title": "Symmetries",
- "description": "Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. Each element can be ``0`` (symmetry off) or ``1`` (symmetry on).",
- "default": [
- 0,
- 0,
- 0
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "sources": {
- "title": "Heat and Charge sources",
- "description": "List of heat and/or charge sources.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "HeatSource": "#/definitions/HeatSource",
- "HeatFromElectricSource": "#/definitions/HeatFromElectricSource",
- "UniformHeatSource": "#/definitions/UniformHeatSource"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/HeatSource"
- },
- {
- "$ref": "#/definitions/HeatFromElectricSource"
- },
- {
- "$ref": "#/definitions/UniformHeatSource"
- }
- ]
- }
- },
- "boundary_spec": {
- "title": "Boundary Condition Specifications",
- "description": "List of boundary condition specifications.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "HeatChargeBoundarySpec": "#/definitions/HeatChargeBoundarySpec",
- "HeatBoundarySpec": "#/definitions/HeatBoundarySpec"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/HeatChargeBoundarySpec"
- },
- {
- "$ref": "#/definitions/HeatBoundarySpec"
- }
- ]
- }
- },
- "monitors": {
- "title": "Monitors",
- "description": "Monitors in the simulation.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "TemperatureMonitor": "#/definitions/TemperatureMonitor",
- "SteadyPotentialMonitor": "#/definitions/SteadyPotentialMonitor",
- "SteadyFreeCarrierMonitor": "#/definitions/SteadyFreeCarrierMonitor",
- "SteadyEnergyBandMonitor": "#/definitions/SteadyEnergyBandMonitor",
- "SteadyElectricFieldMonitor": "#/definitions/SteadyElectricFieldMonitor",
- "SteadyCapacitanceMonitor": "#/definitions/SteadyCapacitanceMonitor"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/TemperatureMonitor"
- },
- {
- "$ref": "#/definitions/SteadyPotentialMonitor"
- },
- {
- "$ref": "#/definitions/SteadyFreeCarrierMonitor"
- },
- {
- "$ref": "#/definitions/SteadyEnergyBandMonitor"
- },
- {
- "$ref": "#/definitions/SteadyElectricFieldMonitor"
- },
- {
- "$ref": "#/definitions/SteadyCapacitanceMonitor"
- }
- ]
- }
- },
- "grid_spec": {
- "title": "Grid Specification",
- "description": "Grid specification for heat-charge simulation.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "UniformUnstructuredGrid": "#/definitions/UniformUnstructuredGrid",
- "DistanceUnstructuredGrid": "#/definitions/DistanceUnstructuredGrid"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/UniformUnstructuredGrid"
- },
- {
- "$ref": "#/definitions/DistanceUnstructuredGrid"
- }
- ]
- },
- "version": {
- "title": "Version",
- "description": "String specifying the front end version number.",
- "default": "2.9.0",
- "type": "string"
- },
- "plot_length_units": {
- "title": "Plot Units",
- "description": "When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.",
- "default": "\u03bcm",
- "enum": [
- "nm",
- "\u03bcm",
- "um",
- "mm",
- "cm",
- "m"
- ],
- "type": "string"
- },
- "structure_priority_mode": {
- "title": "Structure Priority Setting",
- "description": "This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.",
- "default": "equal",
- "enum": [
- "equal",
- "conductor"
- ],
- "type": "string"
- },
- "analysis_spec": {
- "title": "Analysis specification.",
- "description": "The `analysis_spec` is used to specify the type of simulation. Currently, it is used to specify Charge simulations or transient Heat simulations.",
- "anyOf": [
- {
- "$ref": "#/definitions/IsothermalSteadyChargeDCAnalysis"
- },
- {
- "$ref": "#/definitions/UnsteadyHeatAnalysis"
- }
- ]
- }
- },
- "required": [
- "size",
- "grid_spec"
- ],
- "additionalProperties": false
- },
- "HeatSimulation": {
- "title": "HeatSimulation",
- "description": "Contains all information about heat simulation.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nmedium : Union[MultiPhysicsMedium, Medium, AnisotropicMedium, PECMedium, PMCMedium, PoleResidue, Sellmeier, Lorentz, Debye, Drude, FullyAnisotropicMedium, CustomMedium, CustomPoleResidue, CustomSellmeier, CustomLorentz, CustomDebye, CustomDrude, CustomAnisotropicMedium, PerturbationMedium, PerturbationPoleResidue, LossyMetalMedium, Medium2D, AnisotropicMediumFromMedium2D, FluidSpec, SolidSpec, SolidMedium, FluidMedium, ChargeConductorMedium, ChargeInsulatorMedium, SemiconductorMedium] = Medium(attrs={}, name=None, frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, viz_spec=None, heat_spec=None, type='Medium', permittivity=1.0, conductivity=0.0)\n Background medium of simulation, defaults to a standard dispersion-less :class:`Medium` if not specified.\nstructures : Tuple[Structure, ...] = ()\n Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.\nsymmetry : Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1]] = (0, 0, 0)\n Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. Each element can be ``0`` (symmetry off) or ``1`` (symmetry on).\nsources : Tuple[Annotated[Union[tidy3d.components.tcad.source.heat.HeatSource, tidy3d.components.tcad.source.coupled.HeatFromElectricSource, tidy3d.components.tcad.source.heat.UniformHeatSource], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n List of heat and/or charge sources.\nboundary_spec : Tuple[Annotated[Union[tidy3d.components.tcad.boundary.specification.HeatChargeBoundarySpec, tidy3d.components.tcad.boundary.specification.HeatBoundarySpec], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n List of boundary condition specifications.\nmonitors : Tuple[Annotated[Union[tidy3d.components.tcad.monitors.heat.TemperatureMonitor, tidy3d.components.tcad.monitors.charge.SteadyPotentialMonitor, tidy3d.components.tcad.monitors.charge.SteadyFreeCarrierMonitor, tidy3d.components.tcad.monitors.charge.SteadyEnergyBandMonitor, tidy3d.components.tcad.monitors.charge.SteadyElectricFieldMonitor, tidy3d.components.tcad.monitors.charge.SteadyCapacitanceMonitor], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n Monitors in the simulation.\ngrid_spec : Union[UniformUnstructuredGrid, DistanceUnstructuredGrid]\n Grid specification for heat-charge simulation.\nversion : str = 2.9.0\n String specifying the front end version number.\nplot_length_units : Optional[Literal['nm', '\u03bcm', 'um', 'mm', 'cm', 'm']] = \u03bcm\n When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.\nstructure_priority_mode : Literal['equal', 'conductor'] = equal\n This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.\nanalysis_spec : Union[IsothermalSteadyChargeDCAnalysis, UnsteadyHeatAnalysis] = None\n The `analysis_spec` is used to specify the type of simulation. Currently, it is used to specify Charge simulations or transient Heat simulations.\n\nExample\n-------\n>>> import tidy3d as td\n>>> heat_sim = td.HeatSimulation( # doctest: +SKIP\n... size=(3.0, 3.0, 3.0),\n... structures=[\n... td.Structure(\n... geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),\n... medium=td.Medium(\n... permittivity=2.0, heat_spec=td.SolidSpec(\n... conductivity=1,\n... capacity=1,\n... )\n... ),\n... name=\"box\",\n... ),\n... ],\n... medium=td.Medium(permittivity=3.0, heat_spec=td.FluidSpec()),\n... grid_spec=td.UniformUnstructuredGrid(dl=0.1),\n... sources=[td.HeatSource(rate=1, structures=[\"box\"])],\n... boundary_spec=[\n... td.HeatChargeBoundarySpec(\n... placement=td.StructureBoundary(structure=\"box\"),\n... condition=td.TemperatureBC(temperature=500),\n... )\n... ],\n... monitors=[td.TemperatureMonitor(size=(1, 2, 3), name=\"sample\")],\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "HeatSimulation",
- "enum": [
- "HeatSimulation"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "medium": {
- "title": "Background Medium",
- "description": "Background medium of simulation, defaults to a standard dispersion-less :class:`Medium` if not specified.",
- "default": {
- "attrs": {},
- "name": null,
- "frequency_range": null,
- "allow_gain": false,
- "nonlinear_spec": null,
- "modulation_spec": null,
- "viz_spec": null,
- "heat_spec": null,
- "type": "Medium",
- "permittivity": 1.0,
- "conductivity": 0.0
- },
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "MultiPhysicsMedium": "#/definitions/MultiPhysicsMedium",
- "Medium": "#/definitions/Medium",
- "AnisotropicMedium": "#/definitions/AnisotropicMedium",
- "PECMedium": "#/definitions/PECMedium",
- "PMCMedium": "#/definitions/PMCMedium",
- "PoleResidue": "#/definitions/PoleResidue",
- "Sellmeier": "#/definitions/Sellmeier",
- "Lorentz": "#/definitions/Lorentz",
- "Debye": "#/definitions/Debye",
- "Drude": "#/definitions/Drude",
- "FullyAnisotropicMedium": "#/definitions/FullyAnisotropicMedium",
- "CustomMedium": "#/definitions/CustomMedium",
- "CustomPoleResidue": "#/definitions/CustomPoleResidue",
- "CustomSellmeier": "#/definitions/CustomSellmeier",
- "CustomLorentz": "#/definitions/CustomLorentz",
- "CustomDebye": "#/definitions/CustomDebye",
- "CustomDrude": "#/definitions/CustomDrude",
- "CustomAnisotropicMedium": "#/definitions/CustomAnisotropicMedium",
- "PerturbationMedium": "#/definitions/PerturbationMedium",
- "PerturbationPoleResidue": "#/definitions/PerturbationPoleResidue",
- "LossyMetalMedium": "#/definitions/LossyMetalMedium",
- "Medium2D": "#/definitions/Medium2D",
- "AnisotropicMediumFromMedium2D": "#/definitions/AnisotropicMediumFromMedium2D",
- "FluidSpec": "#/definitions/FluidSpec",
- "SolidSpec": "#/definitions/SolidSpec",
- "SolidMedium": "#/definitions/SolidMedium",
- "FluidMedium": "#/definitions/FluidMedium",
- "ChargeConductorMedium": "#/definitions/ChargeConductorMedium",
- "ChargeInsulatorMedium": "#/definitions/ChargeInsulatorMedium",
- "SemiconductorMedium": "#/definitions/SemiconductorMedium"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/MultiPhysicsMedium"
- },
- {
- "$ref": "#/definitions/Medium"
- },
- {
- "$ref": "#/definitions/AnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PECMedium"
- },
- {
- "$ref": "#/definitions/PMCMedium"
- },
- {
- "$ref": "#/definitions/PoleResidue"
- },
- {
- "$ref": "#/definitions/Sellmeier"
- },
- {
- "$ref": "#/definitions/Lorentz"
- },
- {
- "$ref": "#/definitions/Debye"
- },
- {
- "$ref": "#/definitions/Drude"
- },
- {
- "$ref": "#/definitions/FullyAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/CustomMedium"
- },
- {
- "$ref": "#/definitions/CustomPoleResidue"
- },
- {
- "$ref": "#/definitions/CustomSellmeier"
- },
- {
- "$ref": "#/definitions/CustomLorentz"
- },
- {
- "$ref": "#/definitions/CustomDebye"
- },
- {
- "$ref": "#/definitions/CustomDrude"
- },
- {
- "$ref": "#/definitions/CustomAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PerturbationMedium"
- },
- {
- "$ref": "#/definitions/PerturbationPoleResidue"
- },
- {
- "$ref": "#/definitions/LossyMetalMedium"
- },
- {
- "$ref": "#/definitions/Medium2D"
- },
- {
- "$ref": "#/definitions/AnisotropicMediumFromMedium2D"
- },
- {
- "$ref": "#/definitions/FluidSpec"
- },
- {
- "$ref": "#/definitions/SolidSpec"
- },
- {
- "$ref": "#/definitions/SolidMedium"
- },
- {
- "$ref": "#/definitions/FluidMedium"
- },
- {
- "$ref": "#/definitions/ChargeConductorMedium"
- },
- {
- "$ref": "#/definitions/ChargeInsulatorMedium"
- },
- {
- "$ref": "#/definitions/SemiconductorMedium"
- }
- ]
- },
- "structures": {
- "title": "Structures",
- "description": "Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/Structure"
- }
- },
- "symmetry": {
- "title": "Symmetries",
- "description": "Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. Each element can be ``0`` (symmetry off) or ``1`` (symmetry on).",
- "default": [
- 0,
- 0,
- 0
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "sources": {
- "title": "Heat and Charge sources",
- "description": "List of heat and/or charge sources.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "HeatSource": "#/definitions/HeatSource",
- "HeatFromElectricSource": "#/definitions/HeatFromElectricSource",
- "UniformHeatSource": "#/definitions/UniformHeatSource"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/HeatSource"
- },
- {
- "$ref": "#/definitions/HeatFromElectricSource"
- },
- {
- "$ref": "#/definitions/UniformHeatSource"
- }
- ]
- }
- },
- "boundary_spec": {
- "title": "Boundary Condition Specifications",
- "description": "List of boundary condition specifications.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "HeatChargeBoundarySpec": "#/definitions/HeatChargeBoundarySpec",
- "HeatBoundarySpec": "#/definitions/HeatBoundarySpec"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/HeatChargeBoundarySpec"
- },
- {
- "$ref": "#/definitions/HeatBoundarySpec"
- }
- ]
- }
- },
- "monitors": {
- "title": "Monitors",
- "description": "Monitors in the simulation.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "TemperatureMonitor": "#/definitions/TemperatureMonitor",
- "SteadyPotentialMonitor": "#/definitions/SteadyPotentialMonitor",
- "SteadyFreeCarrierMonitor": "#/definitions/SteadyFreeCarrierMonitor",
- "SteadyEnergyBandMonitor": "#/definitions/SteadyEnergyBandMonitor",
- "SteadyElectricFieldMonitor": "#/definitions/SteadyElectricFieldMonitor",
- "SteadyCapacitanceMonitor": "#/definitions/SteadyCapacitanceMonitor"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/TemperatureMonitor"
- },
- {
- "$ref": "#/definitions/SteadyPotentialMonitor"
- },
- {
- "$ref": "#/definitions/SteadyFreeCarrierMonitor"
- },
- {
- "$ref": "#/definitions/SteadyEnergyBandMonitor"
- },
- {
- "$ref": "#/definitions/SteadyElectricFieldMonitor"
- },
- {
- "$ref": "#/definitions/SteadyCapacitanceMonitor"
- }
- ]
- }
- },
- "grid_spec": {
- "title": "Grid Specification",
- "description": "Grid specification for heat-charge simulation.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "UniformUnstructuredGrid": "#/definitions/UniformUnstructuredGrid",
- "DistanceUnstructuredGrid": "#/definitions/DistanceUnstructuredGrid"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/UniformUnstructuredGrid"
- },
- {
- "$ref": "#/definitions/DistanceUnstructuredGrid"
- }
- ]
- },
- "version": {
- "title": "Version",
- "description": "String specifying the front end version number.",
- "default": "2.9.0",
- "type": "string"
- },
- "plot_length_units": {
- "title": "Plot Units",
- "description": "When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.",
- "default": "\u03bcm",
- "enum": [
- "nm",
- "\u03bcm",
- "um",
- "mm",
- "cm",
- "m"
- ],
- "type": "string"
- },
- "structure_priority_mode": {
- "title": "Structure Priority Setting",
- "description": "This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.",
- "default": "equal",
- "enum": [
- "equal",
- "conductor"
- ],
- "type": "string"
- },
- "analysis_spec": {
- "title": "Analysis specification.",
- "description": "The `analysis_spec` is used to specify the type of simulation. Currently, it is used to specify Charge simulations or transient Heat simulations.",
- "anyOf": [
- {
- "$ref": "#/definitions/IsothermalSteadyChargeDCAnalysis"
- },
- {
- "$ref": "#/definitions/UnsteadyHeatAnalysis"
- }
- ]
- }
- },
- "required": [
- "size",
- "grid_spec"
- ],
- "additionalProperties": false
- },
- "EMEModeSolverMonitor": {
- "title": "EMEModeSolverMonitor",
- "description": "EME mode solver monitor.\nRecords EME modes computed in planes intersecting the monitor geometry.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1], NoneType] = None\n Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.\nnum_modes : Optional[NonNegativeInt] = None\n Maximum number of modes for the monitor to record. Cannot exceed the greatest number of modes in any EME cell. A value of 'None' will record all modes.\nnum_sweep : Optional[NonNegativeInt] = 1\n Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.\ninterval_space : Tuple[Literal[1], Literal[1], Literal[1]] = (1, 1, 1)\n Note: not yet supported. Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included. Note: the interval in the propagation direction is not used. Note: this is not yet supported.\neme_cell_interval_space : PositiveInt = 1\n Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1.\ncolocate : bool = True\n Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default (False) is used internally in EME propagation.\nnormalize : bool = True\n Whether to normalize the EME modes to unity flux.\nkeep_invalid_modes : bool = False\n Whether to store modes containing nan values and modes which are exponentially increasing in the propagation direction.\n\nNote\n----\n\n This is different than a :class:`.ModeSolverMonitor`, which computes modes within\n its planar geometry. In contrast, this monitor does not compute new modes; instead,\n it records the modes used for EME expansion and propagation, but only within the\n monitor geometry.\n\nExample\n-------\n>>> monitor = EMEModeSolverMonitor(\n... center=(1,2,3),\n... size=(2,2,2),\n... freqs=[300e12],\n... num_modes=2,\n... name=\"eme_modes\"\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMEModeSolverMonitor",
- "enum": [
- "EMEModeSolverMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "freqs": {
- "title": "Monitor Frequencies",
- "description": "Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "num_modes": {
- "title": "Number of Modes",
- "description": "Maximum number of modes for the monitor to record. Cannot exceed the greatest number of modes in any EME cell. A value of 'None' will record all modes.",
- "minimum": 0,
- "type": "integer"
- },
- "num_sweep": {
- "title": "Number of Sweep Indices",
- "description": "Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.",
- "default": 1,
- "minimum": 0,
- "type": "integer"
- },
- "interval_space": {
- "title": "Spatial Interval",
- "description": "Note: not yet supported. Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included. Note: the interval in the propagation direction is not used. Note: this is not yet supported.",
- "default": [
- 1,
- 1,
- 1
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "eme_cell_interval_space": {
- "title": "EME Cell Interval",
- "description": "Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "colocate": {
- "title": "Colocate Fields",
- "description": "Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default (False) is used internally in EME propagation.",
- "default": true,
- "type": "boolean"
- },
- "normalize": {
- "title": "Normalize Modes",
- "description": "Whether to normalize the EME modes to unity flux.",
- "default": true,
- "type": "boolean"
- },
- "keep_invalid_modes": {
- "title": "Keep Invalid Modes",
- "description": "Whether to store modes containing nan values and modes which are exponentially increasing in the propagation direction.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "EMEFieldMonitor": {
- "title": "EMEFieldMonitor",
- "description": "EME monitor for propagated field.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\ninterval_space : Tuple[PositiveInt, PositiveInt, PositiveInt] = (1, 1, 1)\n Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included.\ncolocate : bool = True\n Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default (False) is used internally in EME propagation.\nfields : Tuple[Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'], ...] = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Collection of field components to store in the monitor.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1], NoneType] = None\n Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.\nnum_modes : Optional[NonNegativeInt] = None\n Maximum number of modes for the monitor to record. For 'EMEFieldMonitor', refers to the number of modes at each port.Cannot exceed the max of the number of modes in the two ports. A value of 'None' will record all modes.\nnum_sweep : Optional[NonNegativeInt] = 1\n Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.\neme_cell_interval_space : Literal[1] = 1\n Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1. Note: this field is not used for EME field monitor.\n\nExample\n-------\n>>> monitor = EMEFieldMonitor(\n... center=(1,2,3),\n... size=(2,2,0),\n... freqs=[300e12],\n... num_modes=2,\n... name=\"eme_field\"\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMEFieldMonitor",
- "enum": [
- "EMEFieldMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "interval_space": {
- "title": "Spatial Interval",
- "description": "Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included.",
- "default": [
- 1,
- 1,
- 1
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "type": "integer",
- "exclusiveMinimum": 0
- },
- {
- "type": "integer",
- "exclusiveMinimum": 0
- },
- {
- "type": "integer",
- "exclusiveMinimum": 0
- }
- ]
- },
- "colocate": {
- "title": "Colocate Fields",
- "description": "Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default (False) is used internally in EME propagation.",
- "default": true,
- "type": "boolean"
- },
- "fields": {
- "title": "Field Components",
- "description": "Collection of field components to store in the monitor.",
- "default": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "array",
- "items": {
- "enum": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "string"
- }
- },
- "freqs": {
- "title": "Monitor Frequencies",
- "description": "Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "num_modes": {
- "title": "Number of Modes",
- "description": "Maximum number of modes for the monitor to record. For 'EMEFieldMonitor', refers to the number of modes at each port.Cannot exceed the max of the number of modes in the two ports. A value of 'None' will record all modes.",
- "minimum": 0,
- "type": "integer"
- },
- "num_sweep": {
- "title": "Number of Sweep Indices",
- "description": "Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.",
- "default": 1,
- "minimum": 0,
- "type": "integer"
- },
- "eme_cell_interval_space": {
- "title": "EME Cell Interval",
- "description": "Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1. Note: this field is not used for EME field monitor.",
- "default": 1,
- "enum": [
- 1
- ],
- "type": "integer"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "EMECoefficientMonitor": {
- "title": "EMECoefficientMonitor",
- "description": "EME monitor for mode coefficients.\nRecords the amplitudes of the forward and backward modes in each cell\nintersecting the monitor geometry.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1], NoneType] = None\n Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.\nnum_modes : Optional[NonNegativeInt] = None\n Maximum number of modes for the monitor to record. Cannot exceed the greatest number of modes in any EME cell. A value of 'None' will record all modes.\nnum_sweep : Optional[NonNegativeInt] = 1\n Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.\ninterval_space : Tuple[Literal[1], Literal[1], Literal[1]] = (1, 1, 1)\n Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included. Not all monitors support values different from 1. Note: This field is not used for 'EMECoefficientMonitor'.\neme_cell_interval_space : PositiveInt = 1\n Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1.\ncolocate : Literal[True] = True\n Defines whether fields are colocated to grid cell boundaries (i.e. to the primal grid) on-the-fly during a solver run. Can be toggled for field recording monitors and is hard-coded for other monitors depending on their specific function.\n\nExample\n-------\n>>> monitor = EMECoefficientMonitor(\n... center=(1,2,3),\n... size=(2,2,2),\n... freqs=[300e12],\n... num_modes=2,\n... name=\"eme_coeffs\"\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMECoefficientMonitor",
- "enum": [
- "EMECoefficientMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "freqs": {
- "title": "Monitor Frequencies",
- "description": "Frequencies at which the monitor will record. Must be a subset of the simulation 'freqs'. A value of 'None' will record at all simulation 'freqs'.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "num_modes": {
- "title": "Number of Modes",
- "description": "Maximum number of modes for the monitor to record. Cannot exceed the greatest number of modes in any EME cell. A value of 'None' will record all modes.",
- "minimum": 0,
- "type": "integer"
- },
- "num_sweep": {
- "title": "Number of Sweep Indices",
- "description": "Number of sweep indices for the monitor to record. Cannot exceed the number of sweep indices for the simulation. If the sweep does not change the monitor data, the sweep index will be omitted. A value of 'None' will record all sweep indices.",
- "default": 1,
- "minimum": 0,
- "type": "integer"
- },
- "interval_space": {
- "title": "Spatial Interval",
- "description": "Number of grid step intervals between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last point of the monitor grid are always included. Not all monitors support values different from 1. Note: This field is not used for 'EMECoefficientMonitor'.",
- "default": [
- 1,
- 1,
- 1
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "eme_cell_interval_space": {
- "title": "EME Cell Interval",
- "description": "Number of eme cells between monitor recordings. If equal to 1, there will be no downsampling. If greater than 1, the step will be applied, but the first and last cells are always included. Not used in all monitors. Not all monitors support values different from 1.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "colocate": {
- "title": "Colocate Fields",
- "description": "Defines whether fields are colocated to grid cell boundaries (i.e. to the primal grid) on-the-fly during a solver run. Can be toggled for field recording monitors and is hard-coded for other monitors depending on their specific function.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "EMEModeSpec": {
- "title": "EMEModeSpec",
- "description": "Mode spec for EME cells. Overrides some of the defaults and allowed values.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_modes : PositiveInt = 1\n Number of modes returned by mode solver.\ntarget_neff : Optional[PositiveFloat] = None\n Guess for effective index of the mode.\nnum_pml : Tuple[NonNegativeInt, NonNegativeInt] = (0, 0)\n Number of standard pml layers to add in the two tangential axes.\nfilter_pol : Optional[Literal['te', 'tm']] = None\n The solver always computes the ``num_modes`` modes closest to the given ``target_neff``. If ``filter_pol==None``, they are simply sorted in order of decreasing effective index. If a polarization filter is selected, the modes are rearranged such that the first ``n_pol`` modes in the list are the ones with the selected polarization fraction larger than or equal to 0.5, while the next ``num_modes - n_pol`` modes are the ones where it is smaller than 0.5 (i.e. the opposite polarization fraction is larger than 0.5). Within each polarization subset, the modes are still ordered by decreasing effective index. ``te``-fraction is defined as the integrated intensity of the E-field component parallel to the first plane axis, normalized to the total in-plane E-field intensity. Conversely, ``tm``-fraction uses the E field component parallel to the second plane axis.\nangle_theta : Literal[0.0] = 0.0\n [units = rad]. Polar angle of the propagation axis from the injection axis. Not currently supported in EME cells. Use an additional 'ModeSolverMonitor' and 'sim_data.smatrix_in_basis' to achieve off-normal injection in EME.\nangle_phi : Literal[0.0] = 0.0\n [units = rad]. Azimuth angle of the propagation axis in the plane orthogonal to the injection axis. Not currently supported in EME cells. Use an additional 'ModeSolverMonitor' and 'sim_data.smatrix_in_basis' to achieve off-normal injection in EME.\nprecision : Literal['auto', 'single', 'double'] = auto\n The solver will be faster and using less memory under single precision, but more accurate under double precision. Choose ``'auto'`` to apply double precision if the simulation contains a good conductor, single precision otherwise.\nbend_radius : Optional[float] = None\n [units = um]. A curvature radius for simulation of waveguide bends. Can be negative, in which case the mode plane center has a smaller value than the curvature center along the tangential axis perpendicular to the bend axis.\nbend_axis : Optional[Literal[0, 1]] = None\n Index into the two tangential axes defining the normal to the plane in which the bend lies. This must be provided if ``bend_radius`` is not ``None``. For example, for a ring in the global xy-plane, and a mode plane in either the xz or the yz plane, the ``bend_axis`` is always 1 (the global z axis).\nangle_rotation : bool = False\n Defines how modes are computed when angle_theta is not zero. If 'False', a coordinate transformation is applied through the permittivity and permeability tensors.If 'True', the structures in the simulation are first rotated to compute a mode solution at a reference plane normal to the structure's azimuthal direction. Then, the fields are rotated to align with the mode plane, using the 'n_eff' calculated at the reference plane. The second option can produce more accurate results, but more care must be taken, for example, in ensuring that the original mode plane intersects the correct geometries in the simulation with rotated structures. Note: currently only supported when 'angle_phi' is a multiple of 'np.pi'.\ntrack_freq : Optional[Literal['central', 'lowest', 'highest']] = None\n Parameter that turns on/off mode tracking based on their similarity. Can take values ``'lowest'``, ``'central'``, or ``'highest'``, which correspond to mode tracking based on the lowest, central, or highest frequency. If ``None`` no mode tracking is performed, which is the default for best performance.\ngroup_index_step : Union[PositiveFloat, bool] = False\n Control the computation of the group index alongside the effective index. If set to a positive value, it sets the fractional frequency step used in the numerical differentiation of the effective index to compute the group index. If set to `True`, the default of 0.005 is used.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "num_modes": {
- "title": "Number of modes",
- "description": "Number of modes returned by mode solver.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "target_neff": {
- "title": "Target effective index",
- "description": "Guess for effective index of the mode.",
- "exclusiveMinimum": 0,
- "type": "number"
- },
- "num_pml": {
- "title": "Number of PML layers",
- "description": "Number of standard pml layers to add in the two tangential axes.",
- "default": [
- 0,
- 0
- ],
- "type": "array",
- "minItems": 2,
- "maxItems": 2,
- "items": [
- {
- "type": "integer",
- "minimum": 0
- },
- {
- "type": "integer",
- "minimum": 0
- }
- ]
- },
- "filter_pol": {
- "title": "Polarization filtering",
- "description": "The solver always computes the ``num_modes`` modes closest to the given ``target_neff``. If ``filter_pol==None``, they are simply sorted in order of decreasing effective index. If a polarization filter is selected, the modes are rearranged such that the first ``n_pol`` modes in the list are the ones with the selected polarization fraction larger than or equal to 0.5, while the next ``num_modes - n_pol`` modes are the ones where it is smaller than 0.5 (i.e. the opposite polarization fraction is larger than 0.5). Within each polarization subset, the modes are still ordered by decreasing effective index. ``te``-fraction is defined as the integrated intensity of the E-field component parallel to the first plane axis, normalized to the total in-plane E-field intensity. Conversely, ``tm``-fraction uses the E field component parallel to the second plane axis.",
- "enum": [
- "te",
- "tm"
- ],
- "type": "string"
- },
- "angle_theta": {
- "title": "Polar Angle",
- "description": "Polar angle of the propagation axis from the injection axis. Not currently supported in EME cells. Use an additional 'ModeSolverMonitor' and 'sim_data.smatrix_in_basis' to achieve off-normal injection in EME.",
- "default": 0.0,
- "units": "rad",
- "enum": [
- 0.0
- ],
- "type": "number"
- },
- "angle_phi": {
- "title": "Azimuth Angle",
- "description": "Azimuth angle of the propagation axis in the plane orthogonal to the injection axis. Not currently supported in EME cells. Use an additional 'ModeSolverMonitor' and 'sim_data.smatrix_in_basis' to achieve off-normal injection in EME.",
- "default": 0.0,
- "units": "rad",
- "enum": [
- 0.0
- ],
- "type": "number"
- },
- "precision": {
- "title": "single, double, or automatic precision in mode solver",
- "description": "The solver will be faster and using less memory under single precision, but more accurate under double precision. Choose ``'auto'`` to apply double precision if the simulation contains a good conductor, single precision otherwise.",
- "default": "auto",
- "enum": [
- "auto",
- "single",
- "double"
- ],
- "type": "string"
- },
- "bend_radius": {
- "title": "Bend radius",
- "description": "A curvature radius for simulation of waveguide bends. Can be negative, in which case the mode plane center has a smaller value than the curvature center along the tangential axis perpendicular to the bend axis.",
- "units": "um",
- "type": "number"
- },
- "bend_axis": {
- "title": "Bend axis",
- "description": "Index into the two tangential axes defining the normal to the plane in which the bend lies. This must be provided if ``bend_radius`` is not ``None``. For example, for a ring in the global xy-plane, and a mode plane in either the xz or the yz plane, the ``bend_axis`` is always 1 (the global z axis).",
- "enum": [
- 0,
- 1
- ],
- "type": "integer"
- },
- "angle_rotation": {
- "title": "Use fields rotation when angle_theta is not zero",
- "description": "Defines how modes are computed when angle_theta is not zero. If 'False', a coordinate transformation is applied through the permittivity and permeability tensors.If 'True', the structures in the simulation are first rotated to compute a mode solution at a reference plane normal to the structure's azimuthal direction. Then, the fields are rotated to align with the mode plane, using the 'n_eff' calculated at the reference plane. The second option can produce more accurate results, but more care must be taken, for example, in ensuring that the original mode plane intersects the correct geometries in the simulation with rotated structures. Note: currently only supported when 'angle_phi' is a multiple of 'np.pi'.",
- "default": false,
- "type": "boolean"
- },
- "track_freq": {
- "title": "Mode Tracking Frequency",
- "description": "Parameter that turns on/off mode tracking based on their similarity. Can take values ``'lowest'``, ``'central'``, or ``'highest'``, which correspond to mode tracking based on the lowest, central, or highest frequency. If ``None`` no mode tracking is performed, which is the default for best performance.",
- "enum": [
- "central",
- "lowest",
- "highest"
- ],
- "type": "string"
- },
- "group_index_step": {
- "title": "Frequency step for group index computation",
- "description": "Control the computation of the group index alongside the effective index. If set to a positive value, it sets the fractional frequency step used in the numerical differentiation of the effective index to compute the group index. If set to `True`, the default of 0.005 is used.",
- "default": false,
- "anyOf": [
- {
- "type": "number",
- "exclusiveMinimum": 0
- },
- {
- "type": "boolean"
- }
- ]
- },
- "type": {
- "title": "Type",
- "default": "EMEModeSpec",
- "enum": [
- "EMEModeSpec"
- ],
- "type": "string"
- }
- },
- "additionalProperties": false
- },
- "EMEUniformGrid": {
- "title": "EMEUniformGrid",
- "description": "Specification for a uniform EME grid.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_reps : PositiveInt = 1\n Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.\nname : Optional[str] = None\n Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.\nnum_cells : PositiveInt\n Number of cells in the uniform EME grid.\nmode_spec : EMEModeSpec\n Mode specification for the uniform EME grid.\n\nExample\n-------\n>>> from tidy3d import EMEModeSpec\n>>> mode_spec = EMEModeSpec(num_modes=10)\n>>> eme_grid = EMEUniformGrid(num_cells=10, mode_spec=mode_spec)",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "num_reps": {
- "title": "Number of Repetitions",
- "description": "Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "name": {
- "title": "Name",
- "description": "Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "EMEUniformGrid",
- "enum": [
- "EMEUniformGrid"
- ],
- "type": "string"
- },
- "num_cells": {
- "title": "Number of cells",
- "description": "Number of cells in the uniform EME grid.",
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "mode_spec": {
- "title": "Mode Specification",
- "description": "Mode specification for the uniform EME grid.",
- "allOf": [
- {
- "$ref": "#/definitions/EMEModeSpec"
- }
- ]
- }
- },
- "required": [
- "num_cells",
- "mode_spec"
- ],
- "additionalProperties": false
- },
- "EMEExplicitGrid": {
- "title": "EMEExplicitGrid",
- "description": "EME grid with explicitly defined internal boundaries.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_reps : PositiveInt = 1\n Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.\nname : Optional[str] = None\n Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.\nmode_specs : List[EMEModeSpec]\n Mode specifications for each cell in the explicit EME grid.\nboundaries : ArrayLike[dtype=float, ndim=1]\n List of coordinates of internal cell boundaries along the propagation axis. Must contain one fewer item than 'mode_specs', and must be strictly increasing. Each cell spans the region between an adjacent pair of boundaries. The first (last) cell spans the region between the first (last) boundary and the simulation boundary.\n\nExample\n-------\n>>> from tidy3d import EMEExplicitGrid, EMEModeSpec\n>>> mode_spec1 = EMEModeSpec(num_modes=10)\n>>> mode_spec2 = EMEModeSpec(num_modes=20)\n>>> eme_grid = EMEExplicitGrid(\n... mode_specs=[mode_spec1, mode_spec2],\n... boundaries=[1],\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "num_reps": {
- "title": "Number of Repetitions",
- "description": "Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "name": {
- "title": "Name",
- "description": "Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "EMEExplicitGrid",
- "enum": [
- "EMEExplicitGrid"
- ],
- "type": "string"
- },
- "mode_specs": {
- "title": "Mode Specifications",
- "description": "Mode specifications for each cell in the explicit EME grid.",
- "type": "array",
- "items": {
- "$ref": "#/definitions/EMEModeSpec"
- }
- },
- "boundaries": {
- "title": "Boundaries",
- "description": "List of coordinates of internal cell boundaries along the propagation axis. Must contain one fewer item than 'mode_specs', and must be strictly increasing. Each cell spans the region between an adjacent pair of boundaries. The first (last) cell spans the region between the first (last) boundary and the simulation boundary.",
- "type": "ArrayLike"
- }
- },
- "required": [
- "mode_specs",
- "boundaries"
- ],
- "additionalProperties": false
- },
- "EMECompositeGrid": {
- "title": "EMECompositeGrid",
- "description": "EME grid made out of multiple subgrids.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_reps : PositiveInt = 1\n Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.\nname : Optional[str] = None\n Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.\nsubgrids : ForwardRef('list[EMESubgridType]')\n Subgrids in the composite grid.\nsubgrid_boundaries : ArrayLike[dtype=float, ndim=1]\n List of coordinates of internal subgrid boundaries along the propagation axis. Must contain one fewer item than 'subgrids', and must be strictly increasing. Each subgrid spans the region between an adjacent pair of subgrid boundaries. The first (last) subgrid spans the region between the first (last) subgrid boundary and the simulation boundary.\n\nExample\n-------\n>>> from tidy3d import EMEUniformGrid, EMEModeSpec\n>>> mode_spec1 = EMEModeSpec(num_modes=10)\n>>> mode_spec2 = EMEModeSpec(num_modes=20)\n>>> subgrid1 = EMEUniformGrid(num_cells=5, mode_spec=mode_spec1)\n>>> subgrid2 = EMEUniformGrid(num_cells=10, mode_spec=mode_spec2)\n>>> eme_grid = EMECompositeGrid(\n... subgrids=[subgrid1, subgrid2],\n... subgrid_boundaries=[1]\n... )",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "num_reps": {
- "title": "Number of Repetitions",
- "description": "Number of periodic repetitions of this EME grid. Useful for efficiently simulating long periodic structures like Bragg gratings. Instead of explicitly repeating the cells, setting 'num_reps' allows the EME solver to reuse the modes and cell interface scattering matrices.",
- "default": 1,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "name": {
- "title": "Name",
- "description": "Name of this 'EMEGridSpec'. Used in 'EMEPeriodicitySweep'.",
- "type": "string"
- },
- "type": {
- "title": "Type",
- "default": "EMECompositeGrid",
- "enum": [
- "EMECompositeGrid"
- ],
- "type": "string"
- },
- "subgrids": {
- "title": "Subgrids",
- "description": "Subgrids in the composite grid.",
- "type": "array",
- "items": {
- "anyOf": [
- {
- "$ref": "#/definitions/EMEUniformGrid"
- },
- {
- "$ref": "#/definitions/EMEExplicitGrid"
- },
- {
- "$ref": "#/definitions/EMECompositeGrid"
- }
- ]
- }
- },
- "subgrid_boundaries": {
- "title": "Subgrid Boundaries",
- "description": "List of coordinates of internal subgrid boundaries along the propagation axis. Must contain one fewer item than 'subgrids', and must be strictly increasing. Each subgrid spans the region between an adjacent pair of subgrid boundaries. The first (last) subgrid spans the region between the first (last) subgrid boundary and the simulation boundary.",
- "type": "ArrayLike"
- }
- },
- "required": [
- "subgrids",
- "subgrid_boundaries"
- ],
- "additionalProperties": false
- },
- "EMELengthSweep": {
- "title": "EMELengthSweep",
- "description": "Spec for sweeping EME cell lengths.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nscale_factors : ArrayLike\n Length scale factors to be used in the EME propagation step. The EME propagation step is repeated after scaling every cell length by this amount. The results are stored in 'sim_data.smatrix'. If a 2D array is provided, the first index is the sweep index and the second index is the cell index, allowing a nonuniform cell scaling along the propagation axis.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMELengthSweep",
- "enum": [
- "EMELengthSweep"
- ],
- "type": "string"
- },
- "scale_factors": {
- "title": "Length Scale Factor",
- "description": "Length scale factors to be used in the EME propagation step. The EME propagation step is repeated after scaling every cell length by this amount. The results are stored in 'sim_data.smatrix'. If a 2D array is provided, the first index is the sweep index and the second index is the cell index, allowing a nonuniform cell scaling along the propagation axis.",
- "type": "ArrayLike"
- }
- },
- "required": [
- "scale_factors"
- ],
- "additionalProperties": false
- },
- "EMEModeSweep": {
- "title": "EMEModeSweep",
- "description": "Spec for sweeping number of modes in EME propagation step.\nUsed for convergence testing.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_modes : ArrayLike[dtype=int, ndim=1]\n Max number of modes to use in the EME propagation step. The EME propagation step is repeated after dropping modes with mode_index exceeding this value. This can be used for convergence testing; reliable results should be independent of the number of modes used. This value cannot exceed the maximum number of modes in any EME cell in the simulation.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMEModeSweep",
- "enum": [
- "EMEModeSweep"
- ],
- "type": "string"
- },
- "num_modes": {
- "title": "Number of Modes",
- "description": "Max number of modes to use in the EME propagation step. The EME propagation step is repeated after dropping modes with mode_index exceeding this value. This can be used for convergence testing; reliable results should be independent of the number of modes used. This value cannot exceed the maximum number of modes in any EME cell in the simulation.",
- "type": "ArrayLike"
- }
- },
- "required": [
- "num_modes"
- ],
- "additionalProperties": false
- },
- "EMEFreqSweep": {
- "title": "EMEFreqSweep",
- "description": "Spec for sweeping frequency in EME propagation step.\nUnlike ``sim.freqs``, the frequency sweep is approximate, using a\nperturbative mode solver relative to the simulation EME modes.\nThis can be a faster way to solve at a larger number of frequencies.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nfreq_scale_factors : ArrayLike[dtype=float, ndim=1]\n Scale factors applied to every frequency in 'EMESimulation.freqs'. After applying the scale factors, the new modes are computed approximately using the exact modes as a basis. If there are multiple 'EMESimulation.freqs', the exact modes are computed at each of those frequencies, and then the scale factors are applied to each independently.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMEFreqSweep",
- "enum": [
- "EMEFreqSweep"
- ],
- "type": "string"
- },
- "freq_scale_factors": {
- "title": "Frequency Scale Factors",
- "description": "Scale factors applied to every frequency in 'EMESimulation.freqs'. After applying the scale factors, the new modes are computed approximately using the exact modes as a basis. If there are multiple 'EMESimulation.freqs', the exact modes are computed at each of those frequencies, and then the scale factors are applied to each independently.",
- "type": "ArrayLike"
- }
- },
- "required": [
- "freq_scale_factors"
- ],
- "additionalProperties": false
- },
- "EMEPeriodicitySweep": {
- "title": "EMEPeriodicitySweep",
- "description": "Spec for sweeping number of repetitions of EME subgrids.\nUseful for simulating long periodic structures like Bragg gratings,\nas it allows the EME solver to reuse the modes and cell interface\nscattering matrices.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nnum_reps : List[dict[str, pydantic.v1.types.PositiveInt]]\n Number of periodic repetitions of named subgrids in this EME grid. At each sweep index, contains a dict mapping the name of a subgrid to the number of repetitions of that subgrid at that sweep index.\n\nCompared to setting ``num_reps`` directly in the ``eme_grid_spec``,\nthis sweep spec allows varying the number of repetitions,\neffectively simulating multiple structures in a single EME simulation.\n\nExample\n-------\n>>> n_list = [1, 50, 100]\n>>> sweep_spec = EMEPeriodicitySweep(num_reps=[{\"unit_cell\": n} for n in n_list])",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMEPeriodicitySweep",
- "enum": [
- "EMEPeriodicitySweep"
- ],
- "type": "string"
- },
- "num_reps": {
- "title": "Number of Repetitions",
- "description": "Number of periodic repetitions of named subgrids in this EME grid. At each sweep index, contains a dict mapping the name of a subgrid to the number of repetitions of that subgrid at that sweep index.",
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": {
- "type": "integer",
- "exclusiveMinimum": 0
- }
- }
- }
- },
- "required": [
- "num_reps"
- ],
- "additionalProperties": false
- },
- "EMESimulation": {
- "title": "EMESimulation",
- "description": "EigenMode Expansion (EME) simulation.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nmedium : Union[Medium, AnisotropicMedium, PECMedium, PMCMedium, PoleResidue, Sellmeier, Lorentz, Debye, Drude, FullyAnisotropicMedium, CustomMedium, CustomPoleResidue, CustomSellmeier, CustomLorentz, CustomDebye, CustomDrude, CustomAnisotropicMedium, PerturbationMedium, PerturbationPoleResidue, LossyMetalMedium] = Medium(attrs={}, name=None, frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, viz_spec=None, heat_spec=None, type='Medium', permittivity=1.0, conductivity=0.0)\n Background medium of simulation, defaults to vacuum if not specified.\nstructures : Tuple[Structure, ...] = ()\n Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.\nsymmetry : Tuple[Literal[0, -1, 1], Literal[0, -1, 1], Literal[0, -1, 1]] = (0, 0, 0)\n Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. \nsources : Tuple[NoneType, ...] = ()\n Sources in the simulation. NOTE: sources are not currently supported for EME simulations. Instead, the simulation performs full bidirectional propagation in the 'port_mode' basis. After running the simulation, use 'smatrix_in_basis' to use another set of modes or input field.\nboundary_spec : BoundarySpec = BoundarySpec(attrs={}, x=Boundary(attrs={},, plus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, minus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, type='Boundary'), y=Boundary(attrs={},, plus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, minus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, type='Boundary'), z=Boundary(attrs={},, plus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, minus=PECBoundary(attrs={},, name=None,, type='PECBoundary'),, type='Boundary'), type='BoundarySpec')\n Specification of boundary conditions along each dimension. By default, PEC boundary conditions are applied on all sides. This field is for consistency with FDTD simulations; however, please note that regardless of the 'boundary_spec', the mode solver terminates the mode plane with PEC boundary. The 'EMEModeSpec' can be used to apply PML layers in the mode solver.\nmonitors : Tuple[Annotated[Union[tidy3d.components.eme.monitor.EMEModeSolverMonitor, tidy3d.components.eme.monitor.EMEFieldMonitor, tidy3d.components.eme.monitor.EMECoefficientMonitor, tidy3d.components.monitor.ModeSolverMonitor, tidy3d.components.monitor.PermittivityMonitor], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n Tuple of monitors in the simulation. Note: monitor names are used to access data after simulation is run.\ngrid_spec : GridSpec = GridSpec(attrs={}, grid_x=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), grid_y=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), grid_z=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), wavelength=None, override_structures=(), snapping_points=(), layer_refinement_specs=(), type='GridSpec')\n Specifications for the simulation grid along each of the three directions. This is distinct from 'eme_grid_spec', which defines the 1D EME grid in the propagation direction.\nversion : str = 2.9.0\n String specifying the front end version number.\nplot_length_units : Optional[Literal['nm', '\u03bcm', 'um', 'mm', 'cm', 'm']] = \u03bcm\n When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.\nstructure_priority_mode : Literal['equal', 'conductor'] = equal\n This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.\nlumped_elements : Tuple[Annotated[Union[tidy3d.components.lumped_element.LumpedResistor, tidy3d.components.lumped_element.CoaxialLumpedResistor, tidy3d.components.lumped_element.LinearLumpedElement], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n Tuple of lumped elements in the simulation. Note: only :class:`tidy3d.LumpedResistor` is supported currently.\nsubpixel : Union[bool, SubpixelSpec] = SubpixelSpec(attrs={}, dielectric=PolarizedAveraging(attrs={},, type='PolarizedAveraging'), metal=Staircasing(attrs={},, type='Staircasing'), pec=PECConformal(attrs={},, type='PECConformal',, timestep_reduction=0.3,, edge_singularity_correction=False), pmc=Staircasing(attrs={},, type='Staircasing'), lossy_metal=SurfaceImpedance(attrs={},, type='SurfaceImpedance',, timestep_reduction=0.0,, edge_singularity_correction=False), type='SubpixelSpec')\n Apply subpixel averaging methods of the permittivity on structure interfaces to result in much higher accuracy for a given grid size. Supply a :class:`SubpixelSpec` to this field to select subpixel averaging methods separately on dielectric, metal, and PEC material interfaces. Alternatively, user may supply a boolean value: ``True`` to apply the default subpixel averaging methods corresponding to ``SubpixelSpec()`` , or ``False`` to apply staircasing.\nsimulation_type : Optional[Literal['autograd_fwd', 'autograd_bwd', 'tidy3d', None]] = tidy3d\n Tag used internally to distinguish types of simulations for ``autograd`` gradient processing.\npost_norm : Union[float, FreqDataArray] = 1.0\n Factor to multiply the fields by after running, given the adjoint source pipeline used. Note: this is used internally only.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n Frequencies for the EME simulation. The field is propagated independently at each provided frequency. This can be slow when the number of frequencies is large. In this case, consider using the approximate 'EMEFreqSweep' as the 'sweep_spec' instead of providing all desired frequencies here.\naxis : Literal[0, 1, 2]\n Propagation axis (0, 1, or 2) for the EME simulation.\neme_grid_spec : Union[EMEUniformGrid, EMECompositeGrid, EMEExplicitGrid]\n Specification for the EME propagation grid. The simulation is divided into cells in the propagation direction; this parameter specifies the layout of those cells. Mode solving is performed in each cell, and then propagation between cells is performed to determine the complete solution. This is distinct from 'grid_spec', which defines the grid in the two tangential directions, as well as the grid used for field monitors.\nstore_port_modes : bool = True\n Whether to store the modes associated with the two ports. Required to find scattering matrix in basis besides the computational basis.\nnormalize : bool = True\n Whether to normalize the port modes to unity flux, thereby normalizing the scattering matrix and expansion coefficients.\nport_offsets : Tuple[NonNegativeFloat, NonNegativeFloat] = (0, 0)\n Offsets for the two ports, relative to the simulation bounds along the propagation axis.\nsweep_spec : Union[EMELengthSweep, EMEModeSweep, EMEFreqSweep, EMEPeriodicitySweep, NoneType] = None\n Specification for a parameter sweep to be performed during the EME propagation step. The results are stored in 'sim_data.smatrix'. Other simulation monitor data is not included in the sweep.\nconstraint : Optional[Literal['passive', 'unitary']] = passive\n Constraint for EME propagation, imposed at cell interfaces. A constraint of 'passive' means that energy can be dissipated but not created at interfaces. A constraint of 'unitary' means that energy is conserved at interfaces (but not necessarily within cells). The option 'none' may be faster for a large number of modes. The option 'passive' can serve as regularization for the field continuity requirement and give more physical results.\n\nNotes\n-----\n\n EME is a frequency-domain method for propagating the electromagnetic field along a\n specified axis. The method is well-suited for propagation of guided waves.\n The electromagnetic fields are expanded locally in the basis of eigenmodes of the\n waveguide; they are then propagated by imposing continuity conditions in this basis.\n\n The EME simulation is performed along the propagation axis ``axis`` at frequencies ``freqs``.\n The simulation is divided into cells along the propagation axis, as defined by\n ``eme_grid_spec``. Mode solving is performed at cell centers, and boundary conditions are\n imposed between cells. The EME ports are defined to be the boundaries of the first and last\n cell in the EME grid. These can be moved using ``port_offsets``.\n\n An EME simulation always computes the full scattering matrix of the structure.\n Additional data can be recorded by adding 'monitors' to the simulation.\n\n **Other Bases**\n\n By default, the scattering matrix is expressed in the basis of EME modes at the two ports. It is sometimes useful to use alternative bases. For example, in a waveguide splitter, we might want the scattering matrix in the basis of modes of the individual waveguides. The functions `smatrix_in_basis` and `field_in_basis` in :class:`.EMESimulationData` can be used for this purpose after the simulation has been run.\n\n **Frequency Sweeps**\n\n Frequency sweeps are supported by including multiple frequencies in the `freqs` field. However, our EME solver repeats the mode solving for each new frequency, so frequency sweeps involving a large number of frequencies can be slow and expensive. If a large number of frequencies are required, consider using our FDTD solver instead.\n\n **Passivity and Unitarity Constraints**\n\n Passivity and unitarity constraints can be imposed via the `constraint` field. These constraints are imposed at interfaces between cells, possibly at the expense of field continuity. Passivity means that the interface can only dissipate energy, and unitarity means the interface will conserve energy (energy may still be dissipated inside cells when the propagation constant is complex). Adding constraints can slow down the simulation significantly, especially for a large number of modes (more than 30 or 40).\n\n **Too Many Modes**\n\n It is important to use enough modes to capture the physics of the device and to ensure that the results have converged (see below). However, using too many modes can slow down the simulation and result in numerical issues. If too many modes are used, it is common to see a warning about invalid modes in the simulation log. While these modes are not included in the EME propagation, this can indicate some other issue with the setup, especially if the results have not converged. In this case, extending the simulation size in the transverse directions and increasing the grid resolution may help by creating more valid modes that can be used in convergence testing.\n\n **Mode Convergence Sweeps**\n\n It is a good idea to check that the number of modes is large enough by running a mode convergence sweep. This can be done using :class:`.EMEModeSweep`.\n\nExample\n-------\n>>> from tidy3d import Box, Medium, Structure, C_0, inf\n>>> from tidy3d import EMEModeSpec, EMEUniformGrid, GridSpec\n>>> from tidy3d import EMEFieldMonitor\n>>> lambda0 = 1550e-9\n>>> freq0 = C_0 / lambda0\n>>> sim_size = 3*lambda0, 3*lambda0, 3*lambda0\n>>> waveguide_size = (lambda0/2, lambda0, inf)\n>>> waveguide = Structure(\n... geometry=Box(center=(0,0,0), size=waveguide_size),\n... medium=Medium(permittivity=2)\n... )\n>>> eme_grid_spec = EMEUniformGrid(num_cells=5, mode_spec=EMEModeSpec(num_modes=10))\n>>> grid_spec = GridSpec(wavelength=lambda0)\n>>> field_monitor = EMEFieldMonitor(\n... size=(0, sim_size[1], sim_size[2]),\n... name=\"field_monitor\"\n... )\n>>> sim = EMESimulation(\n... size=sim_size,\n... monitors=[field_monitor],\n... structures=[waveguide],\n... axis=2,\n... freqs=[freq0],\n... eme_grid_spec=eme_grid_spec,\n... grid_spec=grid_spec\n... )\n\nSee Also\n--------\n\n**Notebooks:**\n * `EME Solver Demonstration <../../notebooks/docs/features/eme.rst>`_",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "EMESimulation",
- "enum": [
- "EMESimulation"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "medium": {
- "title": "Background Medium",
- "description": "Background medium of simulation, defaults to vacuum if not specified.",
- "default": {
- "attrs": {},
- "name": null,
- "frequency_range": null,
- "allow_gain": false,
- "nonlinear_spec": null,
- "modulation_spec": null,
- "viz_spec": null,
- "heat_spec": null,
- "type": "Medium",
- "permittivity": 1.0,
- "conductivity": 0.0
- },
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Medium": "#/definitions/Medium",
- "AnisotropicMedium": "#/definitions/AnisotropicMedium",
- "PECMedium": "#/definitions/PECMedium",
- "PMCMedium": "#/definitions/PMCMedium",
- "PoleResidue": "#/definitions/PoleResidue",
- "Sellmeier": "#/definitions/Sellmeier",
- "Lorentz": "#/definitions/Lorentz",
- "Debye": "#/definitions/Debye",
- "Drude": "#/definitions/Drude",
- "FullyAnisotropicMedium": "#/definitions/FullyAnisotropicMedium",
- "CustomMedium": "#/definitions/CustomMedium",
- "CustomPoleResidue": "#/definitions/CustomPoleResidue",
- "CustomSellmeier": "#/definitions/CustomSellmeier",
- "CustomLorentz": "#/definitions/CustomLorentz",
- "CustomDebye": "#/definitions/CustomDebye",
- "CustomDrude": "#/definitions/CustomDrude",
- "CustomAnisotropicMedium": "#/definitions/CustomAnisotropicMedium",
- "PerturbationMedium": "#/definitions/PerturbationMedium",
- "PerturbationPoleResidue": "#/definitions/PerturbationPoleResidue",
- "LossyMetalMedium": "#/definitions/LossyMetalMedium"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Medium"
- },
- {
- "$ref": "#/definitions/AnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PECMedium"
- },
- {
- "$ref": "#/definitions/PMCMedium"
- },
- {
- "$ref": "#/definitions/PoleResidue"
- },
- {
- "$ref": "#/definitions/Sellmeier"
- },
- {
- "$ref": "#/definitions/Lorentz"
- },
- {
- "$ref": "#/definitions/Debye"
- },
- {
- "$ref": "#/definitions/Drude"
- },
- {
- "$ref": "#/definitions/FullyAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/CustomMedium"
- },
- {
- "$ref": "#/definitions/CustomPoleResidue"
- },
- {
- "$ref": "#/definitions/CustomSellmeier"
- },
- {
- "$ref": "#/definitions/CustomLorentz"
- },
- {
- "$ref": "#/definitions/CustomDebye"
- },
- {
- "$ref": "#/definitions/CustomDrude"
- },
- {
- "$ref": "#/definitions/CustomAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PerturbationMedium"
- },
- {
- "$ref": "#/definitions/PerturbationPoleResidue"
- },
- {
- "$ref": "#/definitions/LossyMetalMedium"
- }
- ]
- },
- "structures": {
- "title": "Structures",
- "description": "Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/Structure"
- }
- },
- "symmetry": {
- "title": "Symmetries",
- "description": "Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. ",
- "default": [
- 0,
- 0,
- 0
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "sources": {
- "title": "Sources",
- "description": "Sources in the simulation. NOTE: sources are not currently supported for EME simulations. Instead, the simulation performs full bidirectional propagation in the 'port_mode' basis. After running the simulation, use 'smatrix_in_basis' to use another set of modes or input field.",
- "default": [],
- "type": "array",
- "items": {
- "type": "null"
- }
- },
- "boundary_spec": {
- "title": "Boundaries",
- "description": "Specification of boundary conditions along each dimension. By default, PEC boundary conditions are applied on all sides. This field is for consistency with FDTD simulations; however, please note that regardless of the 'boundary_spec', the mode solver terminates the mode plane with PEC boundary. The 'EMEModeSpec' can be used to apply PML layers in the mode solver.",
- "default": {
- "attrs": {},
- "x": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "type": "Boundary"
- },
- "y": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "type": "Boundary"
- },
- "z": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PECBoundary"
- },
- "type": "Boundary"
- },
- "type": "BoundarySpec"
- },
- "allOf": [
- {
- "$ref": "#/definitions/BoundarySpec"
- }
- ]
- },
- "monitors": {
- "title": "Monitors",
- "description": "Tuple of monitors in the simulation. Note: monitor names are used to access data after simulation is run.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "EMEModeSolverMonitor": "#/definitions/EMEModeSolverMonitor",
- "EMEFieldMonitor": "#/definitions/EMEFieldMonitor",
- "EMECoefficientMonitor": "#/definitions/EMECoefficientMonitor",
- "ModeSolverMonitor": "#/definitions/ModeSolverMonitor",
- "PermittivityMonitor": "#/definitions/PermittivityMonitor"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/EMEModeSolverMonitor"
- },
- {
- "$ref": "#/definitions/EMEFieldMonitor"
- },
- {
- "$ref": "#/definitions/EMECoefficientMonitor"
- },
- {
- "$ref": "#/definitions/ModeSolverMonitor"
- },
- {
- "$ref": "#/definitions/PermittivityMonitor"
- }
- ]
- }
- },
- "grid_spec": {
- "title": "Grid Specification",
- "description": "Specifications for the simulation grid along each of the three directions. This is distinct from 'eme_grid_spec', which defines the 1D EME grid in the propagation direction.",
- "default": {
- "attrs": {},
- "grid_x": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "grid_y": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "grid_z": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "wavelength": null,
- "override_structures": [],
- "snapping_points": [],
- "layer_refinement_specs": [],
- "type": "GridSpec"
- },
- "validate_default": true,
- "allOf": [
- {
- "$ref": "#/definitions/GridSpec"
- }
- ]
- },
- "version": {
- "title": "Version",
- "description": "String specifying the front end version number.",
- "default": "2.9.0",
- "type": "string"
- },
- "plot_length_units": {
- "title": "Plot Units",
- "description": "When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.",
- "default": "\u03bcm",
- "enum": [
- "nm",
- "\u03bcm",
- "um",
- "mm",
- "cm",
- "m"
- ],
- "type": "string"
- },
- "structure_priority_mode": {
- "title": "Structure Priority Setting",
- "description": "This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.",
- "default": "equal",
- "enum": [
- "equal",
- "conductor"
- ],
- "type": "string"
- },
- "lumped_elements": {
- "title": "Lumped Elements",
- "description": "Tuple of lumped elements in the simulation. Note: only :class:`tidy3d.LumpedResistor` is supported currently.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "LumpedResistor": "#/definitions/LumpedResistor",
- "CoaxialLumpedResistor": "#/definitions/CoaxialLumpedResistor",
- "LinearLumpedElement": "#/definitions/LinearLumpedElement"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/LumpedResistor"
- },
- {
- "$ref": "#/definitions/CoaxialLumpedResistor"
- },
- {
- "$ref": "#/definitions/LinearLumpedElement"
- }
- ]
- }
- },
- "subpixel": {
- "title": "Subpixel Averaging",
- "description": "Apply subpixel averaging methods of the permittivity on structure interfaces to result in much higher accuracy for a given grid size. Supply a :class:`SubpixelSpec` to this field to select subpixel averaging methods separately on dielectric, metal, and PEC material interfaces. Alternatively, user may supply a boolean value: ``True`` to apply the default subpixel averaging methods corresponding to ``SubpixelSpec()`` , or ``False`` to apply staircasing.",
- "default": {
- "attrs": {},
- "dielectric": {
- "attrs": {},
- "type": "PolarizedAveraging"
- },
- "metal": {
- "attrs": {},
- "type": "Staircasing"
- },
- "pec": {
- "attrs": {},
- "type": "PECConformal",
- "timestep_reduction": 0.3,
- "edge_singularity_correction": false
- },
- "pmc": {
- "attrs": {},
- "type": "Staircasing"
- },
- "lossy_metal": {
- "attrs": {},
- "type": "SurfaceImpedance",
- "timestep_reduction": 0.0,
- "edge_singularity_correction": false
- },
- "type": "SubpixelSpec"
- },
- "anyOf": [
- {
- "type": "boolean"
- },
- {
- "$ref": "#/definitions/SubpixelSpec"
- }
- ]
- },
- "simulation_type": {
- "title": "Simulation Type",
- "description": "Tag used internally to distinguish types of simulations for ``autograd`` gradient processing.",
- "default": "tidy3d",
- "enum": [
- "autograd_fwd",
- "autograd_bwd",
- "tidy3d"
- ],
- "type": "string"
- },
- "post_norm": {
- "title": "Post Normalization Values",
- "description": "Factor to multiply the fields by after running, given the adjoint source pipeline used. Note: this is used internally only.",
- "default": 1.0,
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "DataArray",
- "type": "xr.DataArray",
- "properties": {
- "_dims": {
- "title": "_dims",
- "type": "Tuple[str, ...]"
- }
- },
- "required": [
- "_dims"
- ]
- }
- ]
- },
- "freqs": {
- "title": "Frequencies",
- "description": "Frequencies for the EME simulation. The field is propagated independently at each provided frequency. This can be slow when the number of frequencies is large. In this case, consider using the approximate 'EMEFreqSweep' as the 'sweep_spec' instead of providing all desired frequencies here.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "axis": {
- "title": "Propagation Axis",
- "description": "Propagation axis (0, 1, or 2) for the EME simulation.",
- "enum": [
- 0,
- 1,
- 2
- ],
- "type": "integer"
- },
- "eme_grid_spec": {
- "title": "EME Grid Specification",
- "description": "Specification for the EME propagation grid. The simulation is divided into cells in the propagation direction; this parameter specifies the layout of those cells. Mode solving is performed in each cell, and then propagation between cells is performed to determine the complete solution. This is distinct from 'grid_spec', which defines the grid in the two tangential directions, as well as the grid used for field monitors.",
- "anyOf": [
- {
- "$ref": "#/definitions/EMEUniformGrid"
- },
- {
- "$ref": "#/definitions/EMECompositeGrid"
- },
- {
- "$ref": "#/definitions/EMEExplicitGrid"
- }
- ]
- },
- "store_port_modes": {
- "title": "Store Port Modes",
- "description": "Whether to store the modes associated with the two ports. Required to find scattering matrix in basis besides the computational basis.",
- "default": true,
- "type": "boolean"
- },
- "normalize": {
- "title": "Normalize Scattering Matrix",
- "description": "Whether to normalize the port modes to unity flux, thereby normalizing the scattering matrix and expansion coefficients.",
- "default": true,
- "type": "boolean"
- },
- "port_offsets": {
- "title": "Port Offsets",
- "description": "Offsets for the two ports, relative to the simulation bounds along the propagation axis.",
- "default": [
- 0,
- 0
- ],
- "type": "array",
- "minItems": 2,
- "maxItems": 2,
- "items": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "type": "number",
- "minimum": 0
- }
- ]
- },
- "sweep_spec": {
- "title": "EME Sweep Specification",
- "description": "Specification for a parameter sweep to be performed during the EME propagation step. The results are stored in 'sim_data.smatrix'. Other simulation monitor data is not included in the sweep.",
- "anyOf": [
- {
- "$ref": "#/definitions/EMELengthSweep"
- },
- {
- "$ref": "#/definitions/EMEModeSweep"
- },
- {
- "$ref": "#/definitions/EMEFreqSweep"
- },
- {
- "$ref": "#/definitions/EMEPeriodicitySweep"
- }
- ]
- },
- "constraint": {
- "title": "EME Constraint",
- "description": "Constraint for EME propagation, imposed at cell interfaces. A constraint of 'passive' means that energy can be dissipated but not created at interfaces. A constraint of 'unitary' means that energy is conserved at interfaces (but not necessarily within cells). The option 'none' may be faster for a large number of modes. The option 'passive' can serve as regularization for the field continuity requirement and give more physical results.",
- "default": "passive",
- "enum": [
- "passive",
- "unitary"
- ],
- "type": "string"
- }
- },
- "required": [
- "size",
- "freqs",
- "axis",
- "eme_grid_spec"
- ],
- "additionalProperties": false
- },
- "ModeSolver": {
- "title": "ModeSolver",
- "description": "Interface for solving electromagnetic eigenmodes in a 2D plane with translational\ninvariance in the third dimension.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : Union[Simulation, EMESimulation]\n Simulation or EMESimulation defining all structures and mediums.\nplane : Union[Box, ModeSource, ModeMonitor, ModeSolverMonitor]\n Cross-sectional plane in which the mode will be computed.\nmode_spec : ModeSpec\n Container with specifications about the modes to be solved for.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n A list of frequencies at which to solve.\ndirection : Literal['+', '-'] = +\n Direction of waveguide mode propagation along the axis defined by its normal dimension.\ncolocate : bool = True\n Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default is ``True``.\nfields : Tuple[Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'], ...] = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Collection of field components to store in the monitor. Note that some methods like ``flux``, ``dot`` require all tangential field components, while others like ``mode_area`` require all E-field components.\n\nSee Also\n--------\n\n:class:`ModeSource`:\n Injects current source to excite modal profile on finite extent plane.\n\n**Notebooks:**\n * `Waveguide Y junction <../../notebooks/YJunction.html>`_\n * `Photonic crystal waveguide polarization filter <../../../notebooks/PhotonicCrystalWaveguidePolarizationFilter.html>`_\n\n**Lectures:**\n * `Prelude to Integrated Photonics Simulation: Mode Injection `_",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "simulation": {
- "title": "Simulation",
- "description": "Simulation or EMESimulation defining all structures and mediums.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Simulation": "#/definitions/Simulation",
- "EMESimulation": "#/definitions/EMESimulation"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Simulation"
- },
- {
- "$ref": "#/definitions/EMESimulation"
- }
- ]
- },
- "plane": {
- "title": "Plane",
- "description": "Cross-sectional plane in which the mode will be computed.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Box": "#/definitions/Box",
- "ModeSource": "#/definitions/ModeSource",
- "ModeMonitor": "#/definitions/ModeMonitor",
- "ModeSolverMonitor": "#/definitions/ModeSolverMonitor"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Box"
- },
- {
- "$ref": "#/definitions/ModeSource"
- },
- {
- "$ref": "#/definitions/ModeMonitor"
- },
- {
- "$ref": "#/definitions/ModeSolverMonitor"
- }
- ]
- },
- "mode_spec": {
- "title": "Mode specification",
- "description": "Container with specifications about the modes to be solved for.",
- "allOf": [
- {
- "$ref": "#/definitions/ModeSpec"
- }
- ]
- },
- "freqs": {
- "title": "Frequencies",
- "description": "A list of frequencies at which to solve.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "direction": {
- "title": "Propagation direction",
- "description": "Direction of waveguide mode propagation along the axis defined by its normal dimension.",
- "default": "+",
- "enum": [
- "+",
- "-"
- ],
- "type": "string"
- },
- "colocate": {
- "title": "Colocate fields",
- "description": "Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default is ``True``.",
- "default": true,
- "type": "boolean"
- },
- "fields": {
- "title": "Field Components",
- "description": "Collection of field components to store in the monitor. Note that some methods like ``flux``, ``dot`` require all tangential field components, while others like ``mode_area`` require all E-field components.",
- "default": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "array",
- "items": {
- "enum": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "string"
- }
- },
- "type": {
- "title": "Type",
- "default": "ModeSolver",
- "enum": [
- "ModeSolver"
- ],
- "type": "string"
- }
- },
- "required": [
- "simulation",
- "plane",
- "mode_spec",
- "freqs"
- ],
- "additionalProperties": false
- },
- "ModeSimulation": {
- "title": "ModeSimulation",
- "description": "Simulation class for solving electromagnetic eigenmodes in a 2D plane with\ntranslational invariance in the third dimension. If the field ``plane`` is\nspecified, the domain for mode solving is the intersection between the ``plane``\nand the simulation geometry. If the simulation geometry is 2D (has zero size\nin one dimension) and the ``plane`` is ``None``, then the domain for mode solving\nis the entire 2D geometry.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nmedium : Union[Medium, AnisotropicMedium, PECMedium, PMCMedium, PoleResidue, Sellmeier, Lorentz, Debye, Drude, FullyAnisotropicMedium, CustomMedium, CustomPoleResidue, CustomSellmeier, CustomLorentz, CustomDebye, CustomDrude, CustomAnisotropicMedium, PerturbationMedium, PerturbationPoleResidue, LossyMetalMedium] = Medium(attrs={}, name=None, frequency_range=None, allow_gain=False, nonlinear_spec=None, modulation_spec=None, viz_spec=None, heat_spec=None, type='Medium', permittivity=1.0, conductivity=0.0)\n Background medium of simulation, defaults to vacuum if not specified.\nstructures : Tuple[Structure, ...] = ()\n Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.\nsymmetry : Tuple[Literal[0, -1, 1], Literal[0, -1, 1], Literal[0, -1, 1]] = (0, 0, 0)\n Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. \nsources : Tuple[] = ()\n Sources in the simulation. Note: sources are not supported in mode simulations.\nboundary_spec : BoundarySpec = BoundarySpec(attrs={}, x=Boundary(attrs={},, plus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, minus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, type='Boundary'), y=Boundary(attrs={},, plus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, minus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, type='Boundary'), z=Boundary(attrs={},, plus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, minus=PML(attrs={},, name=None,, type='PML',, num_layers=12,, parameters=PMLParams(attrs={},, sigma_order=3,, sigma_min=0.0,, sigma_max=1.5,, type='PMLParams',, kappa_order=3,, kappa_min=1.0,, kappa_max=3.0,, alpha_order=1,, alpha_min=0.0,, alpha_max=0.0)),, type='Boundary'), type='BoundarySpec')\n Specification of boundary conditions along each dimension. If ``None``, PML boundary conditions are applied on all sides. This behavior is for consistency with FDTD simulations; however, please note that the mode solver terminates the mode plane with PEC boundary. The 'ModeSpec' can be used to apply PML layers in the mode solver.\nmonitors : Tuple[PermittivityMonitor, ...] = ()\n Tuple of monitors in the simulation. Note: monitor names are used to access data after simulation is run.\ngrid_spec : GridSpec = GridSpec(attrs={}, grid_x=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), grid_y=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), grid_z=AutoGrid(attrs={},, type='AutoGrid',, max_scale=1.4,, mesher=GradedMesher(attrs={},, type='GradedMesher'),, dl_min=None,, min_steps_per_wvl=10.0,, min_steps_per_sim_size=10.0), wavelength=None, override_structures=(), snapping_points=(), layer_refinement_specs=(), type='GridSpec')\n Specifications for the simulation grid along each of the three directions.\nversion : str = 2.9.0\n String specifying the front end version number.\nplot_length_units : Optional[Literal['nm', '\u03bcm', 'um', 'mm', 'cm', 'm']] = \u03bcm\n When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.\nstructure_priority_mode : Literal['equal', 'conductor'] = equal\n This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.\nlumped_elements : Tuple[Annotated[Union[tidy3d.components.lumped_element.LumpedResistor, tidy3d.components.lumped_element.CoaxialLumpedResistor, tidy3d.components.lumped_element.LinearLumpedElement], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})], ...] = ()\n Tuple of lumped elements in the simulation. Note: only :class:`tidy3d.LumpedResistor` is supported currently.\nsubpixel : Union[bool, SubpixelSpec] = SubpixelSpec(attrs={}, dielectric=PolarizedAveraging(attrs={},, type='PolarizedAveraging'), metal=Staircasing(attrs={},, type='Staircasing'), pec=PECConformal(attrs={},, type='PECConformal',, timestep_reduction=0.3,, edge_singularity_correction=False), pmc=Staircasing(attrs={},, type='Staircasing'), lossy_metal=SurfaceImpedance(attrs={},, type='SurfaceImpedance',, timestep_reduction=0.0,, edge_singularity_correction=False), type='SubpixelSpec')\n Apply subpixel averaging methods of the permittivity on structure interfaces to result in much higher accuracy for a given grid size. Supply a :class:`SubpixelSpec` to this field to select subpixel averaging methods separately on dielectric, metal, and PEC material interfaces. Alternatively, user may supply a boolean value: ``True`` to apply the default subpixel averaging methods corresponding to ``SubpixelSpec()`` , or ``False`` to apply staircasing.\nsimulation_type : Optional[Literal['autograd_fwd', 'autograd_bwd', 'tidy3d', None]] = tidy3d\n Tag used internally to distinguish types of simulations for ``autograd`` gradient processing.\npost_norm : Union[float, FreqDataArray] = 1.0\n Factor to multiply the fields by after running, given the adjoint source pipeline used. Note: this is used internally only.\nmode_spec : ModeSpec\n Container with specifications about the modes to be solved for.\nfreqs : Union[tuple[float, ...], ArrayLike[dtype=float, ndim=1]]\n A list of frequencies at which to solve.\ndirection : Literal['+', '-'] = +\n Direction of waveguide mode propagation along the axis defined by its normal dimension.\ncolocate : bool = True\n Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default is ``True``.\nfields : Tuple[Literal['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'], ...] = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']\n Collection of field components to store in the monitor. Note that some methods like ``flux``, ``dot`` require all tangential field components, while others like ``mode_area`` require all E-field components.\nplane : Union[Box, ModeSource, ModeMonitor, ModeSolverMonitor] = None\n Cross-sectional plane in which the mode will be computed. If provided, the computational domain will be the intersection between the provided ``plane`` and the simulation geometry. If ``None``, the simulation must be 2D, and the plane will be the entire simulation geometry.\n\nThe ``symmetry`` field can be used to enforce reflection symmetry across planes\nthrough the ``center`` of the simulation. Each component of the ``symmetry`` field\nis only used if the ``center`` of the ``plane`` and the simulation geometry\ncoincide in that component. Symmetry normal to the mode solving domain has no\neffect; the field ``filter_pol`` in :class:`.ModeSpec` can be used here instead.\n\nExample\n-------\n>>> from tidy3d import C_0, ModeSpec, BoundarySpec, Boundary\n>>> lambda0 = 1550e-9\n>>> freq0 = C_0 / lambda0\n>>> freqs = [freq0]\n>>> sim_size = lambda0, lambda0, 0\n>>> mode_spec = ModeSpec(num_modes=4)\n>>> boundary_spec = BoundarySpec(\n... x=Boundary.pec(),\n... y=Boundary.pec(),\n... z=Boundary.periodic()\n... )\n>>> sim = ModeSimulation(\n... size=sim_size,\n... freqs=freqs,\n... mode_spec=mode_spec,\n... boundary_spec=boundary_spec\n... )\n\nSee Also\n--------\n\n:class:`ModeSource`:\n Injects current source to excite modal profile on finite extent plane.\n\n**Notebooks:**\n * `Waveguide Y junction <../../notebooks/YJunction.html>`_\n * `Photonic crystal waveguide polarization filter <../../../notebooks/PhotonicCrystalWaveguidePolarizationFilter.html>`_\n\n**Lectures:**\n * `Prelude to Integrated Photonics Simulation: Mode Injection `_",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "ModeSimulation",
- "enum": [
- "ModeSimulation"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "medium": {
- "title": "Background Medium",
- "description": "Background medium of simulation, defaults to vacuum if not specified.",
- "default": {
- "attrs": {},
- "name": null,
- "frequency_range": null,
- "allow_gain": false,
- "nonlinear_spec": null,
- "modulation_spec": null,
- "viz_spec": null,
- "heat_spec": null,
- "type": "Medium",
- "permittivity": 1.0,
- "conductivity": 0.0
- },
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Medium": "#/definitions/Medium",
- "AnisotropicMedium": "#/definitions/AnisotropicMedium",
- "PECMedium": "#/definitions/PECMedium",
- "PMCMedium": "#/definitions/PMCMedium",
- "PoleResidue": "#/definitions/PoleResidue",
- "Sellmeier": "#/definitions/Sellmeier",
- "Lorentz": "#/definitions/Lorentz",
- "Debye": "#/definitions/Debye",
- "Drude": "#/definitions/Drude",
- "FullyAnisotropicMedium": "#/definitions/FullyAnisotropicMedium",
- "CustomMedium": "#/definitions/CustomMedium",
- "CustomPoleResidue": "#/definitions/CustomPoleResidue",
- "CustomSellmeier": "#/definitions/CustomSellmeier",
- "CustomLorentz": "#/definitions/CustomLorentz",
- "CustomDebye": "#/definitions/CustomDebye",
- "CustomDrude": "#/definitions/CustomDrude",
- "CustomAnisotropicMedium": "#/definitions/CustomAnisotropicMedium",
- "PerturbationMedium": "#/definitions/PerturbationMedium",
- "PerturbationPoleResidue": "#/definitions/PerturbationPoleResidue",
- "LossyMetalMedium": "#/definitions/LossyMetalMedium"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Medium"
- },
- {
- "$ref": "#/definitions/AnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PECMedium"
- },
- {
- "$ref": "#/definitions/PMCMedium"
- },
- {
- "$ref": "#/definitions/PoleResidue"
- },
- {
- "$ref": "#/definitions/Sellmeier"
- },
- {
- "$ref": "#/definitions/Lorentz"
- },
- {
- "$ref": "#/definitions/Debye"
- },
- {
- "$ref": "#/definitions/Drude"
- },
- {
- "$ref": "#/definitions/FullyAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/CustomMedium"
- },
- {
- "$ref": "#/definitions/CustomPoleResidue"
- },
- {
- "$ref": "#/definitions/CustomSellmeier"
- },
- {
- "$ref": "#/definitions/CustomLorentz"
- },
- {
- "$ref": "#/definitions/CustomDebye"
- },
- {
- "$ref": "#/definitions/CustomDrude"
- },
- {
- "$ref": "#/definitions/CustomAnisotropicMedium"
- },
- {
- "$ref": "#/definitions/PerturbationMedium"
- },
- {
- "$ref": "#/definitions/PerturbationPoleResidue"
- },
- {
- "$ref": "#/definitions/LossyMetalMedium"
- }
- ]
- },
- "structures": {
- "title": "Structures",
- "description": "Tuple of structures present in simulation. Note: Structures defined later in this list override the simulation material properties in regions of spatial overlap.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/Structure"
- }
- },
- "symmetry": {
- "title": "Symmetries",
- "description": "Tuple of integers defining reflection symmetry across a plane bisecting the simulation domain normal to the x-, y-, and z-axis at the simulation center of each axis, respectively. ",
- "default": [
- 0,
- 0,
- 0
- ],
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- },
- {
- "enum": [
- 0,
- -1,
- 1
- ],
- "type": "integer"
- }
- ]
- },
- "sources": {
- "title": "Sources",
- "description": "Sources in the simulation. Note: sources are not supported in mode simulations.",
- "default": [],
- "type": "array",
- "minItems": 0,
- "maxItems": 0
- },
- "boundary_spec": {
- "title": "Boundaries",
- "description": "Specification of boundary conditions along each dimension. If ``None``, PML boundary conditions are applied on all sides. This behavior is for consistency with FDTD simulations; however, please note that the mode solver terminates the mode plane with PEC boundary. The 'ModeSpec' can be used to apply PML layers in the mode solver.",
- "default": {
- "attrs": {},
- "x": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "type": "Boundary"
- },
- "y": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "type": "Boundary"
- },
- "z": {
- "attrs": {},
- "plus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "minus": {
- "attrs": {},
- "name": null,
- "type": "PML",
- "num_layers": 12,
- "parameters": {
- "attrs": {},
- "sigma_order": 3,
- "sigma_min": 0.0,
- "sigma_max": 1.5,
- "type": "PMLParams",
- "kappa_order": 3,
- "kappa_min": 1.0,
- "kappa_max": 3.0,
- "alpha_order": 1,
- "alpha_min": 0.0,
- "alpha_max": 0.0
- }
- },
- "type": "Boundary"
- },
- "type": "BoundarySpec"
- },
- "allOf": [
- {
- "$ref": "#/definitions/BoundarySpec"
- }
- ]
- },
- "monitors": {
- "title": "Monitors",
- "description": "Tuple of monitors in the simulation. Note: monitor names are used to access data after simulation is run.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/PermittivityMonitor"
- }
- },
- "grid_spec": {
- "title": "Grid Specification",
- "description": "Specifications for the simulation grid along each of the three directions.",
- "default": {
- "attrs": {},
- "grid_x": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "grid_y": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "grid_z": {
- "attrs": {},
- "type": "AutoGrid",
- "max_scale": 1.4,
- "mesher": {
- "attrs": {},
- "type": "GradedMesher"
- },
- "dl_min": null,
- "min_steps_per_wvl": 10.0,
- "min_steps_per_sim_size": 10.0
- },
- "wavelength": null,
- "override_structures": [],
- "snapping_points": [],
- "layer_refinement_specs": [],
- "type": "GridSpec"
- },
- "allOf": [
- {
- "$ref": "#/definitions/GridSpec"
- }
- ]
- },
- "version": {
- "title": "Version",
- "description": "String specifying the front end version number.",
- "default": "2.9.0",
- "type": "string"
- },
- "plot_length_units": {
- "title": "Plot Units",
- "description": "When set to a supported ``LengthUnit``, plots will be produced with proper scaling of axes and include the desired unit specifier in labels.",
- "default": "\u03bcm",
- "enum": [
- "nm",
- "\u03bcm",
- "um",
- "mm",
- "cm",
- "m"
- ],
- "type": "string"
- },
- "structure_priority_mode": {
- "title": "Structure Priority Setting",
- "description": "This field only affects structures of `priority=None`. If `equal`, the priority of those structures is set to 0; if `conductor`, the priority of structures made of `LossyMetalMedium` is set to 90, `PECMedium` to 100, and others to 0.",
- "default": "equal",
- "enum": [
- "equal",
- "conductor"
- ],
- "type": "string"
- },
- "lumped_elements": {
- "title": "Lumped Elements",
- "description": "Tuple of lumped elements in the simulation. Note: only :class:`tidy3d.LumpedResistor` is supported currently.",
- "default": [],
- "type": "array",
- "items": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "LumpedResistor": "#/definitions/LumpedResistor",
- "CoaxialLumpedResistor": "#/definitions/CoaxialLumpedResistor",
- "LinearLumpedElement": "#/definitions/LinearLumpedElement"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/LumpedResistor"
- },
- {
- "$ref": "#/definitions/CoaxialLumpedResistor"
- },
- {
- "$ref": "#/definitions/LinearLumpedElement"
- }
- ]
- }
- },
- "subpixel": {
- "title": "Subpixel Averaging",
- "description": "Apply subpixel averaging methods of the permittivity on structure interfaces to result in much higher accuracy for a given grid size. Supply a :class:`SubpixelSpec` to this field to select subpixel averaging methods separately on dielectric, metal, and PEC material interfaces. Alternatively, user may supply a boolean value: ``True`` to apply the default subpixel averaging methods corresponding to ``SubpixelSpec()`` , or ``False`` to apply staircasing.",
- "default": {
- "attrs": {},
- "dielectric": {
- "attrs": {},
- "type": "PolarizedAveraging"
- },
- "metal": {
- "attrs": {},
- "type": "Staircasing"
- },
- "pec": {
- "attrs": {},
- "type": "PECConformal",
- "timestep_reduction": 0.3,
- "edge_singularity_correction": false
- },
- "pmc": {
- "attrs": {},
- "type": "Staircasing"
- },
- "lossy_metal": {
- "attrs": {},
- "type": "SurfaceImpedance",
- "timestep_reduction": 0.0,
- "edge_singularity_correction": false
- },
- "type": "SubpixelSpec"
- },
- "anyOf": [
- {
- "type": "boolean"
- },
- {
- "$ref": "#/definitions/SubpixelSpec"
- }
- ]
- },
- "simulation_type": {
- "title": "Simulation Type",
- "description": "Tag used internally to distinguish types of simulations for ``autograd`` gradient processing.",
- "default": "tidy3d",
- "enum": [
- "autograd_fwd",
- "autograd_bwd",
- "tidy3d"
- ],
- "type": "string"
- },
- "post_norm": {
- "title": "Post Normalization Values",
- "description": "Factor to multiply the fields by after running, given the adjoint source pipeline used. Note: this is used internally only.",
- "default": 1.0,
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "DataArray",
- "type": "xr.DataArray",
- "properties": {
- "_dims": {
- "title": "_dims",
- "type": "Tuple[str, ...]"
- }
- },
- "required": [
- "_dims"
- ]
- }
- ]
- },
- "mode_spec": {
- "title": "Mode specification",
- "description": "Container with specifications about the modes to be solved for.",
- "allOf": [
- {
- "$ref": "#/definitions/ModeSpec"
- }
- ]
- },
- "freqs": {
- "title": "Frequencies",
- "description": "A list of frequencies at which to solve.",
- "anyOf": [
- {
- "type": "array",
- "items": {
- "type": "number"
- }
- },
- {
- "type": "ArrayLike"
- }
- ]
- },
- "direction": {
- "title": "Propagation direction",
- "description": "Direction of waveguide mode propagation along the axis defined by its normal dimension.",
- "default": "+",
- "enum": [
- "+",
- "-"
- ],
- "type": "string"
- },
- "colocate": {
- "title": "Colocate fields",
- "description": "Toggle whether fields should be colocated to grid cell boundaries (i.e. primal grid nodes). Default is ``True``.",
- "default": true,
- "type": "boolean"
- },
- "fields": {
- "title": "Field Components",
- "description": "Collection of field components to store in the monitor. Note that some methods like ``flux``, ``dot`` require all tangential field components, while others like ``mode_area`` require all E-field components.",
- "default": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "array",
- "items": {
- "enum": [
- "Ex",
- "Ey",
- "Ez",
- "Hx",
- "Hy",
- "Hz"
- ],
- "type": "string"
- }
- },
- "plane": {
- "title": "Plane",
- "description": "Cross-sectional plane in which the mode will be computed. If provided, the computational domain will be the intersection between the provided ``plane`` and the simulation geometry. If ``None``, the simulation must be 2D, and the plane will be the entire simulation geometry.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Box": "#/definitions/Box",
- "ModeSource": "#/definitions/ModeSource",
- "ModeMonitor": "#/definitions/ModeMonitor",
- "ModeSolverMonitor": "#/definitions/ModeSolverMonitor"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Box"
- },
- {
- "$ref": "#/definitions/ModeSource"
- },
- {
- "$ref": "#/definitions/ModeMonitor"
- },
- {
- "$ref": "#/definitions/ModeSolverMonitor"
- }
- ]
- }
- },
- "required": [
- "size",
- "mode_spec",
- "freqs"
- ],
- "additionalProperties": false
- },
- "VolumeMeshMonitor": {
- "title": "VolumeMeshMonitor",
- "description": "Monitor recording the volume mesh. The monitor size must be either 2D or 3D. If a 2D monitor\nis used in a 3D simulation, the sliced volumetric mesh on the plane of the monitor will be\nstored as a ``TriangularGridDataset``.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\ncenter : Union[tuple[Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box], Union[float, autograd.tracer.Box]], Box] = (0.0, 0.0, 0.0)\n [units = um]. Center of object in x, y, and z.\nsize : Union[tuple[Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box], Union[pydantic.v1.types.NonNegativeFloat, autograd.tracer.Box]], Box]\n [units = um]. Size in x, y, and z directions.\nname : ConstrainedStrValue\n Unique name for monitor.\nunstructured : Literal[True] = True\n Return the original unstructured grid.\nconformal : bool = False\n If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "VolumeMeshMonitor",
- "enum": [
- "VolumeMeshMonitor"
- ],
- "type": "string"
- },
- "center": {
- "title": "Center",
- "description": "Center of object in x, y, and z.",
- "default": [
- 0.0,
- 0.0,
- 0.0
- ],
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number"
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "size": {
- "title": "Size",
- "description": "Size in x, y, and z directions.",
- "units": "um",
- "anyOf": [
- {
- "type": "array",
- "minItems": 3,
- "maxItems": 3,
- "items": [
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- {
- "anyOf": [
- {
- "type": "number",
- "minimum": 0
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- }
- ]
- },
- {
- "title": "AutogradBox",
- "type": "autograd.tracer.Box"
- }
- ]
- },
- "name": {
- "title": "Name",
- "description": "Unique name for monitor.",
- "minLength": 1,
- "type": "string"
- },
- "unstructured": {
- "title": "Unstructured Grid",
- "description": "Return the original unstructured grid.",
- "default": true,
- "enum": [
- true
- ],
- "type": "boolean"
- },
- "conformal": {
- "title": "Conformal Monitor Meshing",
- "description": "If ``True`` the simulation mesh will conform to the monitor's geometry. While this can be set for both Cartesian and unstructured monitors, it bears higher significance for the latter ones. Effectively, setting ``conformal = True`` for unstructured monitors (``unstructured = True``) ensures that returned values will not be obtained by interpolation during postprocessing but rather directly transferred from the computational grid.",
- "default": false,
- "type": "boolean"
- }
- },
- "required": [
- "size",
- "name"
- ],
- "additionalProperties": false
- },
- "VolumeMesher": {
- "title": "VolumeMesher",
- "description": "Specification for a standalone volume mesher.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : HeatChargeSimulation\n HeatCharge simulation instance for the mesh specification.\nmonitors : Tuple[VolumeMeshMonitor, ...] = ()\n List of monitors to be used for the mesher.",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "simulation": {
- "title": "Simulation",
- "description": "HeatCharge simulation instance for the mesh specification.",
- "allOf": [
- {
- "$ref": "#/definitions/HeatChargeSimulation"
- }
- ]
- },
- "monitors": {
- "title": "Monitors",
- "description": "List of monitors to be used for the mesher.",
- "default": [],
- "type": "array",
- "items": {
- "$ref": "#/definitions/VolumeMeshMonitor"
- }
- },
- "type": {
- "title": "Type",
- "default": "VolumeMesher",
- "enum": [
- "VolumeMesher"
- ],
- "type": "string"
- }
- },
- "required": [
- "simulation"
- ],
- "additionalProperties": false
- },
- "PayType": {
- "title": "PayType",
- "description": "An enumeration.",
- "enum": [
- "FLEX_CREDIT",
- "AUTO"
- ],
- "type": "string"
- },
- "Job": {
- "title": "Job",
- "description": "Interface for managing the running of a :class:`.Simulation` on server.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulation : Union[Simulation, HeatChargeSimulation, HeatSimulation, EMESimulation, ModeSolver, ModeSimulation, VolumeMesher]\n Simulation to run as a 'task'.\ntask_name : str\n Unique name of the task.\nfolder_name : str = default\n Name of folder to store task on web UI.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\nsolver_version : Optional[str] = None\n Custom solver version to use, otherwise uses default for the current front end version.\nverbose : bool = True\n Whether to print info messages and progressbars.\nsimulation_type : str = tidy3d\n Type of simulation, used internally only.\nparent_tasks : Optional[Tuple[str, ...]] = None\n Tuple of parent task ids, used internally only.\ntask_id_cached : Optional[str] = None\n Optional field to specify ``task_id``. Only used as a workaround internally so that ``task_id`` is written when ``Job.to_file()`` and then the proper task is loaded from ``Job.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\nreduce_simulation : Literal['auto', True, False] = auto\n Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.\npay_type : PayType = PayType.AUTO\n Specify the payment method.\n\nNotes\n-----\n\n This class provides a more convenient way to manage single simulations, mainly because it eliminates the need\n for keeping track of the ``task_id`` and original :class:`.Simulation`.\n\n We can get the cost estimate of running the task before actually running it. This prevents us from\n accidentally running large jobs that we set up by mistake. The estimated cost is the maximum cost\n corresponding to running all the time steps.\n\n Another convenient thing about :class:`Job` objects is that they can be saved and loaded just like other\n ``tidy3d`` components.\n\nExamples\n--------\n\n Once you've created a ``job`` object using :class:`tidy3d.web.api.container.Job`, you can upload it to our servers with:\n\n .. code-block:: python\n\n tidy3d.web.upload(simulation, task_name=\"task_name\", verbose=verbose)`\n\n It will not run until you explicitly tell it to do so with:\n\n .. code-block:: python\n\n tidy3d.web.api.webapi.start(job.task_id)\n\n To monitor the simulation's progress and wait for its completion, use\n\n .. code-block:: python\n\n tidy3d.web.api.webapi.monitor(job.task_id, verbose=verbose)\n\n After running the simulation, you can load the results using for example:\n\n .. code-block:: python\n\n sim_data = tidy3d.web.api.webapi.load(job.task_id, path=\"out/simulation.hdf5\", verbose=verbose)\n\n The job container has a convenient method to save and load the results of a job that has already finished,\n without needing to know the task_id, as below:\n\n .. code-block:: python\n\n # Saves the job metadata to a single file.\n job.to_file(\"data/job.json\")\n\n # You can exit the session, break here, or continue in new session.\n\n # Load the job metadata from file.\n job_loaded = tidy3d.web.api.container.Job.from_file(\"data/job.json\")\n\n # Download the data from the server and load it into a SimulationData object.\n sim_data = job_loaded.load(path=\"data/sim.hdf5\")\n\n\nSee Also\n--------\n\n:meth:`tidy3d.web.api.webapi.run_async`\n Submits a set of :class:`.Simulation` objects to server, starts running, monitors progress,\n downloads, and loads results as a :class:`.BatchData` object.\n\n:class:`Batch`\n Interface for submitting several :class:`Simulation` objects to sever.\n\n**Notebooks**\n * `Running simulations through the cloud <../../notebooks/WebAPI.html>`_\n * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_\n * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "Job",
- "enum": [
- "Job"
- ],
- "type": "string"
- },
- "simulation": {
- "title": "simulation",
- "description": "Simulation to run as a 'task'.",
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Simulation": "#/definitions/Simulation",
- "HeatChargeSimulation": "#/definitions/HeatChargeSimulation",
- "HeatSimulation": "#/definitions/HeatSimulation",
- "EMESimulation": "#/definitions/EMESimulation",
- "ModeSolver": "#/definitions/ModeSolver",
- "ModeSimulation": "#/definitions/ModeSimulation",
- "VolumeMesher": "#/definitions/VolumeMesher"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Simulation"
- },
- {
- "$ref": "#/definitions/HeatChargeSimulation"
- },
- {
- "$ref": "#/definitions/HeatSimulation"
- },
- {
- "$ref": "#/definitions/EMESimulation"
- },
- {
- "$ref": "#/definitions/ModeSolver"
- },
- {
- "$ref": "#/definitions/ModeSimulation"
- },
- {
- "$ref": "#/definitions/VolumeMesher"
- }
- ]
- },
- "task_name": {
- "title": "Task Name",
- "description": "Unique name of the task.",
- "type": "string"
- },
- "folder_name": {
- "title": "Folder Name",
- "description": "Name of folder to store task on web UI.",
- "default": "default",
- "type": "string"
- },
- "callback_url": {
- "title": "Callback URL",
- "description": "Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
- "type": "string"
- },
- "solver_version": {
- "title": "Solver Version",
- "description": "Custom solver version to use, otherwise uses default for the current front end version.",
- "type": "string"
- },
- "verbose": {
- "title": "Verbose",
- "description": "Whether to print info messages and progressbars.",
- "default": true,
- "type": "boolean"
- },
- "simulation_type": {
- "title": "Simulation Type",
- "description": "Type of simulation, used internally only.",
- "default": "tidy3d",
- "type": "string"
- },
- "parent_tasks": {
- "title": "Parent Tasks",
- "description": "Tuple of parent task ids, used internally only.",
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "task_id_cached": {
- "title": "Task ID (Cached)",
- "description": "Optional field to specify ``task_id``. Only used as a workaround internally so that ``task_id`` is written when ``Job.to_file()`` and then the proper task is loaded from ``Job.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.",
- "type": "string"
- },
- "reduce_simulation": {
- "title": "Reduce Simulation",
- "description": "Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.",
- "default": "auto",
- "anyOf": [
- {
- "enum": [
- "auto"
- ],
- "type": "string"
- },
- {
- "enum": [
- true,
- false
- ],
- "type": "boolean"
- }
- ]
- },
- "pay_type": {
- "title": "Payment Type",
- "description": "Specify the payment method.",
- "default": "AUTO",
- "allOf": [
- {
- "$ref": "#/definitions/PayType"
- }
- ]
- }
- },
- "required": [
- "simulation",
- "task_name"
- ],
- "additionalProperties": false
- },
- "Batch": {
- "title": "Batch",
- "description": "Interface for submitting several :class:`Simulation` objects to sever.\n\nParameters\n----------\nattrs : dict = {}\n Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.\nsimulations : Mapping[str, Annotated[Union[tidy3d.components.simulation.Simulation, tidy3d.components.tcad.simulation.heat_charge.HeatChargeSimulation, tidy3d.components.tcad.simulation.heat.HeatSimulation, tidy3d.components.eme.simulation.EMESimulation, tidy3d.components.mode.mode_solver.ModeSolver, tidy3d.components.mode.simulation.ModeSimulation, tidy3d.components.tcad.mesher.VolumeMesher], FieldInfo(default=PydanticUndefined, discriminator='type', extra={})]]\n Mapping of task names to Simulations to run as a batch.\nfolder_name : str = default\n Name of folder to store member of each batch on web UI.\nverbose : bool = True\n Whether to print info messages and progressbars.\nsolver_version : Optional[str] = None\n Custom solver version to use, otherwise uses default for the current front end version.\ncallback_url : Optional[str] = None\n Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.\nsimulation_type : str = tidy3d\n Type of each simulation in the batch, used internally only.\nparent_tasks : Optional[Mapping[str, tuple[str, ...]]] = None\n Collection of parent task ids for each job in batch, used internally only.\nnum_workers : Optional[PositiveInt] = 10\n Number of workers for multi-threading upload and download of batch. Corresponds to ``max_workers`` argument passed to ``concurrent.futures.ThreadPoolExecutor``. When left ``None``, will pass the maximum number of threads available on the system.\nreduce_simulation : Literal['auto', True, False] = auto\n Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.\npay_type : PayType = PayType.AUTO\n Specify the payment method.\njobs_cached : Optional[Mapping[str, Job]] = None\n Optional field to specify ``jobs``. Only used as a workaround internally so that ``jobs`` is written when ``Batch.to_file()`` and then the proper task is loaded from ``Batch.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.\n\nNotes\n-----\n\n Commonly one needs to submit a batch of :class:`Simulation`. The built-in :class:`Batch` object is the best way to upload,\n start, monitor, and load a series of tasks. The batch object is like a :class:`Job`, but stores task metadata\n for a series of simulations.\n\nSee Also\n--------\n\n:meth:`tidy3d.web.api.webapi.run_async`\n Submits a set of :class:`.Simulation` objects to server, starts running, monitors progress,\n downloads, and loads results as a :class:`.BatchData` object.\n\n:class:`Job`:\n Interface for managing the running of a Simulation on server.\n\n**Notebooks**\n * `Running simulations through the cloud <../../notebooks/WebAPI.html>`_\n * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_\n * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_",
- "type": "object",
- "properties": {
- "attrs": {
- "title": "Attributes",
- "description": "Dictionary storing arbitrary metadata for a Tidy3D object. This dictionary can be freely used by the user for storing data without affecting the operation of Tidy3D as it is not used internally. Note that, unlike regular Tidy3D fields, ``attrs`` are mutable. For example, the following is allowed for setting an ``attr`` ``obj.attrs['foo'] = bar``. Also note that `Tidy3D`` will raise a ``TypeError`` if ``attrs`` contain objects that can not be serialized. One can check if ``attrs`` are serializable by calling ``obj.json()``.",
- "default": {},
- "type": "object"
- },
- "type": {
- "title": "Type",
- "default": "Batch",
- "enum": [
- "Batch"
- ],
- "type": "string"
- },
- "simulations": {
- "title": "Simulations",
- "description": "Mapping of task names to Simulations to run as a batch.",
- "type": "object",
- "additionalProperties": {
- "discriminator": {
- "propertyName": "type",
- "mapping": {
- "Simulation": "#/definitions/Simulation",
- "HeatChargeSimulation": "#/definitions/HeatChargeSimulation",
- "HeatSimulation": "#/definitions/HeatSimulation",
- "EMESimulation": "#/definitions/EMESimulation",
- "ModeSolver": "#/definitions/ModeSolver",
- "ModeSimulation": "#/definitions/ModeSimulation",
- "VolumeMesher": "#/definitions/VolumeMesher"
- }
- },
- "oneOf": [
- {
- "$ref": "#/definitions/Simulation"
- },
- {
- "$ref": "#/definitions/HeatChargeSimulation"
- },
- {
- "$ref": "#/definitions/HeatSimulation"
- },
- {
- "$ref": "#/definitions/EMESimulation"
- },
- {
- "$ref": "#/definitions/ModeSolver"
- },
- {
- "$ref": "#/definitions/ModeSimulation"
- },
- {
- "$ref": "#/definitions/VolumeMesher"
- }
- ]
- }
- },
- "folder_name": {
- "title": "Folder Name",
- "description": "Name of folder to store member of each batch on web UI.",
- "default": "default",
- "type": "string"
- },
- "verbose": {
- "title": "Verbose",
- "description": "Whether to print info messages and progressbars.",
- "default": true,
- "type": "boolean"
- },
- "solver_version": {
- "title": "Solver Version",
- "description": "Custom solver version to use, otherwise uses default for the current front end version.",
- "type": "string"
- },
- "callback_url": {
- "title": "Callback URL",
- "description": "Http PUT url to receive simulation finish event. The body content is a json file with fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
- "type": "string"
- },
- "simulation_type": {
- "title": "Simulation Type",
- "description": "Type of each simulation in the batch, used internally only.",
- "default": "tidy3d",
- "type": "string"
- },
- "parent_tasks": {
- "title": "Parent Tasks",
- "description": "Collection of parent task ids for each job in batch, used internally only.",
- "type": "object",
- "additionalProperties": {
- "type": "array",
- "items": {
- "type": "string"
- }
- }
- },
- "num_workers": {
- "title": "Number of Workers",
- "description": "Number of workers for multi-threading upload and download of batch. Corresponds to ``max_workers`` argument passed to ``concurrent.futures.ThreadPoolExecutor``. When left ``None``, will pass the maximum number of threads available on the system.",
- "default": 10,
- "exclusiveMinimum": 0,
- "type": "integer"
- },
- "reduce_simulation": {
- "title": "Reduce Simulation",
- "description": "Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.",
- "default": "auto",
- "anyOf": [
- {
- "enum": [
- "auto"
- ],
- "type": "string"
- },
- {
- "enum": [
- true,
- false
- ],
- "type": "boolean"
- }
- ]
- },
- "pay_type": {
- "title": "Payment Type",
- "description": "Specify the payment method.",
- "default": "AUTO",
- "allOf": [
- {
- "$ref": "#/definitions/PayType"
- }
- ]
- },
- "jobs_cached": {
- "title": "Jobs (Cached)",
- "description": "Optional field to specify ``jobs``. Only used as a workaround internally so that ``jobs`` is written when ``Batch.to_file()`` and then the proper task is loaded from ``Batch.from_file()``. We recommend leaving unset as setting this field along with fields that were not used to create the task will cause errors.",
- "type": "object",
- "additionalProperties": {
- "$ref": "#/definitions/Job"
- }
- }
- },
- "required": [
- "simulations"
- ],
- "additionalProperties": false
}
}
}
\ No newline at end of file
diff --git a/test_task.py b/test_task.py
new file mode 100644
index 0000000000..cc306f1b21
--- /dev/null
+++ b/test_task.py
@@ -0,0 +1,6 @@
+from __future__ import annotations
+
+import tidy3d.web as web
+
+task = web.load("he-172a2b2e-f576-4a3e-a90b-06a64194ed51")
+print(task)
diff --git a/tests/test_plugins/smatrix/terminal_component_modeler_def.py b/tests/test_plugins/smatrix/terminal_component_modeler_def.py
index f4b7e665d5..248a583d0c 100644
--- a/tests/test_plugins/smatrix/terminal_component_modeler_def.py
+++ b/tests/test_plugins/smatrix/terminal_component_modeler_def.py
@@ -166,7 +166,7 @@ def make_component_modeler(
freqs = np.linspace(freq_start, freq_stop, 100)
modeler = TerminalComponentModeler(
- simulation=sim, ports=ports, freqs=freqs, remove_dc_component=False, verbose=True, **kwargs
+ simulation=sim, ports=ports, freqs=freqs, remove_dc_component=False, **kwargs
)
return modeler
@@ -328,7 +328,7 @@ def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePor
freqs = np.linspace(freq_start, freq_stop, 100)
modeler = TerminalComponentModeler(
- simulation=sim, ports=ports, freqs=freqs, remove_dc_component=False, verbose=True, **kwargs
+ simulation=sim, ports=ports, freqs=freqs, remove_dc_component=False, **kwargs
)
return modeler
diff --git a/tests/test_plugins/smatrix/test_component_modeler.py b/tests/test_plugins/smatrix/test_component_modeler.py
index 74064826d9..b8927a9479 100644
--- a/tests/test_plugins/smatrix/test_component_modeler.py
+++ b/tests/test_plugins/smatrix/test_component_modeler.py
@@ -8,10 +8,7 @@
import tidy3d as td
from tidy3d.exceptions import SetupError, Tidy3dKeyError
-from tidy3d.plugins.smatrix import (
- ComponentModeler,
- Port,
-)
+from tidy3d.plugins.smatrix import ComponentModeler, ComponentModelerData, Port, PortSimulationData
from tidy3d.web.api.container import Batch
from ...utils import run_emulated
@@ -193,12 +190,20 @@ def make_component_modeler(**kwargs):
return ComponentModeler(simulation=sim, ports=ports, freqs=sim.monitors[0].freqs, **kwargs)
-def run_component_modeler(monkeypatch, modeler: ComponentModeler):
+def run_component_modeler(monkeypatch, modeler: ComponentModeler) -> ComponentModelerData:
sim_dict = modeler.sim_dict
batch_data = {task_name: run_emulated(sim) for task_name, sim in sim_dict.items()}
- monkeypatch.setattr(ComponentModeler, "batch_data", property(lambda self: batch_data))
- s_matrix = modeler._construct_smatrix()
- return s_matrix
+ port_data = PortSimulationData(
+ ports=list(batch_data.keys()),
+ data=list(batch_data.values()),
+ )
+ modeler_data = ComponentModelerData(modeler=modeler, data=port_data)
+ return modeler_data
+
+
+def get_port_data_array(monkeypatch, modeler: ComponentModeler):
+ modeler_data = run_component_modeler(monkeypatch=monkeypatch, modeler=modeler)
+ return modeler_data.smatrix.data
def test_validate_no_sources():
@@ -244,7 +249,9 @@ def test_ports_too_close_boundary():
def test_validate_batch_supplied(tmp_path):
sim = make_coupler()
_ = ComponentModeler(
- simulation=sim, ports=[], freqs=sim.monitors[0].freqs, path_dir=str(tmp_path)
+ simulation=sim,
+ ports=[],
+ freqs=sim.monitors[0].freqs,
)
@@ -266,13 +273,13 @@ def test_make_component_modeler():
def test_run(monkeypatch):
modeler = make_component_modeler()
- monkeypatch.setattr(ComponentModeler, "run", lambda self, path_dir=None: None)
- modeler.run()
+ _ = run_component_modeler(monkeypatch, modeler=modeler)
def test_run_component_modeler(monkeypatch):
modeler = make_component_modeler()
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler_data = run_component_modeler(monkeypatch, modeler=modeler)
+ s_matrix = modeler_data.smatrix
for port_in in modeler.ports:
for mode_index_in in range(port_in.mode_spec.num_modes):
@@ -295,7 +302,8 @@ def test_component_modeler_run_only(monkeypatch):
ONLY_SOURCE = (port_run_only, mode_index_run_only) = ("right_bot", 0)
run_only = [ONLY_SOURCE]
modeler = make_component_modeler(run_only=run_only)
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler_data = run_component_modeler(monkeypatch, modeler=modeler)
+ s_matrix = modeler_data.smatrix
coords_in_run_only = {"port_in": port_run_only, "mode_index_in": mode_index_run_only}
@@ -340,7 +348,8 @@ def test_run_component_modeler_mappings(monkeypatch):
((("left_bot", 0), ("right_top", 0)), (("left_top", 0), ("right_bot", 0)), +1),
)
modeler = make_component_modeler(element_mappings=element_mappings)
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler_data = run_component_modeler(monkeypatch, modeler=modeler)
+ s_matrix = modeler_data.smatrix
_test_mappings(element_mappings, s_matrix)
@@ -366,55 +375,54 @@ def test_mapping_exclusion(monkeypatch):
element_mappings.append(mapping)
modeler = make_component_modeler(element_mappings=element_mappings)
+ modeler_data = run_component_modeler(monkeypatch, modeler=modeler)
+ s_matrix = modeler_data.smatrix
run_sim_indices = modeler.matrix_indices_run_sim
assert EXCLUDE_INDEX not in run_sim_indices, "mapping didnt exclude row properly"
- s_matrix = run_component_modeler(monkeypatch, modeler)
_test_mappings(element_mappings, s_matrix)
-def test_batch_filename(tmp_path):
- modeler = make_component_modeler()
- path = modeler._batch_path
- assert path
-
-
-def test_import_smatrix_smatrix():
- from tidy3d.plugins.smatrix.smatrix import ComponentModeler, Port # noqa: F401
-
-
-def test_to_from_file_empty_batch(tmp_path):
- modeler = make_component_modeler()
-
- fname = str(tmp_path) + "/modeler.json"
-
- modeler.to_file(fname)
- modeler2 = modeler.from_file(fname)
-
- assert modeler2.batch_cached is None
-
-
-def test_to_from_file_batch(tmp_path, monkeypatch):
- modeler = make_component_modeler()
- _ = run_component_modeler(monkeypatch, modeler)
-
- batch = td.web.Batch(simulations={})
-
- modeler._cached_properties["batch"] = batch
-
- fname = str(tmp_path) + "/modeler.json"
-
- modeler.to_file(fname)
- modeler2 = modeler.from_file(fname)
-
- assert modeler2.batch_cached == modeler2.batch == batch
-
-
-def test_non_default_path_dir(monkeypatch):
- modeler = make_component_modeler(path_dir="not_default")
- monkeypatch.setattr(ComponentModeler, "_construct_smatrix", lambda self: None)
- modeler.run()
- modeler.run(path_dir="not_default")
- with pytest.raises(ValueError):
- modeler.run(path_dir="a_new_path")
+# def test_batch_filename(tmp_path):
+# modeler = make_component_modeler()
+# path = modeler._batch_path
+# assert path
+# def test_import_smatrix_smatrix():
+# from tidy3d.plugins.smatrix.smatrix import ComponentModeler, Port
+
+# def test_to_from_file_empty_batch(tmp_path):
+# modeler = make_component_modeler()
+#
+# fname = str(tmp_path) + "/modeler.json"
+#
+# modeler.to_file(fname)
+# modeler2 = modeler.from_file(fname)
+#
+# assert modeler2.batch_cached is None
+#
+#
+# def test_to_from_file_batch(tmp_path, monkeypatch):
+# modeler = make_component_modeler()
+# _ = run_component_modeler(monkeypatch, modeler)
+#
+# batch = td.web.Batch(simulations={})
+#
+# modeler._cached_properties["batch"] = batch
+#
+# fname = str(tmp_path) + "/modeler.json"
+#
+# modeler.to_file(fname)
+# modeler2 = modeler.from_file(fname)
+#
+# # BREAK this test because it introduces mutability which shouldn't exist
+# assert modeler2.batch_cached == modeler2.batch == batch
+#
+#
+# def test_non_default_path_dir(monkeypatch):
+# modeler = make_component_modeler(path_dir="not_default")
+# monkeypatch.setattr(ComponentModeler, "_construct_smatrix", lambda self: None)
+# modeler.run()
+# modeler.run(path_dir="not_default")
+# with pytest.raises(ValueError):
+# modeler.run(path_dir="a_new_path")
diff --git a/tests/test_plugins/smatrix/test_terminal_component_modeler.py b/tests/test_plugins/smatrix/test_terminal_component_modeler.py
index c601d34e76..a99916feb6 100644
--- a/tests/test_plugins/smatrix/test_terminal_component_modeler.py
+++ b/tests/test_plugins/smatrix/test_terminal_component_modeler.py
@@ -7,6 +7,7 @@
import xarray as xr
import tidy3d as td
+import tidy3d.plugins.smatrix.utils
from tidy3d.components.data.data_array import FreqDataArray
from tidy3d.exceptions import SetupError, Tidy3dError, Tidy3dKeyError
from tidy3d.plugins.microwave import (
@@ -15,11 +16,12 @@
VoltageIntegralAxisAligned,
)
from tidy3d.plugins.smatrix import (
- AbstractComponentModeler,
CoaxialLumpedPort,
LumpedPort,
PortDataArray,
+ PortSimulationData,
TerminalComponentModeler,
+ TerminalComponentModelerData,
TerminalPortDataArray,
WavePort,
)
@@ -31,25 +33,38 @@
mm = 1e3
-def run_component_modeler(monkeypatch, modeler: TerminalComponentModeler):
+def run_component_modeler(
+ monkeypatch, modeler: TerminalComponentModeler
+) -> TerminalComponentModelerData:
sim_dict = modeler.sim_dict
batch_data = {task_name: run_emulated(sim) for task_name, sim in sim_dict.items()}
- monkeypatch.setattr(AbstractComponentModeler, "batch_data", property(lambda self: batch_data))
- monkeypatch.setattr(TerminalComponentModeler, "batch_data", property(lambda self: batch_data))
- monkeypatch.setattr(AbstractComponentModeler, "inv", lambda matrix: np.eye(len(modeler.ports)))
+ port_data = PortSimulationData(
+ ports=list(batch_data.keys()),
+ data=list(batch_data.values()),
+ )
+ modeler_data = TerminalComponentModelerData(modeler=modeler, data=port_data)
+ monkeypatch.setattr(
+ td.plugins.smatrix.utils, "port_array_inv", lambda matrix: np.eye(len(modeler.ports))
+ )
monkeypatch.setattr(
- TerminalComponentModeler,
- "_compute_F",
+ td.plugins.smatrix.utils,
+ "compute_F",
lambda matrix: 1.0 / (2.0 * np.sqrt(np.abs(matrix) + 1e-4)),
)
monkeypatch.setattr(
- TerminalComponentModeler,
- "_check_port_impedance_sign",
- lambda self, Z_numpy: (),
+ td.plugins.smatrix.utils,
+ "check_port_impedance_sign",
+ lambda Z_numpy: np.ndarray([]),
)
- s_matrix = modeler._construct_smatrix()
- return s_matrix
+ return modeler_data
+
+
+def get_terminal_port_data_array(
+ monkeypatch, modeler: TerminalComponentModeler
+) -> TerminalPortDataArray:
+ modeler_data = run_component_modeler(monkeypatch=monkeypatch, modeler=modeler)
+ return modeler_data.smatrix.data
def check_lumped_port_components_snapped_correctly(modeler: TerminalComponentModeler):
@@ -78,7 +93,7 @@ def check_lumped_port_components_snapped_correctly(modeler: TerminalComponentMod
def test_validate_no_sources(tmp_path):
- modeler = make_component_modeler(planar_pec=True, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=True)
source = td.PointDipole(
source_time=td.GaussianPulse(freq0=2e14, fwidth=1e14), polarization="Ex"
)
@@ -88,7 +103,7 @@ def test_validate_no_sources(tmp_path):
def test_validate_3D_sim(tmp_path):
- modeler = make_component_modeler(planar_pec=False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=False)
sim = td.Simulation(
size=(10e3, 10e3, 0),
sources=[],
@@ -106,43 +121,40 @@ def test_validate_3D_sim(tmp_path):
def test_no_port(tmp_path):
- modeler = make_component_modeler(planar_pec=True, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=True)
_ = modeler.ports
with pytest.raises(Tidy3dKeyError):
modeler.get_port_by_name(port_name="NOT_A_PORT")
def test_plot_sim(tmp_path):
- modeler = make_component_modeler(planar_pec=False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=False)
modeler.plot_sim(z=0)
plt.close()
def test_plot_sim_eps(tmp_path):
- modeler = make_component_modeler(planar_pec=False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=False)
modeler.plot_sim_eps(z=0)
plt.close()
@pytest.mark.parametrize("port_refinement", [False, True])
def test_make_component_modeler(tmp_path, port_refinement):
- modeler = make_component_modeler(
- planar_pec=False, path_dir=str(tmp_path), port_refinement=port_refinement
- )
+ modeler = make_component_modeler(planar_pec=False, port_refinement=port_refinement)
if port_refinement:
for sim in modeler.sim_dict.values():
_ = sim.volumetric_structures
def test_run(monkeypatch, tmp_path):
- modeler = make_component_modeler(planar_pec=True, path_dir=str(tmp_path))
- monkeypatch.setattr(TerminalComponentModeler, "run", lambda self, path_dir: None)
- modeler.run(path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=True)
+ modeler_data = run_component_modeler(monkeypatch, modeler)
def test_run_component_modeler(monkeypatch, tmp_path):
- modeler = make_component_modeler(planar_pec=True, path_dir=str(tmp_path))
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler = make_component_modeler(planar_pec=True)
+ s_matrix = get_terminal_port_data_array(monkeypatch, modeler)
for port_in in modeler.ports:
for port_out in modeler.ports:
@@ -189,7 +201,7 @@ def test_s_to_z_component_modeler():
}
s_matrix = TerminalPortDataArray(data=values, coords=coords)
- z_matrix = TerminalComponentModeler.s_to_z(s_matrix, reference=Z0)
+ z_matrix = TerminalComponentModelerData.s_to_z(s_matrix, reference=Z0)
z_matrix_at_f = z_matrix.sel(f=1e8)
assert np.isclose(z_matrix_at_f[0, 0], Z11)
assert np.isclose(z_matrix_at_f[0, 1], Z12)
@@ -203,7 +215,7 @@ def test_s_to_z_component_modeler():
"port": port_names,
}
z_port_matrix = PortDataArray(data=values, coords=coords)
- z_matrix = TerminalComponentModeler.s_to_z(s_matrix, reference=z_port_matrix)
+ z_matrix = TerminalComponentModelerData.s_to_z(s_matrix, reference=z_port_matrix)
z_matrix_at_f = z_matrix.sel(f=1e8)
assert np.isclose(z_matrix_at_f[0, 0], Z11)
assert np.isclose(z_matrix_at_f[0, 1], Z12)
@@ -225,7 +237,7 @@ def test_ab_to_s_component_modeler():
b_values = (1 + 1j) * np.random.random((1, 2, 2))
a_matrix = TerminalPortDataArray(data=a_values, coords=coords)
b_matrix = TerminalPortDataArray(data=b_values, coords=coords)
- S_matrix = TerminalComponentModeler.ab_to_s(a_matrix, b_matrix)
+ S_matrix = TerminalComponentModelerData.ab_to_s(a_matrix, b_matrix)
assert np.isclose(S_matrix, b_matrix).all()
@@ -236,16 +248,12 @@ def test_port_snapping(tmp_path):
y_z_grid = td.UniformGrid(dl=0.1 * 1e3)
x_grid = td.UniformGrid(dl=11 * 1e3)
grid_spec = td.GridSpec(grid_x=x_grid, grid_y=y_z_grid, grid_z=y_z_grid)
- modeler = make_component_modeler(
- planar_pec=True, path_dir=str(tmp_path), port_refinement=False, grid_spec=grid_spec
- )
+ modeler = make_component_modeler(planar_pec=True, port_refinement=False, grid_spec=grid_spec)
check_lumped_port_components_snapped_correctly(modeler=modeler)
def test_coarse_grid_at_port(monkeypatch, tmp_path):
- modeler = make_component_modeler(
- planar_pec=True, path_dir=str(tmp_path), port_refinement=False, port_snapping=False
- )
+ modeler = make_component_modeler(planar_pec=True, port_refinement=False, port_snapping=False)
# Without port refinement the grid is much too coarse for these port sizes
with pytest.raises(SetupError):
_ = run_component_modeler(monkeypatch, modeler)
@@ -268,17 +276,15 @@ def test_converting_port_to_simulation_objects(snap_center):
@pytest.mark.parametrize("port_refinement", [False, True])
def test_make_coaxial_component_modeler(tmp_path, port_refinement):
- modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_refinement=port_refinement
- )
+ modeler = make_coaxial_component_modeler(port_refinement=port_refinement)
if port_refinement:
for sim in modeler.sim_dict.values():
_ = sim.volumetric_structures
def test_run_coaxial_component_modeler(monkeypatch, tmp_path):
- modeler = make_coaxial_component_modeler(path_dir=str(tmp_path))
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler = make_coaxial_component_modeler()
+ s_matrix = get_terminal_port_data_array(monkeypatch, modeler)
for port_in in modeler.ports:
for port_out in modeler.ports:
@@ -309,9 +315,7 @@ def test_run_coaxial_component_modeler(monkeypatch, tmp_path):
)
def test_coarse_grid_at_coaxial_port(monkeypatch, tmp_path, grid_spec):
"""Ensure that the grid is fine enough at the coaxial ports along the transverse dimensions."""
- modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_refinement=False, grid_spec=grid_spec
- )
+ modeler = make_coaxial_component_modeler(port_refinement=False, grid_spec=grid_spec)
# Without port refinement the grid is much too coarse for these port sizes
with pytest.raises(SetupError):
_ = run_component_modeler(monkeypatch, modeler)
@@ -380,9 +384,7 @@ def test_coaxial_port_snapping(tmp_path):
x_y_grid = td.UniformGrid(dl=0.1 * 1e3)
z_grid = td.UniformGrid(dl=11 * 1e3)
grid_spec = td.GridSpec(grid_x=x_y_grid, grid_y=x_y_grid, grid_z=z_grid)
- modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_refinement=False, grid_spec=grid_spec
- )
+ modeler = make_coaxial_component_modeler(port_refinement=False, grid_spec=grid_spec)
check_lumped_port_components_snapped_correctly(modeler=modeler)
@@ -390,7 +392,7 @@ def test_power_delivered_helper(monkeypatch, tmp_path):
"""Test computations involving power waves are correct by manually setting voltage and current
at ports using monkeypatch.
"""
- modeler = make_coaxial_component_modeler(path_dir=str(tmp_path))
+ modeler = make_coaxial_component_modeler()
port1 = modeler.ports[0]
port_impedance = port1.impedance
freqs = np.linspace(1e9, 10e9, 11)
@@ -413,12 +415,12 @@ def compute_current_patch(self, sim_data):
monkeypatch.setattr(CoaxialLumpedPort, "compute_current", compute_current_patch)
# First test should give complete power transfer into the network
- power = TerminalComponentModeler.compute_power_delivered_by_port(sim_data=None, port=port1)
+ power = TerminalComponentModelerData.compute_power_delivered_by_port(sim_data=None, port=port1)
assert np.allclose(power.values, avg_power)
# Second test is complete reflecton
current = np.ones_like(freqs) * 0
- power = TerminalComponentModeler.compute_power_delivered_by_port(sim_data=None, port=port1)
+ power = TerminalComponentModelerData.compute_power_delivered_by_port(sim_data=None, port=port1)
assert np.allclose(power.values, 0)
# Third test is a custom test using equation 4.60 and 4.61 from
@@ -431,7 +433,7 @@ def compute_current_patch(self, sim_data):
current_amplitude = (power_a - power_b) / Rr
voltage = np.ones_like(freqs) * voltage_amplitude
current = np.ones_like(freqs) * current_amplitude
- power = TerminalComponentModeler.compute_power_delivered_by_port(sim_data=None, port=port1)
+ power = TerminalComponentModelerData.compute_power_delivered_by_port(sim_data=None, port=port1)
assert np.allclose(power.values, 0.5 * (power_a**2 - power_b**2))
@@ -441,7 +443,6 @@ def test_make_coaxial_component_modeler_with_wave_ports(tmp_path):
xy_grid = td.UniformGrid(dl=0.1 * 1e3)
grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid)
_ = make_coaxial_component_modeler(
- path_dir=str(tmp_path),
port_types=(WavePort, WavePort),
grid_spec=grid_spec,
)
@@ -459,7 +460,6 @@ def test_run_coaxial_component_modeler_with_wave_ports(
if not (voltage_enabled or current_enabled):
with pytest.raises(pd.ValidationError):
modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path),
port_types=(WavePort, WavePort),
grid_spec=grid_spec,
use_voltage=voltage_enabled,
@@ -468,13 +468,12 @@ def test_run_coaxial_component_modeler_with_wave_ports(
return
modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path),
port_types=(WavePort, WavePort),
grid_spec=grid_spec,
use_voltage=voltage_enabled,
use_current=current_enabled,
)
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ s_matrix = get_terminal_port_data_array(monkeypatch, modeler)
shape_one_port = (len(modeler.freqs), len(modeler.ports))
shape_both_ports = (len(modeler.freqs),)
@@ -497,9 +496,9 @@ def test_run_mixed_component_modeler_with_wave_ports(monkeypatch, tmp_path):
xy_grid = td.UniformGrid(dl=0.1 * 1e3)
grid_spec = td.GridSpec(grid_x=xy_grid, grid_y=xy_grid, grid_z=z_grid)
modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_types=(CoaxialLumpedPort, WavePort), grid_spec=grid_spec
+ port_types=(CoaxialLumpedPort, WavePort), grid_spec=grid_spec
)
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ s_matrix = get_terminal_port_data_array(monkeypatch, modeler)
shape_one_port = (len(modeler.freqs), len(modeler.ports))
shape_both_ports = (len(modeler.freqs),)
@@ -656,7 +655,6 @@ def test_wave_port_grid_validation(tmp_path):
modeler = make_coaxial_component_modeler(
grid_spec=td.GridSpec.auto(wavelength=10e3),
port_refinement=True,
- path_dir=str(tmp_path),
port_types=(WavePort, WavePort),
)
_ = modeler.sim_dict
@@ -664,7 +662,6 @@ def test_wave_port_grid_validation(tmp_path):
modeler = make_coaxial_component_modeler(
grid_spec=td.GridSpec.auto(wavelength=10e3),
port_refinement=False,
- path_dir=str(tmp_path),
port_types=(WavePort, WavePort),
)
with pytest.raises(SetupError):
@@ -673,9 +670,7 @@ def test_wave_port_grid_validation(tmp_path):
def test_wave_port_to_mode_solver(tmp_path):
"""Checks that wave port can be converted to a mode solver."""
- modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_types=(WavePort, WavePort)
- )
+ modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort))
_ = modeler.ports[0].to_mode_solver(modeler.simulation, freqs=[1e9, 2e9, 3e9])
@@ -683,7 +678,7 @@ def test_port_source_snapped_to_PML(tmp_path):
"""Raise meaningful error message when source is snapped into PML because the port is too close
to the boundary.
"""
- modeler = make_component_modeler(planar_pec=True, path_dir=str(tmp_path))
+ modeler = make_component_modeler(planar_pec=True)
port_pos = 5e4
voltage_path = VoltageIntegralAxisAligned(
center=(port_pos, 0, 0),
@@ -723,9 +718,7 @@ def test_port_source_snapped_to_PML(tmp_path):
def test_wave_port_validate_current_integral(tmp_path):
"""Checks that the current integral direction validator runs correctly."""
- modeler = make_coaxial_component_modeler(
- path_dir=str(tmp_path), port_types=(WavePort, WavePort)
- )
+ modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort))
with pytest.raises(pd.ValidationError):
_ = modeler.updated_copy(direction="-", path="ports/0/")
@@ -735,17 +728,17 @@ def test_port_impedance_check():
Z_numpy = np.ones((50, 3))
Z_numpy[:, 1] = -1.0
# All ok if same sign for every frequency
- TerminalComponentModeler._check_port_impedance_sign(Z_numpy)
+ TerminalComponentModelerData.check_port_impedance_sign(Z_numpy)
Z_numpy[25, 1] = 1.0
# Change of sign is unexpected
with pytest.raises(Tidy3dError):
- TerminalComponentModeler._check_port_impedance_sign(Z_numpy)
+ TerminalComponentModelerData.check_port_impedance_sign(Z_numpy)
def test_antenna_helpers(monkeypatch, tmp_path):
"""Test monitor data normalization and combination helpers for antenna parameters."""
# Setup basic modeler with radiation monitor
- modeler = make_component_modeler(False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(False)
sim = modeler.simulation
theta = np.linspace(0, np.pi, 40)
phi = np.linspace(0, 2 * np.pi, 80)
@@ -762,9 +755,8 @@ def test_antenna_helpers(monkeypatch, tmp_path):
modeler = modeler.updated_copy(radiation_monitors=[radiation_monitor])
# Run simulation to get data
- _ = run_component_modeler(monkeypatch, modeler)
- batch_data = modeler.batch_data
- sim_data = batch_data[modeler._task_name(modeler.ports[0])]
+ modeler_data = run_component_modeler(monkeypatch, modeler)
+ sim_data = modeler_data.data[modeler_data.modeler.get_task_name(modeler.ports[0])]
rad_mon_data = sim_data[radiation_monitor.name]
# Test monitor helper
@@ -775,10 +767,10 @@ def test_antenna_helpers(monkeypatch, tmp_path):
# Test monitor data normalization with different amplitude types
a_array = FreqDataArray(np.ones(len(modeler.freqs)), {"f": modeler.freqs})
- normalized_data_array = modeler._monitor_data_at_port_amplitude(
+ normalized_data_array = modeler_data._monitor_data_at_port_amplitude(
modeler.ports[0], sim_data, rad_mon_data, a_array
)
- normalized_data_const = modeler._monitor_data_at_port_amplitude(
+ normalized_data_const = modeler_data._monitor_data_at_port_amplitude(
modeler.ports[0], sim_data, rad_mon_data, 1.0
)
assert isinstance(normalized_data_array, td.DirectivityData)
@@ -789,8 +781,8 @@ def test_antenna_helpers(monkeypatch, tmp_path):
assert isinstance(combined_data, td.DirectivityData)
# Test power wave amplitude computation
- a, b = modeler.compute_power_wave_amplitudes_at_each_port(
- modeler.port_reference_impedances, sim_data
+ a, b = modeler_data.compute_power_wave_amplitudes_at_each_port(
+ modeler_data.port_reference_impedances, sim_data
)
assert isinstance(a, PortDataArray)
assert isinstance(b, PortDataArray)
@@ -799,7 +791,7 @@ def test_antenna_helpers(monkeypatch, tmp_path):
def test_antenna_parameters(monkeypatch, tmp_path):
"""Test basic antenna parameters computation and validation."""
# Setup modeler with radiation monitor
- modeler = make_component_modeler(False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(False)
sim = modeler.simulation
theta = np.linspace(0, np.pi, 101)
phi = np.linspace(0, 2 * np.pi, 201)
@@ -824,8 +816,8 @@ def test_antenna_parameters(monkeypatch, tmp_path):
modeler = modeler.updated_copy(radiation_monitors=[radiation_monitor])
# Run simulation and get antenna parameters
- _ = run_component_modeler(monkeypatch, modeler)
- antenna_params = modeler.get_antenna_metrics_data()
+ modeler_data = run_component_modeler(monkeypatch, modeler)
+ antenna_params = modeler_data.get_antenna_metrics_data()
# Test that all essential parameters exist and are correct type
assert isinstance(antenna_params.radiation_efficiency, FreqDataArray)
@@ -858,7 +850,7 @@ def test_antenna_parameters(monkeypatch, tmp_path):
def test_get_combined_antenna_parameters_data(monkeypatch, tmp_path):
"""Test the computation of combined antenna parameters from multiple ports."""
- modeler = make_component_modeler(False, path_dir=str(tmp_path))
+ modeler = make_component_modeler(False)
sim = modeler.simulation
theta = np.linspace(0, np.pi, 101)
phi = np.linspace(0, 2 * np.pi, 201)
@@ -874,13 +866,13 @@ def test_get_combined_antenna_parameters_data(monkeypatch, tmp_path):
phi=phi,
)
modeler = modeler.updated_copy(radiation_monitors=[radiation_monitor])
- s_matrix = run_component_modeler(monkeypatch, modeler)
+ modeler_data = run_component_modeler(monkeypatch=monkeypatch, modeler=modeler)
# Define port amplitudes
port_amplitudes = {modeler.ports[0].name: 1.0, modeler.ports[1].name: 1j}
# Get combined antenna parameters
- antenna_params = modeler.get_antenna_metrics_data(
+ antenna_params = modeler_data.get_antenna_metrics_data(
port_amplitudes, monitor_name="antenna_monitor"
)
@@ -893,7 +885,7 @@ def test_get_combined_antenna_parameters_data(monkeypatch, tmp_path):
assert isinstance(antenna_params.realized_gain, xr.DataArray)
# Test with single port for comparison
- single_port_params = modeler.get_antenna_metrics_data()
+ single_port_params = modeler_data.get_antenna_metrics_data()
# Values should be different when combining ports vs single port
assert not np.allclose(antenna_params.gain, single_port_params.gain)
diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py
index af66543b9a..3f7acafbeb 100644
--- a/tidy3d/components/data/data_array.py
+++ b/tidy3d/components/data/data_array.py
@@ -23,11 +23,14 @@
from tidy3d.components.geometry.bound_ops import bounds_contains
from tidy3d.components.types import Axis, Bound
from tidy3d.constants import (
+ AMP,
HERTZ,
MICROMETER,
+ OHM,
PICOSECOND_PER_NANOMETER_PER_KILOMETER,
RADIAN,
SECOND,
+ VOLT,
WATT,
)
from tidy3d.exceptions import DataError, FileError
@@ -1309,6 +1312,165 @@ class PerturbationCoefficientDataArray(DataArray):
_dims = ("wvl", "coeff")
+class VoltageArray(DataArray):
+ # Always set __slots__ = () to avoid xarray warnings
+ __slots__ = ()
+ _data_attrs = {"units": VOLT, "long_name": "voltage"}
+
+
+class CurrentArray(DataArray):
+ # Always set __slots__ = () to avoid xarray warnings
+ __slots__ = ()
+ _data_attrs = {"units": AMP, "long_name": "current"}
+
+
+class ImpedanceArray(DataArray):
+ # Always set __slots__ = () to avoid xarray warnings
+ __slots__ = ()
+ _data_attrs = {"units": OHM, "long_name": "impedance"}
+
+
+# Voltage arrays
+class VoltageFreqDataArray(VoltageArray, FreqDataArray):
+ """Voltage data array in frequency domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9, 4e9]
+ >>> coords = dict(f=f)
+ >>> data = np.random.random(3) + 1j * np.random.random(3)
+ >>> vfd = VoltageFreqDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class VoltageTimeDataArray(VoltageArray, TimeDataArray):
+ """Voltage data array in time domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> t = [0, 1e-9, 2e-9, 3e-9]
+ >>> coords = dict(t=t)
+ >>> data = np.sin(2 * np.pi * 1e9 * np.array(t))
+ >>> vtd = VoltageTimeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class VoltageFreqModeDataArray(VoltageArray, FreqModeDataArray):
+ """Voltage data array in frequency-mode domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9]
+ >>> mode_index = [0, 1]
+ >>> coords = dict(f=f, mode_index=mode_index)
+ >>> data = np.random.random((2, 2)) + 1j * np.random.random((2, 2))
+ >>> vfmd = VoltageFreqModeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+# Current arrays
+class CurrentFreqDataArray(CurrentArray, FreqDataArray):
+ """Current data array in frequency domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9, 4e9]
+ >>> coords = dict(f=f)
+ >>> data = np.random.random(3) + 1j * np.random.random(3)
+ >>> cfd = CurrentFreqDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class CurrentTimeDataArray(CurrentArray, TimeDataArray):
+ """Current data array in time domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> t = [0, 1e-9, 2e-9, 3e-9]
+ >>> coords = dict(t=t)
+ >>> data = np.cos(2 * np.pi * 1e9 * np.array(t))
+ >>> ctd = CurrentTimeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class CurrentFreqModeDataArray(CurrentArray, FreqModeDataArray):
+ """Current data array in frequency-mode domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9]
+ >>> mode_index = [0, 1]
+ >>> coords = dict(f=f, mode_index=mode_index)
+ >>> data = np.random.random((2, 2)) + 1j * np.random.random((2, 2))
+ >>> cfmd = CurrentFreqModeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+# Impedance arrays
+class ImpedanceFreqDataArray(ImpedanceArray, FreqDataArray):
+ """Impedance data array in frequency domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9, 4e9]
+ >>> coords = dict(f=f)
+ >>> data = 50.0 + 1j * np.random.random(3)
+ >>> zfd = ImpedanceFreqDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class ImpedanceTimeDataArray(ImpedanceArray, TimeDataArray):
+ """Impedance data array in time domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> t = [0, 1e-9, 2e-9, 3e-9]
+ >>> coords = dict(t=t)
+ >>> data = 50.0 * np.ones_like(t)
+ >>> ztd = ImpedanceTimeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
+class ImpedanceFreqModeDataArray(ImpedanceArray, FreqModeDataArray):
+ """Impedance data array in frequency-mode domain.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9]
+ >>> mode_index = [0, 1]
+ >>> coords = dict(f=f, mode_index=mode_index)
+ >>> data = 50.0 + 10.0 * np.random.random((2, 2))
+ >>> zfmd = ImpedanceFreqModeDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+
+
DATA_ARRAY_TYPES = [
SpatialDataArray,
ScalarFieldDataArray,
@@ -1346,6 +1508,15 @@ class PerturbationCoefficientDataArray(DataArray):
SpatialVoltageDataArray,
PerturbationCoefficientDataArray,
IndexedTimeDataArray,
+ VoltageFreqDataArray,
+ VoltageTimeDataArray,
+ VoltageFreqModeDataArray,
+ CurrentFreqDataArray,
+ CurrentTimeDataArray,
+ CurrentFreqModeDataArray,
+ ImpedanceFreqDataArray,
+ ImpedanceTimeDataArray,
+ ImpedanceFreqModeDataArray,
]
DATA_ARRAY_MAP = {data_array.__name__: data_array for data_array in DATA_ARRAY_TYPES}
@@ -1356,3 +1527,14 @@ class PerturbationCoefficientDataArray(DataArray):
IndexedFieldVoltageDataArray,
PointDataArray,
]
+
+IntegralResultTypes = Union[FreqDataArray, FreqModeDataArray, TimeDataArray]
+VoltageIntegralResultTypes = Union[
+ VoltageFreqDataArray, VoltageFreqModeDataArray, VoltageTimeDataArray
+]
+CurrentIntegralResultTypes = Union[
+ CurrentFreqDataArray, CurrentFreqModeDataArray, CurrentTimeDataArray
+]
+ImpedanceResultTypes = Union[
+ ImpedanceFreqDataArray, ImpedanceFreqModeDataArray, ImpedanceTimeDataArray
+]
diff --git a/tidy3d/plugins/microwave/custom_path_integrals.py b/tidy3d/plugins/microwave/custom_path_integrals.py
index d1e2c89a19..311ec819d8 100644
--- a/tidy3d/plugins/microwave/custom_path_integrals.py
+++ b/tidy3d/plugins/microwave/custom_path_integrals.py
@@ -19,10 +19,10 @@
from .path_integrals import (
AbstractAxesRH,
AxisAlignedPathIntegral,
- CurrentIntegralAxisAligned,
+ CurrentIntegralResultTypes,
IntegralResultTypes,
MonitorDataTypes,
- VoltageIntegralAxisAligned,
+ VoltageIntegralResultTypes,
)
from .viz import (
ARROW_CURRENT,
@@ -89,6 +89,10 @@ def compute_integral(
Result of integral over remaining dimensions (frequency, time, mode indices).
"""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_base_result_data_array,
+ )
+
(dim1, dim2, dim3) = self.local_dims
h_field_name = f"{field}{dim1}"
@@ -130,7 +134,7 @@ def compute_integral(
# Integrate along the path
result = integrand.integrate(coord="s")
result = result.reset_coords(drop=True)
- return AxisAlignedPathIntegral._make_result_data_array(result)
+ return _make_base_result_data_array(result)
@staticmethod
def _compute_dl_component(coord_array: xr.DataArray, closed_contour=False) -> np.array:
@@ -243,7 +247,7 @@ class CustomVoltageIntegral2D(CustomPathIntegral2D):
.. TODO Improve by including extrapolate_to_endpoints field, non-trivial extension."""
- def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
+ def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes:
"""Compute voltage along path defined by a line.
Parameters
@@ -253,13 +257,16 @@ def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
Returns
-------
- :class:`.IntegralResultTypes`
+ :class:`.VoltageIntegralResultTypes`
Result of voltage computation over remaining dimensions (frequency, time, mode indices).
"""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_voltage_data_array,
+ )
+
AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field)
voltage = -1.0 * self.compute_integral(field="E", em_field=em_field)
- voltage = VoltageIntegralAxisAligned._set_data_array_attributes(voltage)
- return voltage
+ return _make_voltage_data_array(voltage)
@add_ax_if_none
def plot(
@@ -316,7 +323,7 @@ class CustomCurrentIntegral2D(CustomPathIntegral2D):
To compute the current flowing in the positive ``axis`` direction, the vertices should be
ordered in a counterclockwise direction."""
- def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
+ def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes:
"""Compute current flowing in a custom loop.
Parameters
@@ -326,13 +333,16 @@ def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
Returns
-------
- :class:`.IntegralResultTypes`
+ :class:`.CurrentIntegralResultTypes`
Result of current computation over remaining dimensions (frequency, time, mode indices).
"""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_current_data_array,
+ )
+
AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field)
current = self.compute_integral(field="H", em_field=em_field)
- current = CurrentIntegralAxisAligned._set_data_array_attributes(current)
- return current
+ return _make_current_data_array(current)
@add_ax_if_none
def plot(
diff --git a/tidy3d/plugins/microwave/impedance_calculator.py b/tidy3d/plugins/microwave/impedance_calculator.py
index a400818620..df9c729580 100644
--- a/tidy3d/plugins/microwave/impedance_calculator.py
+++ b/tidy3d/plugins/microwave/impedance_calculator.py
@@ -8,10 +8,9 @@
import pydantic.v1 as pd
from tidy3d.components.base import Tidy3dBaseModel
-from tidy3d.components.data.data_array import FreqDataArray, FreqModeDataArray, TimeDataArray
+from tidy3d.components.data.data_array import ImpedanceResultTypes
from tidy3d.components.data.monitor_data import FieldTimeData
from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor
-from tidy3d.constants import OHM
from tidy3d.exceptions import ValidationError
from tidy3d.log import log
@@ -19,7 +18,6 @@
from .path_integrals import (
AxisAlignedPathIntegral,
CurrentIntegralAxisAligned,
- IntegralResultTypes,
MonitorDataTypes,
VoltageIntegralAxisAligned,
)
@@ -43,7 +41,7 @@ class ImpedanceCalculator(Tidy3dBaseModel):
description="Definition of contour integral for computing current.",
)
- def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
+ def compute_impedance(self, em_field: MonitorDataTypes) -> ImpedanceResultTypes:
"""Compute impedance for the supplied ``em_field`` using ``voltage_integral`` and
``current_integral``. If only a single integral has been defined, impedance is
computed using the total flux in ``em_field``.
@@ -56,9 +54,11 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
Returns
-------
- :class:`.IntegralResultTypes`
+ :class:`.ImpedanceResultTypes`
Result of impedance computation over remaining dimensions (frequency, time, mode indices).
"""
+ from tidy3d.plugins.smatrix.utils import _make_impedance_data_array
+
AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field)
# If both voltage and current integrals have been defined then impedance is computed directly
@@ -98,7 +98,7 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
impedance = np.real(voltage) / np.real(current)
else:
impedance = voltage / current
- impedance = ImpedanceCalculator._set_data_array_attributes(impedance)
+ impedance = _make_impedance_data_array(impedance)
return impedance
@pd.validator("current_integral", always=True)
@@ -111,19 +111,6 @@ def check_voltage_or_current(cls, val, values):
)
return val
- @staticmethod
- def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes:
- """Helper to set additional metadata for ``IntegralResultTypes``."""
- # Determine type based on coords present
- if "mode_index" in data_array.coords:
- data_array = FreqModeDataArray(data_array)
- elif "f" in data_array.coords:
- data_array = FreqDataArray(data_array)
- else:
- data_array = TimeDataArray(data_array)
- data_array.name = "Z0"
- return data_array.assign_attrs(units=OHM, long_name="characteristic impedance")
-
@pd.root_validator(pre=False)
def _warn_rf_license(cls, values):
log.warning(
diff --git a/tidy3d/plugins/microwave/path_integrals.py b/tidy3d/plugins/microwave/path_integrals.py
index 802f5b0d92..75d1f893de 100644
--- a/tidy3d/plugins/microwave/path_integrals.py
+++ b/tidy3d/plugins/microwave/path_integrals.py
@@ -7,23 +7,22 @@
import numpy as np
import pydantic.v1 as pd
-import xarray as xr
from tidy3d.components.base import Tidy3dBaseModel, cached_property
from tidy3d.components.data.data_array import (
- FreqDataArray,
- FreqModeDataArray,
+ CurrentIntegralResultTypes,
+ IntegralResultTypes,
ScalarFieldDataArray,
ScalarFieldTimeDataArray,
ScalarModeFieldDataArray,
- TimeDataArray,
+ VoltageIntegralResultTypes,
)
from tidy3d.components.data.monitor_data import FieldData, FieldTimeData, ModeData, ModeSolverData
from tidy3d.components.geometry.base import Box, Geometry
from tidy3d.components.types import Ax, Axis, Coordinate2D, Direction
from tidy3d.components.validators import assert_line, assert_plane
from tidy3d.components.viz import add_ax_if_none
-from tidy3d.constants import AMP, VOLT, fp_eps
+from tidy3d.constants import fp_eps
from tidy3d.exceptions import DataError, Tidy3dError
from tidy3d.log import log
@@ -37,7 +36,6 @@
MonitorDataTypes = Union[FieldData, FieldTimeData, ModeData, ModeSolverData]
EMScalarFieldType = Union[ScalarFieldDataArray, ScalarFieldTimeDataArray, ScalarModeFieldDataArray]
-IntegralResultTypes = Union[FreqDataArray, FreqModeDataArray, TimeDataArray]
class AbstractAxesRH(Tidy3dBaseModel, ABC):
@@ -103,6 +101,9 @@ class AxisAlignedPathIntegral(AbstractAxesRH, Box):
def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultTypes:
"""Computes the defined integral given the input ``scalar_field``."""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_base_result_data_array,
+ )
if not scalar_field.does_cover(self.bounds, fp_eps, np.finfo(np.float32).smallest_normal):
raise DataError("Scalar field does not cover the integration domain.")
@@ -137,7 +138,7 @@ def compute_integral(self, scalar_field: EMScalarFieldType) -> IntegralResultTyp
coords_interp, method=method, kwargs={"fill_value": "extrapolate"}
)
result = scalar_field.integrate(coord=coord)
- return self._make_result_data_array(result)
+ return _make_base_result_data_array(result)
def _get_field_along_path(self, scalar_field: EMScalarFieldType) -> EMScalarFieldType:
"""Returns a selection of the input ``scalar_field`` ready for integration."""
@@ -205,15 +206,6 @@ def _check_monitor_data_supported(em_field: MonitorDataTypes):
f"{supported_types}"
)
- @staticmethod
- def _make_result_data_array(result: xr.DataArray) -> IntegralResultTypes:
- """Helper for creating the proper result type."""
- if "t" in result.coords:
- return TimeDataArray(data=result.data, coords=result.coords)
- if "f" in result.coords and "mode_index" in result.coords:
- return FreqModeDataArray(data=result.data, coords=result.coords)
- return FreqDataArray(data=result.data, coords=result.coords)
-
class VoltageIntegralAxisAligned(AxisAlignedPathIntegral):
"""Class for computing the voltage between two points defined by an axis-aligned line."""
@@ -224,8 +216,12 @@ class VoltageIntegralAxisAligned(AxisAlignedPathIntegral):
description="Positive indicates V=Vb-Va where position b has a larger coordinate along the axis of integration.",
)
- def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
+ def compute_voltage(self, em_field: MonitorDataTypes) -> VoltageIntegralResultTypes:
"""Compute voltage along path defined by a line."""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_voltage_data_array,
+ )
+
self._check_monitor_data_supported(em_field=em_field)
e_component = "xyz"[self.main_axis]
field_name = f"E{e_component}"
@@ -238,15 +234,7 @@ def compute_voltage(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
if self.sign == "+":
voltage *= -1
- voltage = VoltageIntegralAxisAligned._set_data_array_attributes(voltage)
- # Return data array of voltage while keeping coordinates of frequency|time|mode index
- return voltage
-
- @staticmethod
- def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes:
- """Add explanatory attributes to the data array."""
- data_array.name = "V"
- return data_array.assign_attrs(units=VOLT, long_name="voltage")
+ return _make_voltage_data_array(voltage)
@staticmethod
def from_terminal_positions(
@@ -381,8 +369,12 @@ class CurrentIntegralAxisAligned(AbstractAxesRH, Box):
description="This parameter is passed to :class:`AxisAlignedPathIntegral` objects when computing the contour integral.",
)
- def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
+ def compute_current(self, em_field: MonitorDataTypes) -> CurrentIntegralResultTypes:
"""Compute current flowing in loop defined by the outer edge of a rectangle."""
+ from tidy3d.plugins.smatrix.utils import (
+ _make_current_data_array,
+ )
+
AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field)
ax1 = self.remaining_axes[0]
ax2 = self.remaining_axes[1]
@@ -407,8 +399,7 @@ def compute_current(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
if self.sign == "-":
current *= -1
- current = CurrentIntegralAxisAligned._set_data_array_attributes(current)
- return current
+ return _make_current_data_array(current)
@cached_property
def main_axis(self) -> Axis:
@@ -498,12 +489,6 @@ def _to_path_integrals(
return (bottom, right, top, left)
- @staticmethod
- def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes:
- """Add explanatory attributes to the data array."""
- data_array.name = "I"
- return data_array.assign_attrs(units=AMP, long_name="current")
-
@add_ax_if_none
def plot(
self,
diff --git a/tidy3d/plugins/smatrix/__init__.py b/tidy3d/plugins/smatrix/__init__.py
index 73591039a7..e0f62d73b2 100644
--- a/tidy3d/plugins/smatrix/__init__.py
+++ b/tidy3d/plugins/smatrix/__init__.py
@@ -4,13 +4,23 @@
import warnings
-from .component_modelers.modal import AbstractComponentModeler, ComponentModeler, ModalPortDataArray
-from .component_modelers.terminal import TerminalComponentModeler
-from .data.terminal import PortDataArray, TerminalPortDataArray
-from .ports.coaxial_lumped import CoaxialLumpedPort
-from .ports.modal import Port
-from .ports.rectangular_lumped import LumpedPort
-from .ports.wave import WavePort
+from tidy3d.plugins.smatrix.component_modelers.base import (
+ AbstractComponentModeler,
+)
+from tidy3d.plugins.smatrix.component_modelers.modal import ComponentModeler
+from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
+from tidy3d.plugins.smatrix.data.data_array import (
+ ModalPortDataArray,
+ PortDataArray,
+ TerminalPortDataArray,
+)
+from tidy3d.plugins.smatrix.data.modal import ComponentModelerData, PortSimulationData
+from tidy3d.plugins.smatrix.data.terminal import MicrowaveSMatrixData, TerminalComponentModelerData
+from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort
+from tidy3d.plugins.smatrix.ports.modal import Port
+from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort
+from tidy3d.plugins.smatrix.ports.wave import WavePort
+from tidy3d.plugins.smatrix.run import compose_modeler_data, create_batch, run
# Instantiate on plugin import till we unite with toplevel
warnings.filterwarnings(
@@ -24,11 +34,19 @@
"AbstractComponentModeler",
"CoaxialLumpedPort",
"ComponentModeler",
+ "ComponentModelerData",
+ "ComponentModelerDataLumpedPort",
"LumpedPort",
+ "MicrowaveSMatrixData",
"ModalPortDataArray",
"Port",
"PortDataArray",
+ "PortSimulationData",
"TerminalComponentModeler",
+ "TerminalComponentModelerData",
"TerminalPortDataArray",
"WavePort",
+ "compose_modeler_data",
+ "create_batch",
+ "run",
]
diff --git a/tidy3d/plugins/smatrix/analysis/__init__.py b/tidy3d/plugins/smatrix/analysis/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tidy3d/plugins/smatrix/analysis/antenna.py b/tidy3d/plugins/smatrix/analysis/antenna.py
new file mode 100644
index 0000000000..a508a6a953
--- /dev/null
+++ b/tidy3d/plugins/smatrix/analysis/antenna.py
@@ -0,0 +1,112 @@
+from __future__ import annotations
+
+from typing import Optional
+
+import numpy as np
+
+from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData
+from tidy3d.plugins.smatrix.analysis.terminal import (
+ compute_power_wave_amplitudes_at_each_port,
+)
+from tidy3d.plugins.smatrix.data.data_array import PortDataArray
+from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData
+
+
+def get_antenna_metrics_data(
+ terminal_component_modeler_data: TerminalComponentModelerData,
+ port_amplitudes: Optional[dict[str, complex]] = None,
+ monitor_name: Optional[str] = None,
+) -> AntennaMetricsData:
+ """Calculate antenna parameters using superposition of fields from multiple port excitations.
+
+ The method computes the radiated far fields and port excitation power wave amplitudes
+ for a superposition of port excitations, which can be used to analyze antenna radiation
+ characteristics.
+
+ Parameters
+ ----------
+ terminal_component_modeler_data: TerminalComponentModelerData
+ Data associated with a :class:`TerminalComponentModeler` simulation run.
+ port_amplitudes : dict[str, complex] = None
+ Dictionary mapping port names to their desired excitation amplitudes. For each port,
+ :math:`\\frac{1}{2}|a|^2` represents the incident power from that port into the system.
+ If None, uses only the first port without any scaling of the raw simulation data.
+ monitor_name : str = None
+ Name of the :class:`.DirectivityMonitor` to use for calculating far fields.
+ If None, uses the first monitor in `radiation_monitors`.
+
+ Returns
+ -------
+ :class:`.AntennaMetricsData`
+ Container with antenna parameters including directivity, gain, and radiation efficiency,
+ computed from the superposition of fields from all excited ports.
+ """
+ # Use the first port as default if none specified
+ if port_amplitudes is None:
+ port_amplitudes = {terminal_component_modeler_data.modeler.ports[0].name: None}
+ port_names = [port.name for port in terminal_component_modeler_data.modeler.ports]
+ # Check port names, and create map from port to amplitude
+ port_dict = {}
+ for key in port_amplitudes.keys():
+ port = terminal_component_modeler_data.modeler.get_port_by_name(port_name=key)
+ port_dict[port] = port_amplitudes[key]
+ # Get the radiation monitor, use first as default
+ # if none specified
+ if monitor_name is None:
+ rad_mon = terminal_component_modeler_data.modeler.radiation_monitors[0]
+ else:
+ rad_mon = terminal_component_modeler_data.modeler.get_radiation_monitor_by_name(
+ monitor_name
+ )
+
+ # Create data arrays for holding the superposition of all port power wave amplitudes
+ f = list(rad_mon.freqs)
+ coords = {"f": f, "port": port_names}
+ a_sum = PortDataArray(np.zeros((len(f), len(port_names)), dtype=complex), coords=coords)
+ b_sum = a_sum.copy()
+ # Retrieve associated simulation data
+ combined_directivity_data = None
+ for port, amplitude in port_dict.items():
+ sim_data_port = terminal_component_modeler_data.data[
+ terminal_component_modeler_data.modeler.get_task_name(port)
+ ]
+ radiation_data = sim_data_port[rad_mon.name]
+
+ a, b = compute_power_wave_amplitudes_at_each_port(
+ modeler=terminal_component_modeler_data.modeler,
+ port_reference_impedances=terminal_component_modeler_data.port_reference_impedances,
+ sim_data=sim_data_port,
+ )
+ # Select a possible subset of frequencies
+ a = a.sel(f=f)
+ b = b.sel(f=f)
+ a_raw = a.sel(port=port.name)
+
+ if amplitude is None:
+ # No scaling performed when amplitude is None
+ scaled_directivity_data = sim_data_port[rad_mon.name]
+ scale_factor = 1.0
+ else:
+ scaled_directivity_data = (
+ terminal_component_modeler_data._monitor_data_at_port_amplitude(
+ port, sim_data_port, radiation_data, amplitude
+ )
+ )
+ scale_factor = amplitude / a_raw
+ a = scale_factor * a
+ b = scale_factor * b
+
+ # Combine the possibly scaled directivity data and the power wave amplitudes
+ if combined_directivity_data is None:
+ combined_directivity_data = scaled_directivity_data
+ else:
+ combined_directivity_data = combined_directivity_data + scaled_directivity_data
+ a_sum += a
+ b_sum += b
+
+ # Compute and add power measures to results
+ power_incident = np.real(0.5 * a_sum * np.conj(a_sum)).sum(dim="port")
+ power_reflected = np.real(0.5 * b_sum * np.conj(b_sum)).sum(dim="port")
+ return AntennaMetricsData.from_directivity_data(
+ combined_directivity_data, power_incident, power_reflected
+ )
diff --git a/tidy3d/plugins/smatrix/analysis/modal.py b/tidy3d/plugins/smatrix/analysis/modal.py
new file mode 100644
index 0000000000..260bf77d78
--- /dev/null
+++ b/tidy3d/plugins/smatrix/analysis/modal.py
@@ -0,0 +1,89 @@
+"""
+Tool for generating an S matrix automatically from a Tidy3d simulation and lumped port definitions.
+"""
+
+from __future__ import annotations
+
+import numpy as np
+
+from tidy3d.plugins.smatrix.data.data_array import ModalPortDataArray
+from tidy3d.plugins.smatrix.data.modal import ComponentModelerData
+
+
+def modal_construct_smatrix(modeler_data: ComponentModelerData) -> ModalPortDataArray:
+ """Post process :class:`.BatchData` to generate scattering matrix, for internal use only."""
+
+ max_mode_index_out, max_mode_index_in = modeler_data.modeler.max_mode_index
+ num_modes_out = max_mode_index_out + 1
+ num_modes_in = max_mode_index_in + 1
+ port_names_out, port_names_in = modeler_data.modeler.port_names
+
+ values = np.zeros(
+ (
+ len(port_names_out),
+ len(port_names_in),
+ num_modes_out,
+ num_modes_in,
+ len(modeler_data.modeler.freqs),
+ ),
+ dtype=complex,
+ )
+ coords = {
+ "port_out": port_names_out,
+ "port_in": port_names_in,
+ "mode_index_out": range(num_modes_out),
+ "mode_index_in": range(num_modes_in),
+ "f": np.array(modeler_data.modeler.freqs),
+ }
+ s_matrix = ModalPortDataArray(values, coords=coords)
+
+ # loop through source ports
+ for col_index in modeler_data.modeler.matrix_indices_run_sim:
+ port_name_in, mode_index_in = col_index
+ port_in = modeler_data.modeler.get_port_by_name(port_name=port_name_in)
+
+ sim_data = modeler_data.data[
+ modeler_data.modeler.get_task_name(port=port_in, mode_index=mode_index_in)
+ ]
+
+ for row_index in modeler_data.modeler.matrix_indices_monitor:
+ port_name_out, mode_index_out = row_index
+ port_out = modeler_data.modeler.get_port_by_name(port_name=port_name_out)
+
+ # directly compute the element
+ mode_amps_data = sim_data[port_out.name].copy().amps
+ dir_out = "-" if port_out.direction == "+" else "+"
+ amp = mode_amps_data.sel(f=coords["f"], direction=dir_out, mode_index=mode_index_out)
+ source_norm = modeler_data.modeler._normalization_factor(port_in, sim_data)
+ s_matrix_elements = np.array(amp.data) / np.array(source_norm)
+ s_matrix.loc[
+ {
+ "port_in": port_name_in,
+ "mode_index_in": mode_index_in,
+ "port_out": port_name_out,
+ "mode_index_out": mode_index_out,
+ }
+ ] = s_matrix_elements
+
+ # element can be determined by user-defined mapping
+ for (row_in, col_in), (row_out, col_out), mult_by in modeler_data.modeler.element_mappings:
+ port_out_from, mode_index_out_from = row_in
+ port_in_from, mode_index_in_from = col_in
+ coords_from = {
+ "port_in": port_in_from,
+ "mode_index_in": mode_index_in_from,
+ "port_out": port_out_from,
+ "mode_index_out": mode_index_out_from,
+ }
+
+ port_out_to, mode_index_out_to = row_out
+ port_in_to, mode_index_in_to = col_out
+ coords_to = {
+ "port_in": port_in_to,
+ "mode_index_in": mode_index_in_to,
+ "port_out": port_out_to,
+ "mode_index_out": mode_index_out_to,
+ }
+ s_matrix.loc[coords_to] = mult_by * s_matrix.loc[coords_from].values
+
+ return s_matrix
diff --git a/tidy3d/plugins/smatrix/analysis/terminal.py b/tidy3d/plugins/smatrix/analysis/terminal.py
new file mode 100644
index 0000000000..eb56733a86
--- /dev/null
+++ b/tidy3d/plugins/smatrix/analysis/terminal.py
@@ -0,0 +1,182 @@
+from __future__ import annotations
+
+import numpy as np
+
+from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
+from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray
+from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData
+from tidy3d.plugins.smatrix.ports.wave import WavePort
+from tidy3d.plugins.smatrix.utils import (
+ ab_to_s,
+ check_port_impedance_sign,
+ compute_F,
+ compute_port_VI,
+)
+
+
+def terminal_construct_smatrix(modeler_data: TerminalComponentModelerData) -> TerminalPortDataArray:
+ """
+ Constructs the scattering matrix (S-matrix) from raw simulation data stored in a :class:`TerminalComponentModelerData`
+
+ This function iterates through each port excitation simulation. For each run,
+ it calculates the resulting incident ('a') and reflected ('b') power wave
+ amplitudes at all ports. These amplitudes are compiled into matrices,
+ which are then used to compute the final S-matrix using the formula
+ :math:`S = b a^{-1}`.
+
+ Args:
+ modeler_data: Data object containing the modeler definition and the raw
+ results from each port simulation run.
+
+ Returns:
+ TerminalPortDataArray
+ The computed S-matrix as a data array with dimensions for frequency,
+ output port, and input port.
+ """
+
+ port_names = [port.name for port in modeler_data.modeler.ports]
+
+ values = np.zeros(
+ (len(modeler_data.modeler.freqs), len(port_names), len(port_names)),
+ dtype=complex,
+ )
+ coords = {
+ "f": np.array(modeler_data.modeler.freqs),
+ "port_out": port_names,
+ "port_in": port_names,
+ }
+ a_matrix = TerminalPortDataArray(values, coords=coords)
+ b_matrix = a_matrix.copy(deep=True)
+
+ # Tabulate the reference impedances at each port and frequency
+ port_impedances = port_reference_impedances(modeler_data=modeler_data)
+
+ # loop through source ports
+ for port_in in modeler_data.modeler.ports:
+ sim_data = modeler_data.data[modeler_data.modeler.get_task_name(port=port_in)]
+ a, b = compute_power_wave_amplitudes_at_each_port(
+ modeler_data.modeler, port_impedances, sim_data
+ )
+ indexer = {"f": a.f, "port_in": port_in.name, "port_out": a.port}
+ a_matrix.loc[indexer] = a
+ b_matrix.loc[indexer] = b
+
+ s_matrix = ab_to_s(a_matrix, b_matrix)
+ return s_matrix
+
+
+def port_reference_impedances(modeler_data: TerminalComponentModelerData) -> PortDataArray:
+ """Calculates the reference impedance for each port across all frequencies.
+
+ This function determines the characteristic impedance for every port defined
+ in the modeler. It handles two types of ports differently: for a
+ :class:`.WavePort`, the impedance is frequency-dependent and computed from
+ modal properties, while for other types like :class:`.LumpedPort`, the
+ impedance is a user-defined constant value.
+
+ Args:
+ modeler_data: Data object containing the modeler definition and the raw
+ simulation data needed for :class:`.WavePort` impedance calculations.
+
+ Returns:
+ TerminalComponentModelerData
+ A data array containing the complex impedance for each port at each
+ frequency.
+ """
+ port_names = [port.name for port in modeler_data.modeler.ports]
+
+ values = np.zeros(
+ (len(modeler_data.modeler.freqs), len(port_names)),
+ dtype=complex,
+ )
+ coords = {"f": np.array(modeler_data.modeler.freqs), "port": port_names}
+ port_impedances = PortDataArray(values, coords=coords)
+ for port in modeler_data.modeler.ports:
+ if isinstance(port, WavePort):
+ # Mode solver data for each wave port is stored in its associated SimulationData
+ sim_data_port = modeler_data.data[modeler_data.modeler.get_task_name(port=port)]
+ # WavePorts have a port impedance calculated from its associated modal field distribution
+ # and is frequency dependent.
+ impedances = port.compute_port_impedance(sim_data_port).values
+ port_impedances.loc[{"port": port.name}] = impedances.squeeze()
+ else:
+ # LumpedPorts have a constant reference impedance
+ port_impedances.loc[{"port": port.name}] = np.full(
+ len(modeler_data.modeler.freqs), port.impedance
+ )
+
+ port_impedances = modeler_data.modeler._set_port_data_array_attributes(port_impedances)
+ return port_impedances
+
+
+def compute_power_wave_amplitudes_at_each_port(
+ modeler: TerminalComponentModeler,
+ port_reference_impedances: PortDataArray,
+ sim_data: SimulationData,
+) -> tuple[PortDataArray, PortDataArray]:
+ """
+ Computes the incident (a) and reflected (b) power wave amplitudes at all ports
+ from a single simulation run where one port was excited.
+
+ This function converts the raw voltage (V) and current (I) data into power wave
+ amplitudes using standard microwave engineering formulas. It also performs a
+ sanity check to ensure the real part of the reference impedance is positive,
+ flipping signs of V and Z if necessary to maintain physical consistency.
+
+ Parameters
+ ----------
+ modeler : TerminalComponentModeler
+ The modeler setup defining the ports.
+ port_reference_impedances : PortDataArray
+ The characteristic impedance for each port at each frequency.
+ sim_data : SimulationData
+ The raw results (fields, currents, etc.) from a single simulation run.
+
+ Returns
+ -------
+ tuple[PortDataArray, PortDataArray]
+ A tuple containing two PortDataArrays:
+ - a: The incident power wave amplitudes at each port.
+ - b: The reflected power wave amplitudes at each port.
+ """
+ port_names = [port.name for port in modeler.ports]
+ values = np.zeros(
+ (len(modeler.freqs), len(port_names)),
+ dtype=complex,
+ )
+ coords = {
+ "f": np.array(modeler.freqs),
+ "port": port_names,
+ }
+
+ V_matrix = PortDataArray(values, coords=coords)
+ I_matrix = V_matrix.copy(deep=True)
+ a = V_matrix.copy(deep=True)
+ b = V_matrix.copy(deep=True)
+
+ for port_out in modeler.ports:
+ V_out, I_out = compute_port_VI(port_out, sim_data)
+ indexer = {"port": port_out.name}
+ V_matrix.loc[indexer] = V_out
+ I_matrix.loc[indexer] = I_out
+
+ V_numpy = V_matrix.values
+ I_numpy = I_matrix.values
+ Z_numpy = port_reference_impedances.values
+
+ # Check to make sure sign is consistent for all impedance values
+ check_port_impedance_sign(Z_numpy)
+
+ # # Check for negative real part of port impedance and flip the V and Z signs accordingly
+ negative_real_Z = np.real(Z_numpy) < 0
+ V_numpy = np.where(negative_real_Z, -V_numpy, V_numpy)
+ Z_numpy = np.where(negative_real_Z, -Z_numpy, Z_numpy)
+
+ F_numpy = compute_F(Z_numpy)
+
+ # Equation 4.67 - Pozar - Microwave Engineering 4ed
+ a.values = F_numpy * (V_numpy + Z_numpy * I_numpy)
+ b.values = F_numpy * (V_numpy - np.conj(Z_numpy) * I_numpy)
+
+ return a, b
diff --git a/tidy3d/plugins/smatrix/component_modelers/base.py b/tidy3d/plugins/smatrix/component_modelers/base.py
index e4b77ad7ab..fb73b3b965 100644
--- a/tidy3d/plugins/smatrix/component_modelers/base.py
+++ b/tidy3d/plugins/smatrix/component_modelers/base.py
@@ -2,39 +2,35 @@
from __future__ import annotations
-import os
-from abc import ABC, abstractmethod
+from abc import ABC
from typing import Optional, Union, get_args
import numpy as np
import pydantic.v1 as pd
-from tidy3d.components.base import Tidy3dBaseModel, cached_property
-from tidy3d.components.data.data_array import DataArray
-from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.components.base import Tidy3dBaseModel
from tidy3d.components.simulation import Simulation
from tidy3d.components.types import FreqArray
-from tidy3d.config import config
from tidy3d.constants import HERTZ
from tidy3d.exceptions import SetupError, Tidy3dKeyError
from tidy3d.log import log
-from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort
from tidy3d.plugins.smatrix.ports.modal import Port
-from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort
+from tidy3d.plugins.smatrix.ports.types import TerminalPortType
from tidy3d.plugins.smatrix.ports.wave import WavePort
-from tidy3d.web.api.container import Batch, BatchData
# fwidth of gaussian pulse in units of central frequency
FWIDTH_FRAC = 1.0 / 10
DEFAULT_DATA_DIR = "."
-LumpedPortType = Union[LumpedPort, CoaxialLumpedPort]
-TerminalPortType = Union[LumpedPortType, WavePort]
-
class AbstractComponentModeler(ABC, Tidy3dBaseModel):
"""Tool for modeling devices and computing port parameters."""
+ name: str = pd.Field(
+ "",
+ title="Simulation",
+ description="Simulation describing the device without any sources present.",
+ )
simulation: Simulation = pd.Field(
...,
title="Simulation",
@@ -66,56 +62,6 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel):
"pulse spectrum which can have a nonzero DC component.",
)
- folder_name: str = pd.Field(
- "default",
- title="Folder Name",
- description="Name of the folder for the tasks on web.",
- )
-
- verbose: bool = pd.Field(
- False,
- title="Verbosity",
- description="Whether the :class:`.AbstractComponentModeler` should print status and progressbars.",
- )
-
- callback_url: str = pd.Field(
- None,
- title="Callback URL",
- description="Http PUT url to receive simulation finish event. "
- "The body content is a json file with fields "
- "``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
- )
-
- path_dir: str = pd.Field(
- DEFAULT_DATA_DIR,
- title="Directory Path",
- description="Base directory where data and batch will be downloaded.",
- )
-
- solver_version: str = pd.Field(
- None,
- title="Solver Version",
- description_str="Custom solver version to use. "
- "If not supplied, uses default for the current front end version.",
- )
-
- batch_cached: Batch = pd.Field(
- None,
- title="Batch (Cached)",
- description="Optional field to specify ``batch``. Only used as a workaround internally "
- "so that ``batch`` is written when ``.to_file()`` and then the proper batch is loaded "
- "from ``.from_file()``. We recommend leaving unset as setting this field along with "
- "fields that were not used to create the task will cause errors.",
- )
-
- @pd.root_validator(pre=False)
- def _warn_deprecation_2_10(cls, values):
- log.warning(
- "ℹ️ ⚠️ Backwards compatibility will be broken for all the ComponentModeler classes in tidy3d version 2.10. Migration documentation will be provided, and existing functionality can be accessed in a different way.",
- log_once=True,
- )
- return values
-
@pd.validator("simulation", always=True)
def _sim_has_no_sources(cls, val):
"""Make sure simulation has no sources as they interfere with tool."""
@@ -140,97 +86,12 @@ def _warn_rf_license(cls, val):
return val
@staticmethod
- def _task_name(port: Port, mode_index: Optional[int] = None) -> str:
+ def get_task_name(port: Port, mode_index: Optional[int] = None) -> str:
"""The name of a task, determined by the port of the source and mode index, if given."""
if mode_index is not None:
return f"smatrix_{port.name}_{mode_index}"
return f"smatrix_{port.name}"
- @cached_property
- def sim_dict(self) -> dict[str, Simulation]:
- """Generate all the :class:`.Simulation` objects for the S matrix calculation."""
-
- def to_file(self, fname: str) -> None:
- """Exports :class:`AbstractComponentModeler` instance to .yaml, .json, or .hdf5 file
-
- Parameters
- ----------
- fname : str
- Full path to the .yaml or .json file to save the :class:`AbstractComponentModeler` to.
-
- Example
- -------
- >>> modeler.to_file(fname='folder/sim.json') # doctest: +SKIP
- """
-
- batch_cached = self._cached_properties.get("batch")
- if batch_cached is not None:
- jobs_cached = batch_cached._cached_properties.get("jobs")
- if jobs_cached is not None:
- jobs = {}
- for key, job in jobs_cached.items():
- task_id = job._cached_properties.get("task_id")
- jobs[key] = job.updated_copy(task_id_cached=task_id)
- batch_cached = batch_cached.updated_copy(jobs_cached=jobs)
- self = self.updated_copy(batch_cached=batch_cached)
- super(AbstractComponentModeler, self).to_file(fname=fname) # noqa: UP008
-
- @cached_property
- def batch(self) -> Batch:
- """:class:`.Batch` associated with this component modeler."""
-
- if self.batch_cached is not None:
- return self.batch_cached
-
- # first try loading the batch from file, if it exists
- batch_path = self._batch_path
-
- if os.path.exists(batch_path):
- return Batch.from_file(fname=batch_path)
-
- return Batch(
- simulations=self.sim_dict,
- folder_name=self.folder_name,
- callback_url=self.callback_url,
- verbose=self.verbose,
- solver_version=self.solver_version,
- )
-
- @cached_property
- def batch_path(self) -> str:
- """Path to the batch saved to file."""
- return self.batch._batch_path(path_dir=self.path_dir)
-
- @cached_property
- def batch_data(self) -> BatchData:
- """The :class:`.BatchData` associated with the simulations run for this component modeler."""
- return self.batch.run(path_dir=self.path_dir)
-
- def get_path_dir(self, path_dir: str) -> None:
- """Check whether the supplied 'path_dir' matches the internal field value."""
-
- if path_dir != self.path_dir and path_dir != DEFAULT_DATA_DIR:
- raise ValueError(
- f"'path_dir' of '{path_dir}' passed, but 'ComponentModeler.path_dir' is "
- f"{self.path_dir}. Moving forward, only the 'ComponentModeler.path_dir' will be "
- "used internally, please update your scripts accordingly to avoid passing this "
- "value to methods. "
- )
-
- return self.path_dir
-
- @cached_property
- def _batch_path(self) -> str:
- """Where we store the batch for this :class:`AbstractComponentModeler` instance after the run."""
- return os.path.join(self.path_dir, "batch" + str(hash(self)) + ".hdf5")
-
- def _run_sims(self, path_dir: str = DEFAULT_DATA_DIR) -> BatchData:
- """Run :class:`.Simulation` for each port and return the batch after saving."""
- _ = self.get_path_dir(path_dir)
- self.batch.to_file(self._batch_path)
- batch_data = self.batch_data
- return batch_data
-
def get_port_by_name(self, port_name: str) -> Port:
"""Get the port from the name."""
ports = [port for port in self.ports if port.name == port_name]
@@ -238,28 +99,6 @@ def get_port_by_name(self, port_name: str) -> Port:
raise Tidy3dKeyError(f'Port "{port_name}" not found.')
return ports[0]
- @abstractmethod
- def _construct_smatrix(self, batch_data: BatchData) -> DataArray:
- """Post process :class:`.BatchData` to generate scattering matrix."""
-
- @abstractmethod
- def _internal_construct_smatrix(self, batch_data: BatchData) -> DataArray:
- """Post process :class:`.BatchData` to generate scattering matrix, for internal use only."""
-
- def run(self, path_dir: str = DEFAULT_DATA_DIR) -> DataArray:
- """Solves for the scattering matrix of the system."""
- _ = self.get_path_dir(path_dir)
- return self._construct_smatrix()
-
- def load(self, path_dir: str = DEFAULT_DATA_DIR) -> DataArray:
- """Load a scattering matrix from saved :class:`.BatchData` object."""
- return self.run(path_dir=path_dir)
-
- @staticmethod
- def inv(matrix: DataArray):
- """Helper to invert a port matrix."""
- return np.linalg.inv(matrix)
-
def _shift_value_signed(self, port: Union[Port, WavePort]) -> float:
"""How far (signed) to shift the source from the monitor."""
@@ -309,10 +148,5 @@ def _shift_value_signed(self, port: Union[Port, WavePort]) -> float:
new_pos = grid_centers[shifted_index]
return new_pos - port_position
- def sim_data_by_task_name(self, task_name: str) -> SimulationData:
- """Get the simulation data by task name, avoids emitting warnings from the ``Simulation``."""
- log_level_cache = config.logging_level
- config.logging_level = "ERROR"
- sim_data = self.batch_data[task_name]
- config.logging_level = log_level_cache
- return sim_data
+
+AbstractComponentModeler.update_forward_refs()
diff --git a/tidy3d/plugins/smatrix/component_modelers/modal.py b/tidy3d/plugins/smatrix/component_modelers/modal.py
index 251d48252a..f4806c59b5 100644
--- a/tidy3d/plugins/smatrix/component_modelers/modal.py
+++ b/tidy3d/plugins/smatrix/component_modelers/modal.py
@@ -18,8 +18,7 @@
from tidy3d.components.types import Ax, Complex
from tidy3d.components.viz import add_ax_if_none, equal_aspect
from tidy3d.exceptions import SetupError
-from tidy3d.plugins.smatrix.ports.modal import ModalPortDataArray, Port
-from tidy3d.web.api.container import BatchData
+from tidy3d.plugins.smatrix.ports.modal import Port
from .base import FWIDTH_FRAC, AbstractComponentModeler
@@ -72,20 +71,6 @@ class ComponentModeler(AbstractComponentModeler):
:class:`ComponentModeler`. ``run_only`` contains the scattering matrix indices that the user wants to run as a
source. If any indices are excluded, they will not be run."""
- verbose: bool = pd.Field(
- False,
- title="Verbosity",
- description="Whether the :class:`.ComponentModeler` should print status and progressbars.",
- )
-
- callback_url: str = pd.Field(
- None,
- title="Callback URL",
- description="Http PUT url to receive simulation finish event. "
- "The body content is a json file with fields "
- "``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
- )
-
@pd.validator("simulation", always=True)
def _sim_has_no_sources(cls, val):
"""Make sure simulation has no sources as they interfere with tool."""
@@ -108,7 +93,7 @@ def sim_dict(self) -> dict[str, Simulation]:
new_mnts = list(self.simulation.monitors) + mode_monitors
sim_copy = self.simulation.copy(update={"sources": [mode_source], "monitors": new_mnts})
- task_name = self._task_name(port=port, mode_index=mode_index)
+ task_name = self.get_task_name(port=port, mode_index=mode_index)
sim_dict[task_name] = sim_copy
return sim_dict
@@ -272,79 +257,3 @@ def get_max_mode_indices(matrix_elements: tuple[str, int]) -> int:
max_mode_index_in = get_max_mode_indices(self.matrix_indices_source)
return max_mode_index_out, max_mode_index_in
-
- def _construct_smatrix(self) -> ModalPortDataArray:
- """Post process :class:`.BatchData` to generate scattering matrix."""
- return self._internal_construct_smatrix(batch_data=self.batch_data)
-
- def _internal_construct_smatrix(self, batch_data: BatchData) -> ModalPortDataArray:
- """Post process :class:`.BatchData` to generate scattering matrix, for internal use only."""
-
- max_mode_index_out, max_mode_index_in = self.max_mode_index
- num_modes_out = max_mode_index_out + 1
- num_modes_in = max_mode_index_in + 1
- port_names_out, port_names_in = self.port_names
-
- values = np.zeros(
- (len(port_names_out), len(port_names_in), num_modes_out, num_modes_in, len(self.freqs)),
- dtype=complex,
- )
- coords = {
- "port_out": port_names_out,
- "port_in": port_names_in,
- "mode_index_out": range(num_modes_out),
- "mode_index_in": range(num_modes_in),
- "f": np.array(self.freqs),
- }
- s_matrix = ModalPortDataArray(values, coords=coords)
-
- # loop through source ports
- for col_index in self.matrix_indices_run_sim:
- port_name_in, mode_index_in = col_index
- port_in = self.get_port_by_name(port_name=port_name_in)
-
- sim_data = batch_data[self._task_name(port=port_in, mode_index=mode_index_in)]
-
- for row_index in self.matrix_indices_monitor:
- port_name_out, mode_index_out = row_index
- port_out = self.get_port_by_name(port_name=port_name_out)
-
- # directly compute the element
- mode_amps_data = sim_data[port_out.name].copy().amps
- dir_out = "-" if port_out.direction == "+" else "+"
- amp = mode_amps_data.sel(
- f=coords["f"], direction=dir_out, mode_index=mode_index_out
- )
- source_norm = self._normalization_factor(port_in, sim_data)
- s_matrix_elements = np.array(amp.data) / np.array(source_norm)
- s_matrix.loc[
- {
- "port_in": port_name_in,
- "mode_index_in": mode_index_in,
- "port_out": port_name_out,
- "mode_index_out": mode_index_out,
- }
- ] = s_matrix_elements
-
- # element can be determined by user-defined mapping
- for (row_in, col_in), (row_out, col_out), mult_by in self.element_mappings:
- port_out_from, mode_index_out_from = row_in
- port_in_from, mode_index_in_from = col_in
- coords_from = {
- "port_in": port_in_from,
- "mode_index_in": mode_index_in_from,
- "port_out": port_out_from,
- "mode_index_out": mode_index_out_from,
- }
-
- port_out_to, mode_index_out_to = row_out
- port_in_to, mode_index_in_to = col_out
- coords_to = {
- "port_in": port_in_to,
- "mode_index_in": mode_index_in_to,
- "port_out": port_out_to,
- "mode_index_out": mode_index_out_to,
- }
- s_matrix.loc[coords_to] = mult_by * s_matrix.loc[coords_from].values
-
- return s_matrix
diff --git a/tidy3d/plugins/smatrix/component_modelers/terminal.py b/tidy3d/plugins/smatrix/component_modelers/terminal.py
index 0e5b9cad0a..06bba945e9 100644
--- a/tidy3d/plugins/smatrix/component_modelers/terminal.py
+++ b/tidy3d/plugins/smatrix/component_modelers/terminal.py
@@ -8,27 +8,24 @@
import pydantic.v1 as pd
from tidy3d.components.base import cached_property
-from tidy3d.components.data.data_array import DataArray, FreqDataArray
-from tidy3d.components.data.monitor_data import MonitorData
-from tidy3d.components.data.sim_data import SimulationData
from tidy3d.components.geometry.utils_2d import snap_coordinate_to_grid
-from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData
from tidy3d.components.monitor import DirectivityMonitor
from tidy3d.components.simulation import Simulation
from tidy3d.components.source.time import GaussianPulse
from tidy3d.components.types import Ax
from tidy3d.components.viz import add_ax_if_none, equal_aspect
from tidy3d.constants import C_0, OHM
-from tidy3d.exceptions import SetupError, Tidy3dError, Tidy3dKeyError, ValidationError
+from tidy3d.exceptions import SetupError, Tidy3dKeyError, ValidationError
from tidy3d.log import log
-from tidy3d.plugins.smatrix.data.terminal import PortDataArray, TerminalPortDataArray
+from tidy3d.plugins.smatrix.component_modelers.base import (
+ AbstractComponentModeler,
+ TerminalPortType,
+)
+from tidy3d.plugins.smatrix.data.data_array import PortDataArray
from tidy3d.plugins.smatrix.ports.base_lumped import AbstractLumpedPort
from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort
from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort
from tidy3d.plugins.smatrix.ports.wave import WavePort
-from tidy3d.web.api.container import BatchData
-
-from .base import AbstractComponentModeler, TerminalPortType
class TerminalComponentModeler(AbstractComponentModeler):
@@ -57,6 +54,14 @@ def _warn_rf_license(cls, values):
)
return values
+ @pd.root_validator(pre=False)
+ def _warn_deprecation_2_10(cls, values):
+ log.warning(
+ "ℹ️ ⚠️ Backwards compatibility will be broken for the TerminalComponentModeler class in tidy3d version 2.10. Migration documentation will be provided, and existing functionality can be accessed in a different way.",
+ log_once=True,
+ )
+ return values
+
@equal_aspect
@add_ax_if_none
def plot_sim(
@@ -163,7 +168,7 @@ def sim_dict(self) -> dict[str, Simulation]:
port_source = port.to_source(
self._source_time, snap_center=snap_centers[port.name], grid=sim_wo_source.grid
)
- task_name = self._task_name(port=port)
+ task_name = self.get_task_name(port=port)
sim_dict[task_name] = sim_wo_source.updated_copy(sources=[port_source])
# Now, create simulations with wave port sources and mode solver monitors for computing port modes
@@ -176,7 +181,7 @@ def sim_dict(self) -> dict[str, Simulation]:
update_dict = {"sources": [port_source]}
- task_name = self._task_name(port=wave_port)
+ task_name = self.get_task_name(port=wave_port)
sim_dict[task_name] = sim_wo_source.copy(update=update_dict)
# Check final simulations for grid size at ports
@@ -193,41 +198,6 @@ def _source_time(self):
fmin=min(self.freqs), fmax=max(self.freqs), remove_dc_component=self.remove_dc_component
)
- def _construct_smatrix(self) -> TerminalPortDataArray:
- """Post process :class:`.BatchData` to generate scattering matrix."""
- return self._internal_construct_smatrix(batch_data=self.batch_data)
-
- def _internal_construct_smatrix(self, batch_data: BatchData) -> TerminalPortDataArray:
- """Post process :class:`.BatchData` to generate scattering matrix, for internal use only."""
-
- port_names = [port.name for port in self.ports]
-
- values = np.zeros(
- (len(self.freqs), len(port_names), len(port_names)),
- dtype=complex,
- )
- coords = {
- "f": np.array(self.freqs),
- "port_out": port_names,
- "port_in": port_names,
- }
- a_matrix = TerminalPortDataArray(values, coords=coords)
- b_matrix = a_matrix.copy(deep=True)
-
- # Tabulate the reference impedances at each port and frequency
- port_impedances = self._port_reference_impedances(batch_data=batch_data)
-
- # loop through source ports
- for port_in in self.ports:
- sim_data = batch_data[self._task_name(port=port_in)]
- a, b = self.compute_power_wave_amplitudes_at_each_port(port_impedances, sim_data)
- indexer = {"f": a.f, "port_in": port_in.name, "port_out": a.port}
- a_matrix.loc[indexer] = a
- b_matrix.loc[indexer] = b
-
- s_matrix = self.ab_to_s(a_matrix, b_matrix)
- return s_matrix
-
@pd.validator("simulation")
def _validate_3d_simulation(cls, val):
"""Error if :class:`.Simulation` is not a 3D simulation"""
@@ -280,221 +250,6 @@ def _check_grid_size_at_wave_ports(simulation: Simulation, ports: list[WavePort]
"for the simulation passed to the 'TerminalComponentModeler'."
)
- def compute_power_wave_amplitudes_at_each_port(
- self, port_reference_impedances: PortDataArray, sim_data: SimulationData
- ) -> tuple[PortDataArray, PortDataArray]:
- """Compute the incident and reflected power wave amplitudes at each port.
- The computed amplitudes have not been normalized.
-
- Parameters
- ----------
- port_reference_impedances : :class:`.PortDataArray`
- Reference impedance at each port.
- sim_data : :class:`.SimulationData`
- Results from the simulation.
-
- Returns
- -------
- tuple[:class:`.PortDataArray`, :class:`.PortDataArray`]
- Incident (a) and reflected (b) power wave amplitudes at each port.
- """
- port_names = [port.name for port in self.ports]
- values = np.zeros(
- (len(self.freqs), len(port_names)),
- dtype=complex,
- )
- coords = {
- "f": np.array(self.freqs),
- "port": port_names,
- }
-
- V_matrix = PortDataArray(values, coords=coords)
- I_matrix = V_matrix.copy(deep=True)
- a = V_matrix.copy(deep=True)
- b = V_matrix.copy(deep=True)
-
- for port_out in self.ports:
- V_out, I_out = self.compute_port_VI(port_out, sim_data)
- indexer = {"port": port_out.name}
- V_matrix.loc[indexer] = V_out
- I_matrix.loc[indexer] = I_out
-
- V_numpy = V_matrix.values
- I_numpy = I_matrix.values
- Z_numpy = port_reference_impedances.values
-
- # Check to make sure sign is consistent for all impedance values
- self._check_port_impedance_sign(Z_numpy)
-
- # # Check for negative real part of port impedance and flip the V and Z signs accordingly
- negative_real_Z = np.real(Z_numpy) < 0
- V_numpy = np.where(negative_real_Z, -V_numpy, V_numpy)
- Z_numpy = np.where(negative_real_Z, -Z_numpy, Z_numpy)
-
- F_numpy = TerminalComponentModeler._compute_F(Z_numpy)
-
- # Equation 4.67 - Pozar - Microwave Engineering 4ed
- a.values = F_numpy * (V_numpy + Z_numpy * I_numpy)
- b.values = F_numpy * (V_numpy - np.conj(Z_numpy) * I_numpy)
-
- return a, b
-
- @staticmethod
- def compute_port_VI(
- port_out: TerminalPortType, sim_data: SimulationData
- ) -> tuple[FreqDataArray, FreqDataArray]:
- """Compute the port voltages and currents.
-
- Parameters
- ----------
- port_out : ``TerminalPortType``
- Port for computing voltage and current.
- sim_data : :class:`.SimulationData`
- Results from simulation containing field data.
-
- Returns
- -------
- tuple[FreqDataArray, FreqDataArray]
- Voltage and current values at the port as frequency arrays.
- """
- voltage = port_out.compute_voltage(sim_data)
- current = port_out.compute_current(sim_data)
- return voltage, current
-
- @staticmethod
- def compute_power_wave_amplitudes(
- port: Union[LumpedPort, CoaxialLumpedPort], sim_data: SimulationData
- ) -> tuple[FreqDataArray, FreqDataArray]:
- """Compute the incident and reflected power wave amplitudes at a lumped port.
- The computed amplitudes have not been normalized.
-
- Parameters
- ----------
- port : Union[:class:`.LumpedPort`, :class:`.CoaxialLumpedPort`]
- Port for computing voltage and current.
- sim_data : :class:`.SimulationData`
- Results from the simulation.
-
- Returns
- -------
- tuple[FreqDataArray, FreqDataArray]
- Incident (a) and reflected (b) power wave amplitude frequency arrays.
- """
- voltage, current = TerminalComponentModeler.compute_port_VI(port, sim_data)
- # Amplitudes for the incident and reflected power waves
- a = (voltage + port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
- b = (voltage - port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
- return a, b
-
- @staticmethod
- def compute_power_delivered_by_port(
- port: Union[LumpedPort, CoaxialLumpedPort], sim_data: SimulationData
- ) -> FreqDataArray:
- """Compute the power delivered to the network by a lumped port.
-
- Parameters
- ----------
- port : Union[:class:`.LumpedPort`, :class:`.CoaxialLumpedPort`]
- Port for computing voltage and current.
- sim_data : :class:`.SimulationData`
- Results from the simulation.
-
- Returns
- -------
- FreqDataArray
- Power in units of Watts as a frequency array.
- """
- a, b = TerminalComponentModeler.compute_power_wave_amplitudes(sim_data=sim_data, port=port)
- # Power delivered is the incident power minus the reflected power
- return 0.5 * (np.abs(a) ** 2 - np.abs(b) ** 2)
-
- @staticmethod
- def ab_to_s(
- a_matrix: TerminalPortDataArray, b_matrix: TerminalPortDataArray
- ) -> TerminalPortDataArray:
- """Get the scattering matrix given the power wave matrices."""
- # Ensure dimensions are ordered properly
- a_matrix = a_matrix.transpose(*TerminalPortDataArray._dims)
- b_matrix = b_matrix.transpose(*TerminalPortDataArray._dims)
-
- s_matrix = a_matrix.copy(deep=True)
- a_vals = s_matrix.copy(deep=True).values
- b_vals = b_matrix.copy(deep=True).values
-
- s_vals = np.matmul(b_vals, AbstractComponentModeler.inv(a_vals))
-
- s_matrix.data = s_vals
- return s_matrix
-
- @staticmethod
- def s_to_z(
- s_matrix: TerminalPortDataArray, reference: Union[complex, PortDataArray]
- ) -> DataArray:
- """Get the impedance matrix given the scattering matrix and a reference impedance."""
-
- # Ensure dimensions are ordered properly
- z_matrix = s_matrix.transpose(*TerminalPortDataArray._dims).copy(deep=True)
- s_vals = z_matrix.values
- eye = np.eye(len(s_matrix.port_out.values), len(s_matrix.port_in.values))
- if isinstance(reference, PortDataArray):
- # From Equation 4.68 - Pozar - Microwave Engineering 4ed
- # Ensure that Zport, F, and Finv act as diagonal matrices when multiplying by left or right
- shape_left = (len(s_matrix.f), len(s_matrix.port_out), 1)
- shape_right = (len(s_matrix.f), 1, len(s_matrix.port_in))
- Zport = reference.values.reshape(shape_right)
- F = TerminalComponentModeler._compute_F(Zport).reshape(shape_right)
- Finv = (1.0 / F).reshape(shape_left)
- FinvSF = Finv * s_vals * F
- RHS = eye * np.conj(Zport) + FinvSF * Zport
- LHS = eye - FinvSF
- z_vals = np.matmul(AbstractComponentModeler.inv(LHS), RHS)
- else:
- # Simpler case when all port impedances are the same
- z_vals = (
- np.matmul(AbstractComponentModeler.inv(eye - s_vals), (eye + s_vals)) * reference
- )
-
- z_matrix.data = z_vals
- return z_matrix
-
- @cached_property
- def port_reference_impedances(self) -> PortDataArray:
- """The reference impedance used at each port for definining power wave amplitudes."""
- return self._port_reference_impedances(self.batch_data)
-
- def _port_reference_impedances(self, batch_data: BatchData) -> PortDataArray:
- """Tabulates the reference impedance of each port at each frequency using the
- supplied :class:`.BatchData`.
- """
- port_names = [port.name for port in self.ports]
-
- values = np.zeros(
- (len(self.freqs), len(port_names)),
- dtype=complex,
- )
- coords = {"f": np.array(self.freqs), "port": port_names}
- port_impedances = PortDataArray(values, coords=coords)
- for port in self.ports:
- if isinstance(port, WavePort):
- # Mode solver data for each wave port is stored in its associated SimulationData
- sim_data_port = batch_data[self._task_name(port=port)]
- # WavePorts have a port impedance calculated from its associated modal field distribution
- # and is frequency dependent.
- impedances = port.compute_port_impedance(sim_data_port).values
- port_impedances.loc[{"port": port.name}] = impedances.squeeze()
- else:
- # LumpedPorts have a constant reference impedance
- port_impedances.loc[{"port": port.name}] = np.full(len(self.freqs), port.impedance)
-
- port_impedances = TerminalComponentModeler._set_port_data_array_attributes(port_impedances)
- return port_impedances
-
- @staticmethod
- def _compute_F(Z_numpy: np.array):
- """Helper to convert port impedance matrix to F, which is used for
- computing generalized scattering parameters."""
- return 1.0 / (2.0 * np.sqrt(np.real(Z_numpy)))
-
@cached_property
def _lumped_ports(self) -> list[AbstractLumpedPort]:
"""A list of all lumped ports in the ``TerminalComponentModeler``"""
@@ -511,19 +266,6 @@ def _set_port_data_array_attributes(data_array: PortDataArray) -> PortDataArray:
data_array.name = "Z0"
return data_array.assign_attrs(units=OHM, long_name="characteristic impedance")
- @staticmethod
- def _check_port_impedance_sign(Z_numpy: np.ndarray):
- """Sanity check for consistent sign of real part of Z for each port across all frequencies."""
- for port_idx in range(Z_numpy.shape[1]):
- port_Z = Z_numpy[:, port_idx]
- signs = np.sign(np.real(port_Z))
- if not np.all(signs == signs[0]):
- raise Tidy3dError(
- f"Inconsistent sign of real part of Z detected for port {port_idx}. "
- "If you received this error, please create an issue in the Tidy3D "
- "github repository."
- )
-
def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor:
"""Find and return a :class:`.DirectivityMonitor` monitor by its name.
@@ -547,113 +289,5 @@ def get_radiation_monitor_by_name(self, monitor_name: str) -> DirectivityMonitor
return monitor
raise Tidy3dKeyError(f"No radiation monitor named '{monitor_name}'.")
- def _monitor_data_at_port_amplitude(
- self,
- port: TerminalPortType,
- sim_data: SimulationData,
- monitor_data: MonitorData,
- a_port: Union[FreqDataArray, complex],
- ) -> MonitorData:
- """Normalize the monitor data to a desired complex amplitude of a port,
- represented by ``a_port``, where :math:`\\frac{1}{2}|a|^2` is the power
- incident from the port into the system.
- """
- a_raw, _ = self.compute_power_wave_amplitudes_at_each_port(
- self.port_reference_impedances, sim_data
- )
- a_raw_port = a_raw.sel(port=port.name)
- if not isinstance(a_port, FreqDataArray):
- freqs = list(monitor_data.monitor.freqs)
- array_vals = a_port * np.ones(len(freqs))
- a_port = FreqDataArray(array_vals, coords={"f": freqs})
- scale_array = a_port / a_raw_port
- return monitor_data.scale_fields_by_freq_array(scale_array, method="nearest")
-
- def get_antenna_metrics_data(
- self,
- port_amplitudes: Optional[dict[str, complex]] = None,
- monitor_name: Optional[str] = None,
- ) -> AntennaMetricsData:
- """Calculate antenna parameters using superposition of fields from multiple port excitations.
-
- The method computes the radiated far fields and port excitation power wave amplitudes
- for a superposition of port excitations, which can be used to analyze antenna radiation
- characteristics.
-
- Parameters
- ----------
- port_amplitudes : dict[str, complex] = None
- Dictionary mapping port names to their desired excitation amplitudes. For each port,
- :math:`\\frac{1}{2}|a|^2` represents the incident power from that port into the system.
- If None, uses only the first port without any scaling of the raw simulation data.
- monitor_name : str = None
- Name of the :class:`.DirectivityMonitor` to use for calculating far fields.
- If None, uses the first monitor in `radiation_monitors`.
- Returns
- -------
- :class:`.AntennaMetricsData`
- Container with antenna parameters including directivity, gain, and radiation efficiency,
- computed from the superposition of fields from all excited ports.
- """
- # Use the first port as default if none specified
- if port_amplitudes is None:
- port_amplitudes = {self.ports[0].name: None}
- port_names = [port.name for port in self.ports]
- # Check port names, and create map from port to amplitude
- port_dict = {}
- for key in port_amplitudes.keys():
- port = self.get_port_by_name(port_name=key)
- port_dict[port] = port_amplitudes[key]
- # Get the radiation monitor, use first as default
- # if none specified
- if monitor_name is None:
- rad_mon = self.radiation_monitors[0]
- else:
- rad_mon = self.get_radiation_monitor_by_name(monitor_name)
-
- # Create data arrays for holding the superposition of all port power wave amplitudes
- f = list(rad_mon.freqs)
- coords = {"f": f, "port": port_names}
- a_sum = PortDataArray(np.zeros((len(f), len(port_names)), dtype=complex), coords=coords)
- b_sum = a_sum.copy()
- # Retrieve associated simulation data
- combined_directivity_data = None
- for port, amplitude in port_dict.items():
- sim_data_port = self.batch_data[self._task_name(port=port)]
- radiation_data = sim_data_port[rad_mon.name]
-
- a, b = self.compute_power_wave_amplitudes_at_each_port(
- self.port_reference_impedances, sim_data_port
- )
- # Select a possible subset of frequencies
- a = a.sel(f=f)
- b = b.sel(f=f)
- a_raw = a.sel(port=port.name)
-
- if amplitude is None:
- # No scaling performed when amplitude is None
- scaled_directivity_data = sim_data_port[rad_mon.name]
- scale_factor = 1.0
- else:
- scaled_directivity_data = self._monitor_data_at_port_amplitude(
- port, sim_data_port, radiation_data, amplitude
- )
- scale_factor = amplitude / a_raw
- a = scale_factor * a
- b = scale_factor * b
-
- # Combine the possibly scaled directivity data and the power wave amplitudes
- if combined_directivity_data is None:
- combined_directivity_data = scaled_directivity_data
- else:
- combined_directivity_data = combined_directivity_data + scaled_directivity_data
- a_sum += a
- b_sum += b
-
- # Compute and add power measures to results
- power_incident = np.real(0.5 * a_sum * np.conj(a_sum)).sum(dim="port")
- power_reflected = np.real(0.5 * b_sum * np.conj(b_sum)).sum(dim="port")
- return AntennaMetricsData.from_directivity_data(
- combined_directivity_data, power_incident, power_reflected
- )
+TerminalComponentModeler.update_forward_refs()
diff --git a/tidy3d/plugins/smatrix/component_modelers/types.py b/tidy3d/plugins/smatrix/component_modelers/types.py
new file mode 100644
index 0000000000..2ca572e7c4
--- /dev/null
+++ b/tidy3d/plugins/smatrix/component_modelers/types.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+from typing import Union
+
+from .modal import ComponentModeler
+from .terminal import TerminalComponentModeler
+
+ComponentModelerType = Union[ComponentModeler, TerminalComponentModeler]
diff --git a/tidy3d/plugins/smatrix/data/data_array.py b/tidy3d/plugins/smatrix/data/data_array.py
new file mode 100644
index 0000000000..629a515189
--- /dev/null
+++ b/tidy3d/plugins/smatrix/data/data_array.py
@@ -0,0 +1,86 @@
+"""Storing data associated with results from the TerminalComponentModeler"""
+
+from __future__ import annotations
+
+import pydantic.v1 as pd
+
+from tidy3d.components.data.data_array import DataArray
+from tidy3d.log import log
+
+
+class PortDataArray(DataArray):
+ """Array of values over dimensions of frequency and port name.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> f = [2e9, 3e9, 4e9]
+ >>> ports = ["port1", "port2"]
+ >>> coords = dict(f=f, port=ports)
+ >>> data = (1+1j) * np.random.random((3, 2))
+ >>> pd = PortDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+ _dims = ("f", "port")
+
+ @pd.root_validator(pre=False)
+ def _warn_rf_license(cls, values):
+ log.warning(
+ "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.",
+ log_once=True,
+ )
+ return values
+
+
+class ModalPortDataArray(DataArray):
+ """Port parameter matrix elements for modal ports.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> ports_in = ['port1', 'port2']
+ >>> ports_out = ['port1', 'port2']
+ >>> mode_index_in = [0, 1]
+ >>> mode_index_out = [0, 1]
+ >>> f = [2e14]
+ >>> coords = dict(
+ ... port_in=ports_in,
+ ... port_out=ports_out,
+ ... mode_index_in=mode_index_in,
+ ... mode_index_out=mode_index_out,
+ ... f=f
+ ... )
+ >>> fd = ModalPortDataArray((1 + 1j) * np.random.random((2, 2, 2, 2, 1)), coords=coords)
+ """
+
+ __slots__ = ()
+ _dims = ("port_out", "mode_index_out", "port_in", "mode_index_in", "f")
+ _data_attrs = {"long_name": "modal port matrix element"}
+
+
+class TerminalPortDataArray(DataArray):
+ """Port parameter matrix elements for terminal-based ports.
+
+ Example
+ -------
+ >>> import numpy as np
+ >>> ports_in = ["port1", "port2"]
+ >>> ports_out = ["port1", "port2"]
+ >>> f = [2e14]
+ >>> coords = dict(f=f, port_out=ports_out, port_in=ports_in)
+ >>> data = (1+1j) * np.random.random((1, 2, 2))
+ >>> td = TerminalPortDataArray(data, coords=coords)
+ """
+
+ __slots__ = ()
+ _dims = ("f", "port_out", "port_in")
+ _data_attrs = {"long_name": "terminal-based port matrix element"}
+
+ @pd.root_validator(pre=False)
+ def _warn_rf_license(cls, values):
+ log.warning(
+ "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.",
+ log_once=True,
+ )
+ return values
diff --git a/tidy3d/plugins/smatrix/data/modal.py b/tidy3d/plugins/smatrix/data/modal.py
new file mode 100644
index 0000000000..de481ca79d
--- /dev/null
+++ b/tidy3d/plugins/smatrix/data/modal.py
@@ -0,0 +1,58 @@
+"""Tool for generating an S matrix automatically from a Tidy3d simulation and lumped port definitions."""
+
+from __future__ import annotations
+
+import pydantic.v1 as pd
+
+from tidy3d.components.base import Tidy3dBaseModel, cached_property
+from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.plugins.smatrix.component_modelers.modal import ComponentModeler
+from tidy3d.plugins.smatrix.data.data_array import ModalPortDataArray
+from tidy3d.plugins.smatrix.ports.types import PortReferenceType
+
+
+class PortSimulationData(Tidy3dBaseModel):
+ ports: tuple[PortReferenceType, ...]
+ data: tuple[SimulationData, ...]
+
+ def __getitem__(self, port_name: str) -> SimulationData:
+ """
+ Allows retrieving simulation data by the port name.
+
+ Args:
+ port_name: The string name of the port to look up.
+
+ Returns:
+ The SimulationData object corresponding to the given port name.
+
+ Raises:
+ KeyError: If no port with the given name is found.
+ """
+ for i, port_i in enumerate(self.ports):
+ if port_i == port_name:
+ return self.data[i]
+ raise KeyError(f"Port '{port_name}' not found.")
+
+
+class ComponentModelerData(Tidy3dBaseModel):
+ modeler: ComponentModeler = pd.Field(
+ ...,
+ title="ComponentModeler",
+ description="The original :class:`ComponentModeler` object that defines the simulation setup "
+ "and from which this data was generated.",
+ )
+
+ data: PortSimulationData = pd.Field(
+ ...,
+ title="ComponentModeler",
+ description="The original :class:`ComponentModeler` object that defines the simulation setup "
+ "and from which this data was generated.",
+ )
+
+ @cached_property
+ def smatrix(self) -> ModalPortDataArray:
+ "Stores the computed S-matrix and reference impedances for the terminal ports"
+ from tidy3d.plugins.smatrix.analysis.modal import modal_construct_smatrix
+
+ modal_port_data_array = modal_construct_smatrix(modeler_data=self)
+ return modal_port_data_array
diff --git a/tidy3d/plugins/smatrix/data/terminal.py b/tidy3d/plugins/smatrix/data/terminal.py
index 39917d6a46..ee0ddce34b 100644
--- a/tidy3d/plugins/smatrix/data/terminal.py
+++ b/tidy3d/plugins/smatrix/data/terminal.py
@@ -1,28 +1,103 @@
-"""Storing data associated with results from the TerminalComponentModeler"""
+"""Tool for generating an S matrix automatically from a Tidy3d simulation and lumped port definitions."""
from __future__ import annotations
+from typing import Literal, Optional, Union
+
+import numpy as np
import pydantic.v1 as pd
-from tidy3d.components.data.data_array import DataArray
+from tidy3d.components.base import Tidy3dBaseModel, cached_property
+from tidy3d.components.data.data_array import FreqDataArray
+from tidy3d.components.data.monitor_data import MonitorData
+from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData
from tidy3d.log import log
+from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
+from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray
+from tidy3d.plugins.smatrix.data.modal import PortSimulationData
+from tidy3d.plugins.smatrix.ports.types import TerminalPortType
+from tidy3d.plugins.smatrix.utils import (
+ ab_to_s,
+ check_port_impedance_sign,
+ compute_F,
+ compute_port_VI,
+ compute_power_delivered_by_port,
+ compute_power_wave_amplitudes,
+ s_to_z,
+)
+
+# The definition of wave amplitudes used to construct scattering matrix
+SParamDef = Literal["pseudo", "power"]
+
+
+class MicrowaveSMatrixData(Tidy3dBaseModel):
+ """Stores the computed S-matrix and reference impedances for the terminal ports."""
+
+ port_reference_impedances: Optional[PortDataArray] = pd.Field(
+ None,
+ title="Port Reference Impedances",
+ description="Reference impedance for each port used in the S-parameter calculation. This is optional and may not be present if not specified or computed.",
+ )
+ data: TerminalPortDataArray = pd.Field(
+ ...,
+ title="S-Matrix Data",
+ description="An array containing the computed S-matrix of the device. The data is organized by terminal ports, representing the scattering parameters between them.",
+ )
+
+ s_param_def: SParamDef = pd.Field(
+ "pseudo",
+ title="Scattering Parameter Definition",
+ description="Whether to scattering parameters are defined using the 'pseudo' or 'power' wave definitions.",
+ )
+
+
+class TerminalComponentModelerData(Tidy3dBaseModel):
+ """
+ Data associated with a :class:`TerminalComponentModeler` simulation run.
-class PortDataArray(DataArray):
- """Array of values over dimensions of frequency and port name.
+ This class serves as a data container for the results of a component modeler simulation,
+ with the original simulation definition, and port simulation data, and the solver log.
- Example
- -------
- >>> import numpy as np
- >>> f = [2e9, 3e9, 4e9]
- >>> ports = ["port1", "port2"]
- >>> coords = dict(f=f, port=ports)
- >>> data = (1+1j) * np.random.random((3, 2))
- >>> pd = PortDataArray(data, coords=coords)
+ See Also
+ --------
+ :func:`tidy3d.plugins.smatrix.utils.ab_to_s`
+ :func:`tidy3d.plugins.smatrix.utils.check_port_impedance_sign`
+ :func:`tidy3d.plugins.smatrix.utils.compute_F`
+ :func:`tidy3d.plugins.smatrix.utils.compute_port_VI`
+ :func:`tidy3d.plugins.smatrix.utils.compute_power_delivered_by_port`
+ :func:`tidy3d.plugins.smatrix.utils.compute_power_wave_amplitudes`
+ :func:`tidy3d.plugins.smatrix.utils.s_to_z`
"""
- __slots__ = ()
- _dims = ("f", "port")
+ modeler: TerminalComponentModeler = pd.Field(
+ ...,
+ title="TerminalComponentModeler",
+ description="The original :class:`TerminalComponentModeler` object that defines the simulation setup "
+ "and from which this data was generated.",
+ )
+
+ data: PortSimulationData = pd.Field(
+ ...,
+ title="Port-Simulation Data",
+ description="Stores raw simulation data from each microwave port-specific simulation.",
+ )
+
+ log: str = pd.Field(
+ None,
+ title="Solver Log",
+ description="A string containing the log information from the simulation run.",
+ )
+
+ @cached_property
+ def smatrix(self) -> MicrowaveSMatrixData:
+ "Stores the computed S-matrix and reference impedances for the terminal ports"
+ from tidy3d.plugins.smatrix.analysis.terminal import terminal_construct_smatrix
+
+ terminal_port_data = terminal_construct_smatrix(modeler_data=self)
+ smatrix_data = MicrowaveSMatrixData(data=terminal_port_data)
+ return smatrix_data
@pd.root_validator(pre=False)
def _warn_rf_license(cls, values):
@@ -32,29 +107,125 @@ def _warn_rf_license(cls, values):
)
return values
+ def _monitor_data_at_port_amplitude(
+ self,
+ port: TerminalPortType,
+ sim_data: SimulationData,
+ monitor_data: MonitorData,
+ a_port: Union[FreqDataArray, complex],
+ ) -> MonitorData:
+ """Normalize the monitor data to a desired complex amplitude of a port,
+ represented by ``a_port``, where :math:`\\frac{1}{2}|a|^2` is the power
+ incident from the port into the system.
+ """
+ a_raw, _ = self.compute_power_wave_amplitudes_at_each_port(
+ self.port_reference_impedances, sim_data=sim_data
+ )
+ a_raw_port = a_raw.sel(port=port.name)
+ if not isinstance(a_port, FreqDataArray):
+ freqs = list(monitor_data.monitor.freqs)
+ array_vals = a_port * np.ones(len(freqs))
+ a_port = FreqDataArray(array_vals, coords={"f": freqs})
+ scale_array = a_port / a_raw_port
+ return monitor_data.scale_fields_by_freq_array(scale_array, method="nearest")
-class TerminalPortDataArray(DataArray):
- """Port parameter matrix elements for terminal-based ports.
+ def get_antenna_metrics_data(
+ self,
+ port_amplitudes: Optional[dict[str, complex]] = None,
+ monitor_name: Optional[str] = None,
+ ) -> AntennaMetricsData:
+ """Calculate antenna parameters using superposition of fields from multiple port excitations.
- Example
- -------
- >>> import numpy as np
- >>> ports_in = ["port1", "port2"]
- >>> ports_out = ["port1", "port2"]
- >>> f = [2e14]
- >>> coords = dict(f=f, port_out=ports_out, port_in=ports_in)
- >>> data = (1+1j) * np.random.random((1, 2, 2))
- >>> td = TerminalPortDataArray(data, coords=coords)
- """
+ The method computes the radiated far fields and port excitation power wave amplitudes
+ for a superposition of port excitations, which can be used to analyze antenna radiation
+ characteristics.
- __slots__ = ()
- _dims = ("f", "port_out", "port_in")
- _data_attrs = {"long_name": "terminal-based port matrix element"}
+ Parameters
+ ----------
+ port_amplitudes : dict[str, complex] = None
+ Dictionary mapping port names to their desired excitation amplitudes. For each port,
+ :math:`\\frac{1}{2}|a|^2` represents the incident power from that port into the system.
+ If None, uses only the first port without any scaling of the raw simulation data.
+ monitor_name : str = None
+ Name of the :class:`.DirectivityMonitor` to use for calculating far fields.
+ If None, uses the first monitor in `radiation_monitors`.
- @pd.root_validator(pre=False)
- def _warn_rf_license(cls, values):
- log.warning(
- "ℹ️ ⚠️ RF simulations are subject to new license requirements in the future. You have instantiated at least one RF-specific component.",
- log_once=True,
+ Returns
+ -------
+ :class:`.AntennaMetricsData`
+ Container with antenna parameters including directivity, gain, and radiation efficiency,
+ computed from the superposition of fields from all excited ports.
+ """
+ from tidy3d.plugins.smatrix.analysis.antenna import get_antenna_metrics_data
+
+ antenna_metrics_data = get_antenna_metrics_data(
+ terminal_component_modeler_data=self,
+ port_amplitudes=port_amplitudes,
+ monitor_name=monitor_name,
)
- return values
+ return antenna_metrics_data
+
+ @cached_property
+ def port_reference_impedances(self) -> PortDataArray:
+ """Calculates the reference impedance for each port across all frequencies.
+
+ This function determines the characteristic impedance for every port defined
+ in the modeler. It handles two types of ports differently: for a
+ :class:`.WavePort`, the impedance is frequency-dependent and computed from
+ modal properties, while for other types like :class:`.LumpedPort`, the
+ impedance is a user-defined constant value.
+
+
+ Returns:
+ A data array containing the complex impedance for each port at each
+ frequency.
+ """
+ from tidy3d.plugins.smatrix.analysis.terminal import port_reference_impedances
+
+ return port_reference_impedances(self)
+
+ def compute_power_wave_amplitudes_at_each_port(
+ self, port_reference_impedances: PortDataArray, sim_data: SimulationData
+ ) -> tuple[PortDataArray, PortDataArray]:
+ """Computes power waves 'a' and 'b' at all ports from one simulation.
+
+ This function converts raw voltage (V) and current (I) at each port into
+ incident (a) and reflected (b) power wave amplitudes. The conversion uses
+ the formulas:
+ :math:`a = F (V + ZI)`
+ :math:`b = F (V - Z^*I)`
+ where :math:`Z` is the port impedance and :math:`F` is a normalization
+ factor. A sanity check ensures the real part of the reference impedance
+ is positive, flipping signs of V and Z if needed for physical consistency.
+
+ Args:
+ modeler: The modeler setup defining the ports.
+ port_reference_impedances: The characteristic impedance for each port
+ at each frequency.
+ sim_data: The raw results (fields, currents, etc.) from a single
+ simulation run with one port excited.
+
+ Returns:
+ A tuple containing two :class:`.PortDataArray` objects: the incident
+ power wave amplitudes 'a' and the reflected power wave amplitudes 'b'.
+ """
+ from tidy3d.plugins.smatrix.analysis.terminal import (
+ compute_power_wave_amplitudes_at_each_port,
+ )
+
+ data = compute_power_wave_amplitudes_at_each_port(
+ modeler=self.modeler,
+ port_reference_impedances=port_reference_impedances,
+ sim_data=sim_data,
+ )
+ return data
+
+ # Mirror Utils
+ # So they can be reused elsewhere without a class reimport
+ ab_to_s = ab_to_s
+ compute_F = compute_F
+ check_port_impedance_sign = check_port_impedance_sign
+ compute_port_VI = compute_port_VI
+ compute_power_wave_amplitudes = compute_power_wave_amplitudes
+ compute_power_delivered_by_port = compute_power_delivered_by_port
+ s_to_z = s_to_z
diff --git a/tidy3d/plugins/smatrix/data/types.py b/tidy3d/plugins/smatrix/data/types.py
new file mode 100644
index 0000000000..4f4488d6e7
--- /dev/null
+++ b/tidy3d/plugins/smatrix/data/types.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+from typing import Union
+
+from tidy3d.plugins.smatrix.data.modal import ComponentModelerData
+from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData
+
+ComponentModelerDataType = Union[TerminalComponentModelerData, ComponentModelerData]
diff --git a/tidy3d/plugins/smatrix/ports/modal.py b/tidy3d/plugins/smatrix/ports/modal.py
index eec551a80b..5a0c597eef 100644
--- a/tidy3d/plugins/smatrix/ports/modal.py
+++ b/tidy3d/plugins/smatrix/ports/modal.py
@@ -4,38 +4,11 @@
import pydantic.v1 as pd
-from tidy3d.components.data.data_array import DataArray
from tidy3d.components.geometry.base import Box
from tidy3d.components.mode_spec import ModeSpec
from tidy3d.components.types import Direction
-class ModalPortDataArray(DataArray):
- """Port parameter matrix elements for modal ports.
-
- Example
- -------
- >>> import numpy as np
- >>> ports_in = ['port1', 'port2']
- >>> ports_out = ['port1', 'port2']
- >>> mode_index_in = [0, 1]
- >>> mode_index_out = [0, 1]
- >>> f = [2e14]
- >>> coords = dict(
- ... port_in=ports_in,
- ... port_out=ports_out,
- ... mode_index_in=mode_index_in,
- ... mode_index_out=mode_index_out,
- ... f=f
- ... )
- >>> fd = ModalPortDataArray((1 + 1j) * np.random.random((2, 2, 2, 2, 1)), coords=coords)
- """
-
- __slots__ = ()
- _dims = ("port_out", "mode_index_out", "port_in", "mode_index_in", "f")
- _data_attrs = {"long_name": "modal port matrix element"}
-
-
class Port(Box):
"""Specifies a port in the scattering matrix."""
diff --git a/tidy3d/plugins/smatrix/ports/types.py b/tidy3d/plugins/smatrix/ports/types.py
new file mode 100644
index 0000000000..20b30c07de
--- /dev/null
+++ b/tidy3d/plugins/smatrix/ports/types.py
@@ -0,0 +1,11 @@
+from __future__ import annotations
+
+from typing import Union
+
+from tidy3d.plugins.smatrix.ports.coaxial_lumped import CoaxialLumpedPort
+from tidy3d.plugins.smatrix.ports.rectangular_lumped import LumpedPort
+from tidy3d.plugins.smatrix.ports.wave import WavePort
+
+LumpedPortType = Union[LumpedPort, CoaxialLumpedPort]
+TerminalPortType = Union[LumpedPortType, WavePort]
+PortReferenceType = Union[str, TerminalPortType] # TODO Debate this
diff --git a/tidy3d/plugins/smatrix/run.py b/tidy3d/plugins/smatrix/run.py
new file mode 100644
index 0000000000..6c7a17640c
--- /dev/null
+++ b/tidy3d/plugins/smatrix/run.py
@@ -0,0 +1,140 @@
+from __future__ import annotations
+
+import os
+
+from tidy3d.plugins.smatrix.component_modelers.modal import ComponentModeler
+from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
+from tidy3d.plugins.smatrix.component_modelers.types import (
+ ComponentModelerType,
+)
+from tidy3d.plugins.smatrix.data.modal import ComponentModelerData, PortSimulationData
+from tidy3d.plugins.smatrix.data.terminal import TerminalComponentModelerData
+from tidy3d.plugins.smatrix.data.types import ComponentModelerDataType
+from tidy3d.web import Batch, BatchData
+
+DEFAULT_DATA_DIR = "."
+
+
+def create_batch(
+ modeler: ComponentModelerType,
+ path_dir: str = DEFAULT_DATA_DIR,
+ file_name: str = "batch.hdf5",
+ **kwargs,
+) -> Batch:
+ """Creates a simulation Batch from a component modeler and saves it to a file.
+
+ Args:
+ modeler: The component modeler that defines the set of simulations.
+ path_dir: Directory where the batch file will be saved.
+ file_name: Name for the HDF5 file where the batch is stored.
+ **kwargs: Additional keyword arguments passed to the `Batch` constructor.
+
+ Returns:
+ The configured `Batch` object ready for execution.
+ """
+ filepath = os.path.join(path_dir, file_name)
+ batch = Batch(simulations=modeler.sim_dict, **kwargs)
+ batch.to_file(filepath)
+ return batch
+
+
+def compose_terminal_modeler_data(
+ modeler: TerminalComponentModeler,
+ batch_data: BatchData,
+) -> TerminalComponentModelerData:
+ """Assembles `TerminalComponentModelerData` from simulation results.
+
+ This function maps the simulation data from a completed batch run back to the
+ ports of the terminal component modeler.
+
+ Args:
+ modeler: The `TerminalComponentModeler` used to generate the simulations.
+ batch_data: The results obtained from running the simulation `Batch`.
+
+ Returns:
+ A `TerminalComponentModelerData` object containing the results mapped to
+ their respective ports.
+ """
+ ports = [modeler.get_task_name(port=port_i) for port_i in modeler.ports]
+ data = [batch_data[modeler.get_task_name(port=port_i)] for port_i in modeler.ports]
+ port_simulation_data = PortSimulationData(ports=ports, data=data)
+ return TerminalComponentModelerData(modeler=modeler, data=port_simulation_data)
+
+
+def compose_component_modeler_data(
+ modeler: ComponentModeler,
+ batch_data: BatchData,
+) -> ComponentModelerData:
+ """Assembles `ComponentModelerData` from simulation results.
+
+ This function maps the simulation data from a completed batch run back to the
+ ports of the component modeler.
+
+ Args:
+ modeler: The `ComponentModeler` used to generate the simulations.
+ batch_data: The results obtained from running the simulation `Batch`.
+
+ Returns:
+ A `ComponentModelerData` object containing the results mapped to
+ their respective ports.
+ """
+ ports = [modeler.get_task_name(port=port_i) for port_i in modeler.ports]
+ data = [batch_data[modeler.get_task_name(port=port_i)] for port_i in modeler.ports]
+ port_simulation_data = PortSimulationData(ports=ports, data=data)
+ return ComponentModelerData(modeler=modeler, data=port_simulation_data)
+
+
+def compose_modeler_data(
+ modeler: ComponentModelerType,
+ batch_data: BatchData,
+) -> ComponentModelerDataType:
+ """Selects the correct composer based on the modeler type and creates the data object.
+
+ This method acts as a dispatcher, inspecting the type of `modeler` to determine
+ which composer function (`compose_component_modeler_data` or
+ `compose_terminal_modeler_data`) to invoke.
+
+ Args:
+ modeler: The component modeler, which can be either a `ComponentModeler` or
+ a `TerminalComponentModeler`.
+ batch_data: The results obtained from running the simulation `Batch`.
+
+ Returns:
+ The appropriate `ComponentModelerDataType` object containing the simulation results.
+
+ Raises:
+ TypeError: If the provided `modeler` is not a recognized type.
+ """
+ if isinstance(modeler, ComponentModeler):
+ modeler_data = compose_component_modeler_data(modeler=modeler, batch_data=batch_data)
+ elif isinstance(modeler, TerminalComponentModeler):
+ modeler_data = compose_terminal_modeler_data(modeler=modeler, batch_data=batch_data)
+ else:
+ raise TypeError(f"Unsupported modeler type: {type(modeler).__name__}")
+
+ return modeler_data
+
+
+def run(
+ modeler: ComponentModelerType,
+ path_dir: str = DEFAULT_DATA_DIR,
+) -> ComponentModelerDataType:
+ """Executes the full simulation workflow for a given component modeler.
+
+ This function orchestrates the end-to-end process:
+ 1. Creates a `Batch` of simulations from the `modeler`.
+ 2. Submits the `Batch` for execution and waits for results.
+ 3. Composes the results into a structured `ComponentModelerDataType` object.
+
+ Args:
+ modeler: The component modeler defining the simulations to be run.
+ path_dir: The directory where the batch file will be saved.
+
+ Returns:
+ A `ComponentModelerDataType` object containing the processed simulation data,
+ ready for S-parameter extraction and analysis.
+ """
+ batch = create_batch(modeler=modeler, path_dir=path_dir)
+ batch_data = batch.run()
+ modeler_data = compose_modeler_data(modeler=modeler, batch_data=batch_data)
+ return modeler_data
diff --git a/tidy3d/plugins/smatrix/smatrix.py b/tidy3d/plugins/smatrix/smatrix.py
deleted file mode 100644
index 8d244171a4..0000000000
--- a/tidy3d/plugins/smatrix/smatrix.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# backwards compatibility support for ``from tidy3d.plugins.smatrix.smatrix import ``
-from __future__ import annotations
-
-from .component_modelers.modal import ComponentModeler
-from .ports.modal import Port
-
-__all__ = ["ComponentModeler", "Port"]
diff --git a/tidy3d/plugins/smatrix/utils.py b/tidy3d/plugins/smatrix/utils.py
new file mode 100644
index 0000000000..ac0d9af835
--- /dev/null
+++ b/tidy3d/plugins/smatrix/utils.py
@@ -0,0 +1,259 @@
+from __future__ import annotations
+
+from typing import Union
+
+import numpy as np
+
+from tidy3d.components.data.data_array import (
+ CurrentFreqDataArray,
+ CurrentFreqModeDataArray,
+ CurrentIntegralResultTypes,
+ CurrentTimeDataArray,
+ DataArray,
+ FreqDataArray,
+ FreqModeDataArray,
+ ImpedanceFreqDataArray,
+ ImpedanceFreqModeDataArray,
+ ImpedanceResultTypes,
+ ImpedanceTimeDataArray,
+ IntegralResultTypes,
+ TimeDataArray,
+ VoltageFreqDataArray,
+ VoltageFreqModeDataArray,
+ VoltageIntegralResultTypes,
+ VoltageTimeDataArray,
+)
+from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.exceptions import Tidy3dError
+from tidy3d.plugins.smatrix.data.data_array import PortDataArray, TerminalPortDataArray
+from tidy3d.plugins.smatrix.ports.types import LumpedPortType, TerminalPortType
+
+
+def port_array_inv(matrix: DataArray):
+ """Helper to invert a port matrix."""
+ return np.linalg.inv(matrix)
+
+
+def ab_to_s(
+ a_matrix: TerminalPortDataArray, b_matrix: TerminalPortDataArray
+) -> TerminalPortDataArray:
+ """Get the scattering matrix given the power wave matrices.
+
+ The scattering matrix S is computed from the incident (a) and reflected (b)
+ power wave amplitude matrices using the formula :math:`S = b * a^-1`.
+
+ Args:
+ a_matrix: Matrix of incident power wave amplitudes.
+ b_matrix: Matrix of reflected power wave amplitudes.
+
+ Returns:
+ The computed scattering (S) matrix.
+ """
+ # Ensure dimensions are ordered properly
+ a_matrix = a_matrix.transpose(*TerminalPortDataArray._dims)
+ b_matrix = b_matrix.transpose(*TerminalPortDataArray._dims)
+
+ s_matrix = a_matrix.copy(deep=True)
+ a_vals = s_matrix.copy(deep=True).values
+ b_vals = b_matrix.copy(deep=True).values
+
+ s_vals = np.matmul(b_vals, port_array_inv(a_vals))
+
+ s_matrix.data = s_vals
+ return s_matrix
+
+
+def check_port_impedance_sign(Z_numpy: np.ndarray):
+ """Sanity check for consistent sign of real part of Z for each port.
+
+ This check iterates through each port and ensures that the sign of the real
+ part of its impedance does not change across all frequencies. A sign change
+ can indicate an unphysical result or numerical instability.
+
+ Args:
+ Z_numpy: NumPy array of impedance values with shape (num_freqs, num_ports).
+
+ Raises:
+ Tidy3dError: If an inconsistent sign of the real part of the impedance
+ is detected for any port.
+ """
+ for port_idx in range(Z_numpy.shape[1]):
+ port_Z = Z_numpy[:, port_idx]
+ signs = np.sign(np.real(port_Z))
+ if not np.all(signs == signs[0]):
+ raise Tidy3dError(
+ f"Inconsistent sign of real part of Z detected for port {port_idx}. "
+ "If you received this error, please create an issue in the Tidy3D "
+ "github repository."
+ )
+
+
+def compute_F(Z_numpy: np.array):
+ r"""Helper to convert port impedance matrix to F for generalized S-parameters.
+
+ The matrix F is used when converting between S and Z parameters for circuits
+ with differing port impedances. Its diagonal elements are defined as
+ :math:`F_{kk} = 1 / (2 * \sqrt{Re(Z_k)})`.
+
+ Args:
+ Z_numpy: NumPy array of complex port impedances.
+
+ Returns:
+ NumPy array containing the computed F values.
+ """
+ return 1.0 / (2.0 * np.sqrt(np.real(Z_numpy)))
+
+
+def compute_port_VI(
+ port_out: TerminalPortType, sim_data: SimulationData
+) -> tuple[FreqDataArray, FreqDataArray]:
+ """Compute the port voltages and currents.
+
+ Parameters
+ ----------
+ port_out : ``TerminalPortType``
+ Port for computing voltage and current.
+ sim_data : :class:`.SimulationData`
+ Results from simulation containing field data.
+
+ Returns
+ -------
+ tuple[FreqDataArray, FreqDataArray]
+ Voltage and current values at the port as frequency arrays.
+ """
+ voltage = port_out.compute_voltage(sim_data)
+ current = port_out.compute_current(sim_data)
+ return voltage, current
+
+
+def compute_power_wave_amplitudes(
+ port: LumpedPortType, sim_data: SimulationData
+) -> tuple[FreqDataArray, FreqDataArray]:
+ """Calculates the unnormalized power wave amplitudes from port voltage (V),
+ current (I), and impedance (Z0) using:
+
+ .. math::
+ a = (V + Z0*I) / (2 * sqrt(Re(Z0)))
+ b = (V - Z0*I) / (2 * sqrt(Re(Z0)))
+
+ Parameters
+ ----------
+ port : :class:`.LumpedPortType`
+ Port for computing voltage and current.
+ sim_data : :class:`.SimulationData`
+ Results from the simulation.
+
+ Returns
+ -------
+ tuple[FreqDataArray, FreqDataArray]
+ Incident (a) and reflected (b) power wave amplitude frequency arrays.
+ """
+ voltage, current = compute_port_VI(port, sim_data)
+ # Amplitudes for the incident and reflected power waves
+ a = (voltage + port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
+ b = (voltage - port.impedance * current) / 2 / np.sqrt(np.real(port.impedance))
+ return a, b
+
+
+def compute_power_delivered_by_port(
+ port: LumpedPortType, sim_data: SimulationData
+) -> FreqDataArray:
+ """Compute the power delivered to the network by a lumped port.
+
+ The power is calculated as the incident power minus the reflected power:
+ P = 0.5 * (|a|^2 - |b|^2).
+
+ Parameters
+ ----------
+ port : :class:`.LumpedPortType`
+ Port for computing voltage and current.
+ sim_data : :class:`.SimulationData`
+ Results from the simulation.
+
+ Returns
+ -------
+ FreqDataArray
+ Power in units of Watts as a frequency array.
+ """
+ a, b = compute_power_wave_amplitudes(sim_data=sim_data, port=port)
+ # Power delivered is the incident power minus the reflected power
+ return 0.5 * (np.abs(a) ** 2 - np.abs(b) ** 2)
+
+
+def s_to_z(s_matrix: TerminalPortDataArray, reference: Union[complex, PortDataArray]) -> DataArray:
+ """Get the impedance matrix given the scattering matrix and a reference impedance.
+
+ This function converts an S-matrix to a Z-matrix. It handles both a single
+ uniform reference impedance and generalized per-port reference impedances.
+
+ Args:
+ s_matrix: The scattering (S) matrix to convert.
+ reference: The reference impedance. Can be a single complex value for all
+ ports or a PortDataArray for per-port impedances.
+
+ Returns:
+ The computed impedance (Z) matrix as a DataArray.
+ """
+
+ # Ensure dimensions are ordered properly
+ z_matrix = s_matrix.transpose(*TerminalPortDataArray._dims).copy(deep=True)
+ s_vals = z_matrix.values
+ eye = np.eye(len(s_matrix.port_out.values), len(s_matrix.port_in.values))
+ if isinstance(reference, PortDataArray):
+ # From Equation 4.68 - Pozar - Microwave Engineering 4ed
+ # Ensure that Zport, F, and Finv act as diagonal matrices when multiplying by left or right
+ shape_left = (len(s_matrix.f), len(s_matrix.port_out), 1)
+ shape_right = (len(s_matrix.f), 1, len(s_matrix.port_in))
+ Zport = reference.values.reshape(shape_right)
+ F = compute_F(Zport).reshape(shape_right)
+ Finv = (1.0 / F).reshape(shape_left)
+ FinvSF = Finv * s_vals * F
+ RHS = eye * np.conj(Zport) + FinvSF * Zport
+ LHS = eye - FinvSF
+ z_vals = np.matmul(port_array_inv(LHS), RHS)
+ else:
+ # Simpler case when all port impedances are the same
+ z_vals = np.matmul(port_array_inv(eye - s_vals), (eye + s_vals)) * reference
+
+ z_matrix.data = z_vals
+ return z_matrix
+
+
+def _make_base_result_data_array(result: DataArray) -> IntegralResultTypes:
+ """Helper for creating the proper base result type."""
+ cls = FreqDataArray
+ if "t" in result.coords:
+ cls = TimeDataArray
+ if "f" in result.coords and "mode_index" in result.coords:
+ cls = FreqModeDataArray
+ return cls.assign_data_attrs(cls(data=result.data, coords=result.coords))
+
+
+def _make_voltage_data_array(result: DataArray) -> CurrentIntegralResultTypes:
+ """Helper for creating the proper voltage array type."""
+ cls = CurrentFreqDataArray
+ if "t" in result.coords:
+ cls = CurrentTimeDataArray
+ if "f" in result.coords and "mode_index" in result.coords:
+ cls = CurrentFreqModeDataArray
+ return cls.assign_data_attrs(cls(data=result.data, coords=result.coords))
+
+
+def _make_current_data_array(result: DataArray) -> VoltageIntegralResultTypes:
+ """Helper for creating the proper current array type."""
+ cls = VoltageFreqDataArray
+ if "t" in result.coords:
+ cls = VoltageTimeDataArray
+ if "f" in result.coords and "mode_index" in result.coords:
+ cls = VoltageFreqModeDataArray
+ return cls.assign_data_attrs(cls(data=result.data, coords=result.coords))
+
+
+def _make_impedance_data_array(result: DataArray) -> ImpedanceResultTypes:
+ """Helper for creating the proper impedance array type."""
+ cls = ImpedanceFreqDataArray
+ if "t" in result.coords:
+ cls = ImpedanceTimeDataArray
+ if "f" in result.coords and "mode_index" in result.coords:
+ cls = ImpedanceFreqModeDataArray
+ return cls.assign_data_attrs(cls(data=result.data, coords=result.coords))
diff --git a/tidy3d/plugins/smatrix/web/__init__.py b/tidy3d/plugins/smatrix/web/__init__.py
new file mode 100644
index 0000000000..ff51cf9f3b
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/__init__.py
@@ -0,0 +1,69 @@
+# ruff: noqa: E402
+"""imports interfaces for interacting with server"""
+
+from __future__ import annotations
+
+from tidy3d.log import get_logging_console, log
+from tidy3d.version import __version__
+
+from .core import core_config
+
+# set logger to tidy3d.log before it's invoked in other imports
+core_config.set_config(log, get_logging_console(), __version__)
+
+# from .api.asynchronous import run_async # NOTE: we use autograd one now (see below)
+# autograd compatible wrappers for run and run_async
+from .api.autograd.autograd import run, run_async
+from .api.container import Batch, BatchData, Job
+from .api.webapi import (
+ abort,
+ account,
+ delete,
+ delete_old,
+ download,
+ download_json,
+ download_log,
+ estimate_cost,
+ get_info,
+ get_tasks,
+ load,
+ load_simulation,
+ monitor,
+ real_cost,
+ start,
+ test,
+ # run, # NOTE: use autograd one now (see below)
+ upload,
+)
+from .cli import tidy3d_cli
+from .cli.app import configure_fn as configure
+from .cli.migrate import migrate
+
+migrate()
+
+__all__ = [
+ "Batch",
+ "BatchData",
+ "Job",
+ "abort",
+ "account",
+ "configure",
+ "delete",
+ "delete_old",
+ "download",
+ "download_json",
+ "download_log",
+ "estimate_cost",
+ "get_info",
+ "get_tasks",
+ "load",
+ "load_simulation",
+ "monitor",
+ "real_cost",
+ "run",
+ "run_async",
+ "start",
+ "test",
+ "tidy3d_cli",
+ "upload",
+]
diff --git a/tidy3d/plugins/smatrix/web/api/__init__.py b/tidy3d/plugins/smatrix/web/api/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tidy3d/plugins/smatrix/web/api/asynchronous.py b/tidy3d/plugins/smatrix/web/api/asynchronous.py
new file mode 100644
index 0000000000..91eacb5f18
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/asynchronous.py
@@ -0,0 +1,88 @@
+"""Interface to run several jobs in batch using simplified syntax."""
+
+from __future__ import annotations
+
+from typing import Literal, Optional, Union
+
+from tidy3d.log import log
+from tidy3d.web.core.types import PayType
+
+from .container import DEFAULT_DATA_DIR, Batch, BatchData
+from .tidy3d_stub import SimulationType
+
+
+def run_async(
+ simulations: dict[str, SimulationType],
+ folder_name: str = "default",
+ path_dir: str = DEFAULT_DATA_DIR,
+ callback_url: Optional[str] = None,
+ num_workers: Optional[int] = None,
+ verbose: bool = True,
+ simulation_type: str = "tidy3d",
+ parent_tasks: Optional[dict[str, list[str]]] = None,
+ reduce_simulation: Literal["auto", True, False] = "auto",
+ pay_type: Union[PayType, str] = PayType.AUTO,
+) -> BatchData:
+ """Submits a set of Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] objects to server,
+ starts running, monitors progress, downloads, and loads results as a :class:`.BatchData` object.
+
+ .. TODO add example and see also reference.
+
+ Parameters
+ ----------
+ simulations : Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]
+ Mapping of task name to simulation.
+ folder_name : str = "default"
+ Name of folder to store each task on web UI.
+ path_dir : str
+ Base directory where data will be downloaded, by default current working directory.
+ callback_url : str = None
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ num_workers: int = None
+ Number of tasks to submit at once in a batch, if None, will run all at the same time.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ reduce_simulation: Literal["auto", True, False] = "auto"
+ Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.
+ pay_type: Union[PayType, str] = PayType.AUTO
+ Specify the payment method.
+
+ Returns
+ ------
+ :class:`BatchData`
+ Contains the Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] for each
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] in :class:`Batch`.
+
+ See Also
+ --------
+
+ :class:`Job`:
+ Interface for managing the running of a Simulation on server.
+
+ :class:`Batch`
+ Interface for submitting several :class:`Simulation` objects to sever.
+ """
+ if simulation_type is None:
+ simulation_type = "tidy3d"
+
+ # if number of workers not specified, just use the number of simulations
+ if num_workers is not None:
+ log.warning(
+ "The 'num_workers' kwarg does not have an effect anymore as all "
+ "simulations will now be uploaded in a single batch."
+ )
+
+ batch = Batch(
+ simulations=simulations,
+ folder_name=folder_name,
+ callback_url=callback_url,
+ verbose=verbose,
+ simulation_type=simulation_type,
+ parent_tasks=parent_tasks,
+ reduce_simulation=reduce_simulation,
+ pay_type=pay_type,
+ )
+
+ batch_data = batch.run(path_dir=path_dir)
+ return batch_data
diff --git a/tidy3d/plugins/smatrix/web/api/autograd/__init__.py b/tidy3d/plugins/smatrix/web/api/autograd/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tidy3d/plugins/smatrix/web/api/autograd/autograd.py b/tidy3d/plugins/smatrix/web/api/autograd/autograd.py
new file mode 100644
index 0000000000..8e059cadeb
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/autograd/autograd.py
@@ -0,0 +1,1275 @@
+# autograd wrapper for web functions
+from __future__ import annotations
+
+import os
+import tempfile
+import typing
+from collections import defaultdict
+from os.path import basename, dirname, join
+from pathlib import Path
+
+import numpy as np
+import xarray as xr
+from autograd.builtins import dict as dict_ag
+from autograd.extend import defvjp, primitive
+
+import tidy3d as td
+from tidy3d.components.autograd import AutogradFieldMap, get_static
+from tidy3d.components.autograd.constants import (
+ ADJOINT_FREQ_CHUNK_SIZE,
+ MAX_NUM_ADJOINT_PER_FWD,
+ MAX_NUM_TRACED_STRUCTURES,
+)
+from tidy3d.components.autograd.derivative_utils import DerivativeInfo
+from tidy3d.components.data.data_array import DataArray
+from tidy3d.exceptions import AdjointError
+from tidy3d.web.api.asynchronous import DEFAULT_DATA_DIR
+from tidy3d.web.api.asynchronous import run_async as run_async_webapi
+from tidy3d.web.api.container import DEFAULT_DATA_PATH, Batch, BatchData, Job
+from tidy3d.web.api.tidy3d_stub import SimulationDataType, SimulationType
+from tidy3d.web.api.webapi import run as run_webapi
+from tidy3d.web.core.s3utils import download_file, upload_file
+from tidy3d.web.core.types import PayType
+
+from .utils import E_to_D, FieldMap, TracerKeys, get_derivative_maps
+
+# keys for data into auxiliary dictionary
+AUX_KEY_SIM_DATA_ORIGINAL = "sim_data"
+AUX_KEY_SIM_DATA_FWD = "sim_data_fwd_adjoint"
+AUX_KEY_FWD_TASK_ID = "task_id_fwd"
+AUX_KEY_SIM_ORIGINAL = "sim_original"
+# server-side auxiliary files to upload/download
+SIM_VJP_FILE = "output/autograd_sim_vjp.hdf5"
+SIM_FIELDS_KEYS_FILE = "autograd_sim_fields_keys.hdf5"
+
+# default value for whether to do local gradient calculation (True) or server side (False)
+LOCAL_GRADIENT = False
+
+# directory to store adjoint data for local gradient calculation relative to run path
+LOCAL_ADJOINT_DIR = "adjoint_data"
+
+# if True, will plot the adjoint fields on the plane provided. used for debugging only
+_INSPECT_ADJOINT_FIELDS = False
+_INSPECT_ADJOINT_PLANE = td.Box(center=(0, 0, 0), size=(td.inf, td.inf, 0))
+
+
+def is_valid_for_autograd(simulation: td.Simulation) -> bool:
+ """Check whether a supplied simulation can use autograd run."""
+
+ # only support Simulations
+ if not isinstance(simulation, td.Simulation):
+ return False
+
+ # if no tracers just use regular web.run()
+ traced_fields = simulation._strip_traced_fields(
+ include_untraced_data_arrays=False, starting_path=("structures",)
+ )
+ if not traced_fields:
+ return False
+
+ # if no frequency-domain data (e.g. only field time monitors), raise an error
+ if not simulation._freqs_adjoint:
+ raise AdjointError(
+ "No frequency-domain data found in simulation, but found traced structures. "
+ "For an autograd run, you must have at least one frequency-domain monitor."
+ )
+
+ # if too many structures, raise an error
+ structure_indices = {i for key, i, *_ in traced_fields.keys() if key == "structures"}
+ num_traced_structures = len(structure_indices)
+ if num_traced_structures > MAX_NUM_TRACED_STRUCTURES:
+ raise AdjointError(
+ f"Autograd support is currently limited to {MAX_NUM_TRACED_STRUCTURES} structures with "
+ f"traced fields. Found {num_traced_structures} structures with traced fields."
+ )
+
+ return True
+
+
+def is_valid_for_autograd_async(simulations: dict[str, td.Simulation]) -> bool:
+ """Check whether the supplied simulations dict can use autograd run_async."""
+ if not isinstance(simulations, dict):
+ return False
+ if not all(is_valid_for_autograd(sim) for sim in simulations.values()):
+ return False
+ return True
+
+
+def run(
+ simulation: SimulationType,
+ task_name: str,
+ folder_name: str = "default",
+ path: str = "simulation_data.hdf5",
+ callback_url: typing.Optional[str] = None,
+ verbose: bool = True,
+ progress_callback_upload: typing.Optional[typing.Callable[[float], None]] = None,
+ progress_callback_download: typing.Optional[typing.Callable[[float], None]] = None,
+ solver_version: typing.Optional[str] = None,
+ worker_group: typing.Optional[str] = None,
+ simulation_type: str = "tidy3d",
+ parent_tasks: typing.Optional[list[str]] = None,
+ local_gradient: bool = LOCAL_GRADIENT,
+ max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD,
+ reduce_simulation: typing.Literal["auto", True, False] = "auto",
+ pay_type: typing.Union[PayType, str] = PayType.AUTO,
+ priority: typing.Optional[int] = None,
+) -> SimulationDataType:
+ """
+ Submits a :class:`.Simulation` to server, starts running, monitors progress, downloads,
+ and loads results as a :class:`.SimulationDataType` object.
+
+ Parameters
+ ----------
+ simulation : Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ Simulation to upload to server.
+ task_name : str
+ Name of task.
+ folder_name : str = "default"
+ Name of folder to store task on web UI.
+ path : str = "simulation_data.hdf5"
+ Path to download results file (.hdf5), including filename.
+ callback_url : str = None
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ simulation_type : str = "tidy3d"
+ Type of simulation being uploaded.
+ progress_callback_upload : Callable[[float], None] = None
+ Optional callback function called when uploading file with ``bytes_in_chunk`` as argument.
+ progress_callback_download : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+ solver_version: str = None
+ target solver version.
+ worker_group: str = None
+ worker group
+ local_gradient: bool = False
+ Whether to perform gradient calculation locally, requiring more downloads but potentially
+ more stable with experimental features.
+ max_num_adjoint_per_fwd: int = 10
+ Maximum number of adjoint simulations allowed to run automatically.
+ reduce_simulation: Literal["auto", True, False] = "auto"
+ Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.
+ pay_type: typing.Union[PayType, str] = PayType.AUTO
+ Which method to pay for the simulation.
+ priority: int = None
+ Task priority for vGPU queue (1=lowest, 10=highest).
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ Object containing solver results for the supplied simulation.
+
+ Notes
+ -----
+
+ Submitting a simulation to our cloud server is very easily done by a simple web API call.
+
+ .. code-block:: python
+
+ sim_data = tidy3d.web.api.webapi.run(simulation, task_name='my_task', path='out/data.hdf5')
+
+ The :meth:`tidy3d.web.api.webapi.run()` method shows the simulation progress by default. When uploading a
+ simulation to the server without running it, you can use the :meth:`tidy3d.web.api.webapi.monitor`,
+ :meth:`tidy3d.web.api.container.Job.monitor`, or :meth:`tidy3d.web.api.container.Batch.monitor` methods to
+ display the progress of your simulation(s).
+
+ Examples
+ --------
+
+ To access the original :class:`.Simulation` object that created the simulation data you can use:
+
+ .. code-block:: python
+
+ # Run the simulation.
+ sim_data = web.run(simulation, task_name='task_name', path='out/sim.hdf5')
+
+ # Get a copy of the original simulation object.
+ sim_copy = sim_data.simulation
+
+ See Also
+ --------
+
+ :meth:`tidy3d.web.api.webapi.monitor`
+ Print the real time task progress until completion.
+
+ :meth:`tidy3d.web.api.container.Job.monitor`
+ Monitor progress of running :class:`Job`.
+
+ :meth:`tidy3d.web.api.container.Batch.monitor`
+ Monitor progress of each of the running tasks.
+ """
+ if priority is not None and (priority < 1 or priority > 10):
+ raise ValueError("Priority must be between '1' and '10' if specified.")
+ if is_valid_for_autograd(simulation):
+ return _run(
+ simulation=simulation,
+ task_name=task_name,
+ folder_name=folder_name,
+ path=path,
+ callback_url=callback_url,
+ verbose=verbose,
+ progress_callback_upload=progress_callback_upload,
+ progress_callback_download=progress_callback_download,
+ solver_version=solver_version,
+ worker_group=worker_group,
+ simulation_type="tidy3d_autograd",
+ parent_tasks=parent_tasks,
+ local_gradient=local_gradient,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ pay_type=pay_type,
+ )
+
+ return run_webapi(
+ simulation=simulation,
+ task_name=task_name,
+ folder_name=folder_name,
+ path=path,
+ callback_url=callback_url,
+ verbose=verbose,
+ progress_callback_upload=progress_callback_upload,
+ progress_callback_download=progress_callback_download,
+ solver_version=solver_version,
+ worker_group=worker_group,
+ simulation_type=simulation_type,
+ parent_tasks=parent_tasks,
+ reduce_simulation=reduce_simulation,
+ pay_type=pay_type,
+ priority=priority,
+ )
+
+
+def run_async(
+ simulations: dict[str, SimulationType],
+ folder_name: str = "default",
+ path_dir: str = DEFAULT_DATA_DIR,
+ callback_url: typing.Optional[str] = None,
+ num_workers: typing.Optional[int] = None,
+ verbose: bool = True,
+ simulation_type: str = "tidy3d",
+ parent_tasks: typing.Optional[dict[str, list[str]]] = None,
+ local_gradient: bool = LOCAL_GRADIENT,
+ max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD,
+ reduce_simulation: typing.Literal["auto", True, False] = "auto",
+ pay_type: typing.Union[PayType, str] = PayType.AUTO,
+) -> BatchData:
+ """Submits a set of Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] objects to server,
+ starts running, monitors progress, downloads, and loads results as a :class:`.BatchData` object.
+
+ .. TODO add example and see also reference.
+
+ Parameters
+ ----------
+ simulations : Dict[str, Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]]
+ Mapping of task name to simulation.
+ folder_name : str = "default"
+ Name of folder to store each task on web UI.
+ path_dir : str
+ Base directory where data will be downloaded, by default current working directory.
+ callback_url : str = None
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ num_workers: int = None
+ Number of tasks to submit at once in a batch, if None, will run all at the same time.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ local_gradient: bool = False
+ Whether to perform gradient calculations locally, requiring more downloads but potentially
+ more stable with experimental features.
+ max_num_adjoint_per_fwd: int = 10
+ Maximum number of adjoint simulations allowed to run automatically.
+ reduce_simulation: Literal["auto", True, False] = "auto"
+ Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.
+ pay_type: typing.Union[PayType, str] = PayType.AUTO
+ Specify the payment method.
+
+ Returns
+ ------
+ :class:`BatchData`
+ Contains the Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] for each
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] in :class:`Batch`.
+
+ See Also
+ --------
+
+ :class:`Job`:
+ Interface for managing the running of a Simulation on server.
+
+ :class:`Batch`
+ Interface for submitting several :class:`Simulation` objects to sever.
+ """
+ if is_valid_for_autograd_async(simulations):
+ return _run_async(
+ simulations=simulations,
+ folder_name=folder_name,
+ path_dir=path_dir,
+ callback_url=callback_url,
+ num_workers=num_workers,
+ verbose=verbose,
+ simulation_type="tidy3d_autograd_async",
+ parent_tasks=parent_tasks,
+ local_gradient=local_gradient,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ pay_type=pay_type,
+ )
+
+ return run_async_webapi(
+ simulations=simulations,
+ folder_name=folder_name,
+ path_dir=path_dir,
+ callback_url=callback_url,
+ num_workers=num_workers,
+ verbose=verbose,
+ simulation_type=simulation_type,
+ parent_tasks=parent_tasks,
+ reduce_simulation=reduce_simulation,
+ pay_type=pay_type,
+ )
+
+
+""" User-facing ``run`` and `run_async`` functions, compatible with ``autograd`` """
+
+
+def _run(
+ simulation: td.Simulation,
+ task_name: str,
+ local_gradient: bool = LOCAL_GRADIENT,
+ max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD,
+ **run_kwargs,
+) -> td.SimulationData:
+ """User-facing ``web.run`` function, compatible with ``autograd`` differentiation."""
+
+ traced_fields_sim = setup_run(simulation=simulation)
+
+ # if we register this as not needing adjoint at all (no tracers), call regular run function
+ if not traced_fields_sim:
+ td.log.warning(
+ "No autograd derivative tracers found in the 'Simulation' passed to 'run'. "
+ "This could indicate that there is no path from your objective function arguments "
+ "to the 'Simulation'. If this is unexpected, double check your objective function "
+ "pre-processing. Running regular tidy3d simulation."
+ )
+ data, _ = _run_tidy3d(simulation, task_name=task_name, **run_kwargs)
+ return data
+
+ # will store the SimulationData for original and forward so we can access them later
+ aux_data = {}
+
+ # run our custom @primitive, passing the traced fields first to register with autograd
+ traced_fields_data = _run_primitive(
+ traced_fields_sim, # if you pass as a kwarg it will not trace :/
+ sim_original=simulation.to_static(),
+ task_name=task_name,
+ aux_data=aux_data,
+ local_gradient=local_gradient,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ **run_kwargs,
+ )
+
+ return postprocess_run(traced_fields_data=traced_fields_data, aux_data=aux_data)
+
+
+def _run_async(
+ simulations: dict[str, td.Simulation],
+ local_gradient: bool = LOCAL_GRADIENT,
+ max_num_adjoint_per_fwd: int = MAX_NUM_ADJOINT_PER_FWD,
+ **run_async_kwargs,
+) -> dict[str, td.SimulationData]:
+ """User-facing ``web.run_async`` function, compatible with ``autograd`` differentiation."""
+
+ task_names = simulations.keys()
+
+ traced_fields_sim_dict = {}
+ for task_name in task_names:
+ traced_fields_sim_dict[task_name] = setup_run(simulation=simulations[task_name])
+ traced_fields_sim_dict = dict_ag(traced_fields_sim_dict)
+
+ # TODO: shortcut primitive running for any items with no tracers?
+
+ aux_data_dict = {task_name: {} for task_name in task_names}
+ sims_original = {
+ task_name: simulation.to_static() for task_name, simulation in simulations.items()
+ }
+ traced_fields_data_dict = _run_async_primitive(
+ traced_fields_sim_dict, # if you pass as a kwarg it will not trace :/
+ sims_original=sims_original,
+ aux_data_dict=aux_data_dict,
+ local_gradient=local_gradient,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ **run_async_kwargs,
+ )
+
+ # TODO: package this as a Batch? it might be not possible as autograd tracers lose their
+ # powers when we save them to file.
+ sim_data_dict = {}
+ for task_name in task_names:
+ traced_fields_data = traced_fields_data_dict[task_name]
+ aux_data = aux_data_dict[task_name]
+ sim_data = postprocess_run(traced_fields_data=traced_fields_data, aux_data=aux_data)
+ sim_data_dict[task_name] = sim_data
+
+ return sim_data_dict
+
+
+def setup_run(simulation: td.Simulation) -> AutogradFieldMap:
+ """Process a user-supplied ``Simulation`` into inputs to ``_run_primitive``."""
+
+ # get a mapping of all the traced fields in the provided simulation
+ return simulation._strip_traced_fields(
+ include_untraced_data_arrays=False, starting_path=("structures",)
+ )
+
+
+def postprocess_run(traced_fields_data: AutogradFieldMap, aux_data: dict) -> td.SimulationData:
+ """Process the return from ``_run_primitive`` into ``SimulationData`` for user."""
+
+ # grab the user's 'SimulationData' and return with the autograd-tracers inserted
+ sim_data_original = aux_data[AUX_KEY_SIM_DATA_ORIGINAL]
+ return sim_data_original._insert_traced_fields(traced_fields_data)
+
+
+""" Autograd-traced Primitive for FWD pass ``run`` functions """
+
+
+@primitive
+def _run_primitive(
+ sim_fields: AutogradFieldMap,
+ sim_original: td.Simulation,
+ task_name: str,
+ aux_data: dict,
+ local_gradient: bool,
+ max_num_adjoint_per_fwd: int,
+ **run_kwargs,
+) -> AutogradFieldMap:
+ """Autograd-traced 'run()' function: runs simulation, strips tracer data, caches fwd data."""
+
+ td.log.info("running primitive '_run_primitive()'")
+
+ # indicate this is a forward run. not exposed to user but used internally by pipeline.
+ run_kwargs["is_adjoint"] = False
+
+ # compute the combined simulation for both local and remote, so we can validate it
+ sim_combined = setup_fwd(
+ sim_fields=sim_fields,
+ sim_original=sim_original,
+ local_gradient=local_gradient,
+ )
+
+ if local_gradient:
+ sim_data_combined, _ = _run_tidy3d(sim_combined, task_name=task_name, **run_kwargs)
+
+ field_map = postprocess_fwd(
+ sim_data_combined=sim_data_combined,
+ sim_original=sim_original,
+ aux_data=aux_data,
+ )
+ else:
+ sim_combined.validate_pre_upload()
+ sim_original = sim_original.updated_copy(simulation_type="autograd_fwd", deep=False)
+ run_kwargs["simulation_type"] = "autograd_fwd"
+ run_kwargs["sim_fields_keys"] = list(sim_fields.keys())
+
+ sim_data_orig, task_id_fwd = _run_tidy3d(
+ sim_original,
+ task_name=task_name,
+ **run_kwargs,
+ )
+
+ # TODO: put this in postprocess?
+ aux_data[AUX_KEY_FWD_TASK_ID] = task_id_fwd
+ aux_data[AUX_KEY_SIM_DATA_ORIGINAL] = sim_data_orig
+ field_map = sim_data_orig._strip_traced_fields(
+ include_untraced_data_arrays=True, starting_path=("data",)
+ )
+
+ return field_map
+
+
+@primitive
+def _run_async_primitive(
+ sim_fields_dict: dict[str, AutogradFieldMap],
+ sims_original: dict[str, td.Simulation],
+ aux_data_dict: dict[dict[str, typing.Any]],
+ local_gradient: bool,
+ max_num_adjoint_per_fwd: int,
+ **run_async_kwargs,
+) -> dict[str, AutogradFieldMap]:
+ task_names = sim_fields_dict.keys()
+
+ sims_combined = {}
+ for task_name in task_names:
+ sim_fields = sim_fields_dict[task_name]
+ sim_original = sims_original[task_name]
+ sims_combined[task_name] = setup_fwd(
+ sim_fields=sim_fields,
+ sim_original=sim_original,
+ local_gradient=local_gradient,
+ )
+
+ if local_gradient:
+ batch_data_combined, _ = _run_async_tidy3d(sims_combined, **run_async_kwargs)
+
+ field_map_fwd_dict = {}
+ for task_name in task_names:
+ sim_data_combined = batch_data_combined[task_name]
+ sim_original = sims_original[task_name]
+ aux_data = aux_data_dict[task_name]
+ field_map_fwd_dict[task_name] = postprocess_fwd(
+ sim_data_combined=sim_data_combined,
+ sim_original=sim_original,
+ aux_data=aux_data,
+ )
+ else:
+ for sim in sims_combined.values():
+ sim.validate_pre_upload()
+ run_async_kwargs["simulation_type"] = "autograd_fwd"
+ run_async_kwargs["sim_fields_keys_dict"] = {}
+ for task_name, sim_fields in sim_fields_dict.items():
+ run_async_kwargs["sim_fields_keys_dict"][task_name] = list(sim_fields.keys())
+
+ sims_original = {
+ task_name: sim.updated_copy(simulation_type="autograd_fwd", deep=False)
+ for task_name, sim in sims_original.items()
+ }
+
+ sim_data_orig_dict, task_ids_fwd_dict = _run_async_tidy3d(
+ sims_original,
+ **run_async_kwargs,
+ )
+
+ field_map_fwd_dict = {}
+ for task_name, task_id_fwd in task_ids_fwd_dict.items():
+ sim_data_orig = sim_data_orig_dict[task_name]
+ aux_data_dict[task_name][AUX_KEY_FWD_TASK_ID] = task_id_fwd
+ aux_data_dict[task_name][AUX_KEY_SIM_DATA_ORIGINAL] = sim_data_orig
+ field_map = sim_data_orig._strip_traced_fields(
+ include_untraced_data_arrays=True, starting_path=("data",)
+ )
+ field_map_fwd_dict[task_name] = field_map
+
+ return field_map_fwd_dict
+
+
+def setup_fwd(
+ sim_fields: AutogradFieldMap,
+ sim_original: td.Simulation,
+ local_gradient: bool = LOCAL_GRADIENT,
+) -> td.Simulation:
+ """Return a forward simulation with adjoint monitors attached."""
+
+ # Always try to build the variant that includes adjoint monitors so that
+ # errors in monitor placement are caught early.
+ sim_with_adj_mon = sim_original._with_adjoint_monitors(sim_fields)
+ return sim_with_adj_mon if local_gradient else sim_original
+
+
+def postprocess_fwd(
+ sim_data_combined: td.SimulationData,
+ sim_original: td.Simulation,
+ aux_data: dict,
+) -> AutogradFieldMap:
+ """Postprocess the combined simulation data into an Autograd field map."""
+
+ num_mnts_original = len(sim_original.monitors)
+ sim_data_original, sim_data_fwd = sim_data_combined._split_original_fwd(
+ num_mnts_original=num_mnts_original
+ )
+
+ aux_data[AUX_KEY_SIM_DATA_ORIGINAL] = sim_data_original
+ aux_data[AUX_KEY_SIM_DATA_FWD] = sim_data_fwd
+
+ # strip out the tracer AutogradFieldMap for the .data from the original sim
+ data_traced = sim_data_original._strip_traced_fields(
+ include_untraced_data_arrays=True, starting_path=("data",)
+ )
+
+ # return the AutogradFieldMap that autograd registers as the "output" of the primitive
+ return data_traced
+
+
+def upload_sim_fields_keys(sim_fields_keys: list[tuple], task_id: str, verbose: bool = False):
+ """Function to grab the VJP result for the simulation fields from the adjoint task ID."""
+ handle, fname = tempfile.mkstemp(suffix=".hdf5")
+ os.close(handle)
+ try:
+ TracerKeys(keys=sim_fields_keys).to_file(fname)
+ upload_file(
+ task_id,
+ fname,
+ SIM_FIELDS_KEYS_FILE,
+ verbose=verbose,
+ )
+ except Exception as e:
+ td.log.error(f"Error occurred while uploading simulation fields keys: {e}")
+ raise e
+ finally:
+ os.unlink(fname)
+
+
+""" VJP maker for ADJ pass."""
+
+
+def get_vjp_traced_fields(task_id_adj: str, verbose: bool) -> AutogradFieldMap:
+ """Function to grab the VJP result for the simulation fields from the adjoint task ID."""
+ handle, fname = tempfile.mkstemp(suffix=".hdf5")
+ os.close(handle)
+ try:
+ download_file(task_id_adj, SIM_VJP_FILE, to_file=fname, verbose=verbose)
+ field_map = FieldMap.from_file(fname)
+ except Exception as e:
+ td.log.error(f"Error occurred while getting VJP traced fields: {e}")
+ raise e
+ finally:
+ os.unlink(fname)
+ return field_map.to_autograd_field_map
+
+
+def _run_bwd(
+ data_fields_original: AutogradFieldMap,
+ sim_fields_original: AutogradFieldMap,
+ sim_original: td.Simulation,
+ task_name: str,
+ aux_data: dict,
+ local_gradient: bool,
+ max_num_adjoint_per_fwd: int,
+ **run_kwargs,
+) -> typing.Callable[[AutogradFieldMap], AutogradFieldMap]:
+ """VJP-maker for ``_run_primitive()``. Constructs and runs adjoint simulations, computes grad."""
+
+ # indicate this is an adjoint run
+ run_kwargs["is_adjoint"] = True
+
+ # get the fwd epsilon and field data from the cached aux_data
+ sim_data_orig = aux_data[AUX_KEY_SIM_DATA_ORIGINAL]
+ sim_fields_keys = list(sim_fields_original.keys())
+
+ td.log.info(f"Number of fields to compute gradients for: {len(sim_fields_keys)}")
+
+ if local_gradient:
+ sim_data_fwd = aux_data[AUX_KEY_SIM_DATA_FWD]
+ td.log.info("Using local gradient computation mode")
+ else:
+ td.log.info("Using server-side gradient computation mode")
+
+ td.log.info("Constructing custom VJP function for backwards pass.")
+
+ def vjp(data_fields_vjp: AutogradFieldMap) -> AutogradFieldMap:
+ """dJ/d{sim.traced_fields()} as a function of Function of dJ/d{data.traced_fields()}"""
+
+ # build the (possibly multiple) adjoint simulations
+ sims_adj = setup_adj(
+ data_fields_vjp=data_fields_vjp,
+ sim_data_orig=sim_data_orig,
+ sim_fields_keys=sim_fields_keys,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ )
+
+ if not sims_adj:
+ td.log.warning(
+ f"Adjoint simulation for task '{task_name}' contains no sources. "
+ "This can occur if the objective function does not depend on the "
+ "simulation's output. If this is unexpected, please review your "
+ "setup or contact customer support for assistance."
+ )
+ return {
+ k: (type(v)(0 * x for x in v) if isinstance(v, (list, tuple)) else 0 * v)
+ for k, v in sim_fields_original.items()
+ }
+
+ # Run adjoint simulations in batch
+ task_names_adj = [f"{task_name}_adjoint_{i}" for i in range(len(sims_adj))]
+ sims_adj_dict = dict(zip(task_names_adj, sims_adj))
+
+ td.log.info(f"Running {len(sims_adj)} adjoint simulations")
+
+ vjp_traced_fields = {}
+
+ if local_gradient:
+ # Run all adjoint sims in batch
+ td.log.info("Starting local batch adjoint simulations")
+ path = Path(run_kwargs.pop("path"))
+ path_dir_adj = path.parent / LOCAL_ADJOINT_DIR
+ path_dir_adj.mkdir(exist_ok=True)
+
+ batch_data_adj, _ = _run_async_tidy3d(
+ sims_adj_dict, path_dir=str(path_dir_adj), **run_kwargs
+ )
+ td.log.info("Completed local batch adjoint simulations")
+
+ # Process results from local gradient computation
+ vjp_fields_dict = {}
+ for task_name_adj, sim_data_adj in batch_data_adj.items():
+ td.log.info(f"Processing VJP contribution from {task_name_adj}")
+ vjp_fields_dict[task_name_adj] = postprocess_adj(
+ sim_data_adj=sim_data_adj,
+ sim_data_orig=sim_data_orig,
+ sim_data_fwd=sim_data_fwd,
+ sim_fields_keys=sim_fields_keys,
+ )
+ else:
+ td.log.info("Starting server-side batch of adjoint simulations ...")
+
+ # Link each adjoint sim to the forward task it depends on
+ task_id_fwd = aux_data[AUX_KEY_FWD_TASK_ID]
+ run_kwargs["simulation_type"] = "autograd_bwd"
+
+ # Build a per-task parent_tasks mapping
+ parent_tasks = {}
+ for tname_adj in sims_adj_dict:
+ parent_tasks[tname_adj] = [task_id_fwd]
+ run_kwargs["parent_tasks"] = parent_tasks
+
+ # Update each simulation's type, then run them in batch
+ sims_adj_dict = {
+ tname_adj: sim.updated_copy(simulation_type="autograd_bwd", deep=False)
+ for tname_adj, sim in sims_adj_dict.items()
+ }
+ vjp_fields_dict = _run_async_tidy3d_bwd(
+ simulations=sims_adj_dict,
+ **run_kwargs,
+ )
+ td.log.info("Completed server-side batch of adjoint simulations.")
+
+ # Accumulate gradients from all adjoint simulations
+ for task_name_adj, vjp_fields in vjp_fields_dict.items():
+ td.log.info(f"Processing VJP contribution from {task_name_adj}")
+ for k, v in vjp_fields.items():
+ if k in vjp_traced_fields:
+ val = vjp_traced_fields[k]
+ if isinstance(val, (list, tuple)) and isinstance(v, (list, tuple)):
+ vjp_traced_fields[k] = type(val)(x + y for x, y in zip(val, v))
+ else:
+ vjp_traced_fields[k] += v
+ else:
+ vjp_traced_fields[k] = v
+
+ td.log.debug(f"Computed gradients for {len(vjp_traced_fields)} fields")
+ return vjp_traced_fields
+
+ return vjp
+
+
+def _run_async_bwd(
+ data_fields_original_dict: dict[str, AutogradFieldMap],
+ sim_fields_original_dict: dict[str, AutogradFieldMap],
+ sims_original: dict[str, td.Simulation],
+ aux_data_dict: dict[str, dict[str, typing.Any]],
+ local_gradient: bool,
+ max_num_adjoint_per_fwd: int,
+ **run_async_kwargs,
+) -> typing.Callable[[dict[str, AutogradFieldMap]], dict[str, AutogradFieldMap]]:
+ """VJP-maker for ``_run_primitive()``. Constructs and runs adjoint simulation, computes grad."""
+
+ # indicate this is an adjoint run
+ run_async_kwargs["is_adjoint"] = True
+
+ task_names = data_fields_original_dict.keys()
+
+ # get the fwd epsilon and field data from the cached aux_data
+ sim_data_orig_dict = {}
+ sim_data_fwd_dict = {}
+ sim_fields_keys_dict = {}
+ for task_name in task_names:
+ aux_data = aux_data_dict[task_name]
+ sim_data_orig_dict[task_name] = aux_data[AUX_KEY_SIM_DATA_ORIGINAL]
+ sim_fields_keys_dict[task_name] = list(sim_fields_original_dict[task_name].keys())
+
+ if local_gradient:
+ sim_data_fwd_dict[task_name] = aux_data[AUX_KEY_SIM_DATA_FWD]
+
+ td.log.info("constructing custom vjp function for backwards pass.")
+
+ def vjp(data_fields_dict_vjp: dict[str, AutogradFieldMap]) -> dict[str, AutogradFieldMap]:
+ """dJ/d{sim.traced_fields()} as a function of Function of dJ/d{data.traced_fields()}"""
+
+ # Collect all adjoint simulations across all forward tasks
+ all_sims_adj = {}
+ sim_fields_vjp_dict = {}
+ task_name_mapping = {} # Maps adjoint task names to original task names
+
+ for task_name in task_names:
+ data_fields_vjp = data_fields_dict_vjp[task_name]
+ sim_data_orig = sim_data_orig_dict[task_name]
+ sim_fields_keys = sim_fields_keys_dict[task_name]
+
+ sims_adj = setup_adj(
+ data_fields_vjp=data_fields_vjp,
+ sim_data_orig=sim_data_orig,
+ sim_fields_keys=sim_fields_keys,
+ max_num_adjoint_per_fwd=max_num_adjoint_per_fwd,
+ )
+
+ if not sims_adj:
+ td.log.debug(f"Adjoint simulation for task '{task_name}' contains no sources.")
+ sim_fields_vjp_dict[task_name] = {
+ k: (type(v)(0 * x for x in v) if isinstance(v, (list, tuple)) else 0 * v)
+ for k, v in sim_fields_original_dict[task_name].items()
+ }
+ continue
+
+ # Add each adjoint simulation to the combined batch with unique task names
+ for i, sim_adj in enumerate(sims_adj):
+ adj_task_name = f"{task_name}_adjoint_{i}"
+ all_sims_adj[adj_task_name] = sim_adj
+ task_name_mapping[adj_task_name] = task_name
+
+ if not all_sims_adj:
+ td.log.warning(
+ "No simulation in batch contains adjoint sources and thus all gradients are zero."
+ )
+ return sim_fields_vjp_dict
+
+ # Dictionary to store VJP results from all adjoint simulations
+ vjp_results = {}
+
+ if local_gradient:
+ # Run all adjoint simulations in a single batch
+ path_dir = Path(run_async_kwargs.pop("path_dir"))
+ path_dir_adj = path_dir / LOCAL_ADJOINT_DIR
+ path_dir_adj.mkdir(exist_ok=True)
+
+ batch_data_adj, _ = _run_async_tidy3d(
+ all_sims_adj, path_dir=str(path_dir_adj), **run_async_kwargs
+ )
+
+ # Process results for each adjoint task
+ for adj_task_name, sim_data_adj in batch_data_adj.items():
+ task_name = task_name_mapping[adj_task_name]
+ sim_data_orig = sim_data_orig_dict[task_name]
+ sim_data_fwd = sim_data_fwd_dict[task_name]
+ sim_fields_keys = sim_fields_keys_dict[task_name]
+
+ # Compute VJP contribution
+ vjp_results[adj_task_name] = postprocess_adj(
+ sim_data_adj=sim_data_adj,
+ sim_data_orig=sim_data_orig,
+ sim_data_fwd=sim_data_fwd,
+ sim_fields_keys=sim_fields_keys,
+ )
+ else:
+ # Set up parent tasks mapping for all adjoint simulations
+ parent_tasks = {}
+ for adj_task_name, task_name in task_name_mapping.items():
+ task_id_fwd = aux_data_dict[task_name][AUX_KEY_FWD_TASK_ID]
+ parent_tasks[adj_task_name] = [task_id_fwd]
+
+ run_async_kwargs["parent_tasks"] = parent_tasks
+ run_async_kwargs["simulation_type"] = "autograd_bwd"
+
+ # Update simulation types
+ all_sims_adj = {
+ task_name: sim.updated_copy(simulation_type="autograd_bwd", deep=False)
+ for task_name, sim in all_sims_adj.items()
+ }
+
+ # Run all adjoint simulations in a single batch
+ vjp_results = _run_async_tidy3d_bwd(
+ simulations=all_sims_adj,
+ **run_async_kwargs,
+ )
+
+ # Accumulate gradients from all adjoint simulations
+ for adj_task_name, vjp_fields in vjp_results.items():
+ task_name = task_name_mapping[adj_task_name]
+
+ if task_name not in sim_fields_vjp_dict:
+ sim_fields_vjp_dict[task_name] = {}
+
+ for k, v in vjp_fields.items():
+ if k in sim_fields_vjp_dict[task_name]:
+ val = sim_fields_vjp_dict[task_name][k]
+ if isinstance(val, (list, tuple)) and isinstance(v, (list, tuple)):
+ sim_fields_vjp_dict[task_name][k] = type(val)(x + y for x, y in zip(val, v))
+ else:
+ sim_fields_vjp_dict[task_name][k] += v
+ else:
+ sim_fields_vjp_dict[task_name][k] = v
+
+ return sim_fields_vjp_dict
+
+ return vjp
+
+
+def setup_adj(
+ data_fields_vjp: AutogradFieldMap,
+ sim_data_orig: td.SimulationData,
+ sim_fields_keys: list[tuple],
+ max_num_adjoint_per_fwd: int,
+) -> list[td.Simulation]:
+ """Construct an adjoint simulation from a set of data_fields for the VJP."""
+
+ td.log.info("Running custom vjp (adjoint) pipeline.")
+
+ # filter out any data_fields_vjp with all 0's
+ data_fields_vjp = {
+ k: get_static(v) for k, v in data_fields_vjp.items() if not np.allclose(v, 0)
+ }
+
+ # if all entries are zero, there is no adjoint sim to run
+ if not data_fields_vjp:
+ return []
+
+ # start with the full simulation data structure and either zero out the fields
+ # that have no tracer data for them or insert the tracer data
+ full_sim_data_dict = sim_data_orig._strip_traced_fields(
+ include_untraced_data_arrays=True, starting_path=("data",)
+ )
+ for path in full_sim_data_dict.keys():
+ if path in data_fields_vjp:
+ full_sim_data_dict[path] = data_fields_vjp[path]
+ else:
+ full_sim_data_dict[path] *= 0
+
+ # insert the raw VJP data into the .data of the original SimulationData
+ sim_data_vjp = sim_data_orig._insert_traced_fields(field_mapping=full_sim_data_dict)
+
+ # make adjoint simulation from that SimulationData
+ data_vjp_paths = set(data_fields_vjp.keys())
+
+ num_monitors = len(sim_data_orig.simulation.monitors)
+ adjoint_monitors = sim_data_orig.simulation._with_adjoint_monitors(sim_fields_keys).monitors[
+ num_monitors:
+ ]
+
+ sims_adj = sim_data_vjp._make_adjoint_sims(
+ data_vjp_paths=data_vjp_paths,
+ adjoint_monitors=adjoint_monitors,
+ )
+
+ if _INSPECT_ADJOINT_FIELDS and sims_adj:
+ adj_fld_mnt = td.FieldMonitor(
+ center=_INSPECT_ADJOINT_PLANE.center,
+ size=_INSPECT_ADJOINT_PLANE.size,
+ freqs=adjoint_monitors[0].freqs,
+ name="adjoint_fields",
+ )
+
+ import matplotlib.pylab as plt
+
+ import tidy3d.web as web
+
+ sim_data_new = web.run(
+ sims_adj[0].updated_copy(monitors=[adj_fld_mnt]),
+ task_name="adjoint_field_viz",
+ verbose=False,
+ )
+ _, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True, figsize=(10, 4))
+ sim_data_new.plot_field("adjoint_fields", "Ex", "re", ax=ax1)
+ sim_data_new.plot_field("adjoint_fields", "Ey", "re", ax=ax2)
+ sim_data_new.plot_field("adjoint_fields", "Ez", "re", ax=ax3)
+ plt.show()
+
+ if len(sims_adj) > max_num_adjoint_per_fwd:
+ raise AdjointError(
+ f"Number of adjoint simulations ({len(sims_adj)}) exceeds the maximum allowed "
+ f"({max_num_adjoint_per_fwd}) per forward simulation. This typically means that "
+ "there are many frequencies and monitors in the simulation that are being differentiated "
+ "w.r.t. in the objective function. To proceed, please double-check the simulation "
+ "setup, increase the 'max_num_adjoint_per_fwd' parameter in the run function, and re-run."
+ )
+
+ return sims_adj
+
+
+def _compute_eps_array(medium, frequencies):
+ """Compute permittivity array for all frequencies.
+
+ Parameters
+ ----------
+ medium : Medium
+ Medium to compute permittivity for.
+ frequencies : ArrayLike
+ Array of frequencies at which to evaluate permittivity.
+
+ Returns
+ -------
+ DataArray
+ Permittivity values with frequency dimension.
+ """
+ eps_data = [np.mean(medium.eps_model(f)) for f in frequencies]
+ return DataArray(data=np.array(eps_data), dims=("f",), coords={"f": frequencies})
+
+
+def _slice_field_data(field_data: dict, freq_slice: slice) -> dict:
+ """Slice field data dictionary along frequency dimension.
+
+ Parameters
+ ----------
+ field_data : dict
+ Dictionary of field components.
+ freq_slice : slice
+ Frequency slice to apply.
+
+ Returns
+ -------
+ dict
+ Sliced field data dictionary.
+ """
+ return {k: v.isel(f=freq_slice) for k, v in field_data.items()}
+
+
+def postprocess_adj(
+ sim_data_adj: td.SimulationData,
+ sim_data_orig: td.SimulationData,
+ sim_data_fwd: td.SimulationData,
+ sim_fields_keys: list[tuple],
+) -> AutogradFieldMap:
+ """Postprocess some data from the adjoint simulation into the VJP for the original sim flds."""
+
+ # map of index into 'structures' to the list of paths we need vjps for
+ sim_vjp_map = defaultdict(list)
+ for _, structure_index, *structure_path in sim_fields_keys:
+ structure_path = tuple(structure_path)
+ sim_vjp_map[structure_index].append(structure_path)
+
+ # store the derivative values given the forward and adjoint data
+ sim_fields_vjp = {}
+ for structure_index, structure_paths in sim_vjp_map.items():
+ # grab the forward and adjoint data
+ E_fwd = sim_data_fwd._get_adjoint_data(structure_index, data_type="fld")
+ eps_fwd = sim_data_fwd._get_adjoint_data(structure_index, data_type="eps")
+ E_adj = sim_data_adj._get_adjoint_data(structure_index, data_type="fld")
+ eps_adj = sim_data_adj._get_adjoint_data(structure_index, data_type="eps")
+
+ # post normalize the adjoint fields if a single, broadband source
+ adj_flds_normed = {}
+ for key, val in E_adj.field_components.items():
+ adj_flds_normed[key] = val * sim_data_adj.simulation.post_norm
+
+ E_adj = E_adj.updated_copy(**adj_flds_normed)
+
+ # maps of the E_fwd * E_adj and D_fwd * D_adj, each as as td.FieldData & 'Ex', 'Ey', 'Ez'
+ der_maps = get_derivative_maps(
+ fld_fwd=E_fwd, eps_fwd=eps_fwd, fld_adj=E_adj, eps_adj=eps_adj
+ )
+ E_der_map = der_maps["E"]
+ D_der_map = der_maps["D"]
+
+ D_fwd = E_to_D(E_fwd, eps_fwd)
+ D_adj = E_to_D(E_adj, eps_fwd)
+
+ # compute the derivatives for this structure
+ structure = sim_data_fwd.simulation.structures[structure_index]
+
+ # compute epsilon arrays for all frequencies
+ adjoint_frequencies = np.array(E_adj.monitor.freqs)
+
+ eps_in = _compute_eps_array(structure.medium, adjoint_frequencies)
+ eps_out = _compute_eps_array(sim_data_orig.simulation.medium, adjoint_frequencies)
+
+ # handle background medium if present
+ if structure.background_medium:
+ eps_background = _compute_eps_array(structure.background_medium, adjoint_frequencies)
+ else:
+ eps_background = None
+
+ # auto permittivity detection for non-box geometries
+ if not isinstance(structure.geometry, td.Box):
+ sim_orig = sim_data_orig.simulation
+ plane_eps = eps_fwd.monitor.geometry
+
+ # permittivity without this structure
+ structs_no_struct = list(sim_orig.structures)
+ structs_no_struct.pop(structure_index)
+ sim_no_structure = sim_orig.updated_copy(structures=structs_no_struct)
+
+ eps_no_structure_data = [
+ sim_no_structure.epsilon(box=plane_eps, coord_key="centers", freq=f)
+ for f in adjoint_frequencies
+ ]
+
+ # permittivity with infinite structure
+ structs_inf_struct = list(sim_orig.structures)[structure_index + 1 :]
+ sim_inf_structure = sim_orig.updated_copy(
+ structures=structs_inf_struct,
+ medium=structure.medium,
+ monitors=[],
+ )
+
+ eps_inf_structure_data = [
+ sim_inf_structure.epsilon(box=plane_eps, coord_key="centers", freq=f)
+ for f in adjoint_frequencies
+ ]
+
+ eps_no_structure = xr.concat(eps_no_structure_data, dim="f").assign_coords(
+ f=adjoint_frequencies
+ )
+ eps_inf_structure = xr.concat(eps_inf_structure_data, dim="f").assign_coords(
+ f=adjoint_frequencies
+ )
+ else:
+ eps_no_structure = eps_inf_structure = None
+
+ # compute bounds intersection
+ struct_bounds = rmin_struct, rmax_struct = structure.geometry.bounds
+ rmin_sim, rmax_sim = sim_data_orig.simulation.bounds
+ rmin_intersect = tuple([max(a, b) for a, b in zip(rmin_sim, rmin_struct)])
+ rmax_intersect = tuple([min(a, b) for a, b in zip(rmax_sim, rmax_struct)])
+ bounds_intersect = (rmin_intersect, rmax_intersect)
+
+ # get chunk size - if None, process all frequencies as one chunk
+ freq_chunk_size = ADJOINT_FREQ_CHUNK_SIZE
+ n_freqs = len(adjoint_frequencies)
+ if freq_chunk_size is None:
+ freq_chunk_size = n_freqs
+
+ # process in chunks
+ vjp_value_map = {}
+
+ for chunk_start in range(0, n_freqs, freq_chunk_size):
+ chunk_end = min(chunk_start + freq_chunk_size, n_freqs)
+ freq_slice = slice(chunk_start, chunk_end)
+
+ # slice field data for current chunk
+ E_der_map_chunk = _slice_field_data(E_der_map.field_components, freq_slice)
+ D_der_map_chunk = _slice_field_data(D_der_map.field_components, freq_slice)
+ E_fwd_chunk = _slice_field_data(E_fwd.field_components, freq_slice)
+ E_adj_chunk = _slice_field_data(E_adj.field_components, freq_slice)
+ D_fwd_chunk = _slice_field_data(D_fwd.field_components, freq_slice)
+ D_adj_chunk = _slice_field_data(D_adj.field_components, freq_slice)
+ eps_data_chunk = _slice_field_data(eps_fwd.field_components, freq_slice)
+
+ # slice epsilon arrays
+ eps_in_chunk = eps_in.isel(f=freq_slice)
+ eps_out_chunk = eps_out.isel(f=freq_slice)
+ eps_background_chunk = (
+ eps_background.isel(f=freq_slice) if eps_background is not None else None
+ )
+ eps_no_structure_chunk = (
+ eps_no_structure.isel(f=freq_slice) if eps_no_structure is not None else None
+ )
+ eps_inf_structure_chunk = (
+ eps_inf_structure.isel(f=freq_slice) if eps_inf_structure is not None else None
+ )
+
+ # create derivative info with sliced data
+ derivative_info = DerivativeInfo(
+ paths=structure_paths,
+ E_der_map=E_der_map_chunk,
+ D_der_map=D_der_map_chunk,
+ E_fwd=E_fwd_chunk,
+ E_adj=E_adj_chunk,
+ D_fwd=D_fwd_chunk,
+ D_adj=D_adj_chunk,
+ eps_data=eps_data_chunk,
+ eps_in=eps_in_chunk,
+ eps_out=eps_out_chunk,
+ eps_background=eps_background_chunk,
+ frequencies=adjoint_frequencies[freq_slice], # only chunk frequencies
+ eps_no_structure=eps_no_structure_chunk,
+ eps_inf_structure=eps_inf_structure_chunk,
+ bounds=struct_bounds,
+ bounds_intersect=bounds_intersect,
+ )
+
+ # compute derivatives for chunk
+ vjp_chunk = structure._compute_derivatives(derivative_info)
+
+ # accumulate results
+ for path, value in vjp_chunk.items():
+ if path in vjp_value_map:
+ val = vjp_value_map[path]
+ if isinstance(val, (list, tuple)) and isinstance(value, (list, tuple)):
+ vjp_value_map[path] = type(val)(x + y for x, y in zip(val, value))
+ else:
+ vjp_value_map[path] += value
+ else:
+ vjp_value_map[path] = value
+
+ # store vjps in output map
+ for structure_path, vjp_value in vjp_value_map.items():
+ sim_path = ("structures", structure_index, *list(structure_path))
+ sim_fields_vjp[sim_path] = vjp_value
+
+ return sim_fields_vjp
+
+
+""" Register primitives and VJP makers used by the user-facing functions."""
+
+defvjp(_run_primitive, _run_bwd, argnums=[0])
+defvjp(_run_async_primitive, _run_async_bwd, argnums=[0])
+
+
+""" The fundamental Tidy3D run and run_async functions used above. """
+
+
+def parse_run_kwargs(**run_kwargs):
+ """Parse the ``run_kwargs`` to extract what should be passed to the ``Job`` initialization."""
+ job_fields = [*list(Job._upload_fields), "solver_version", "pay_type"]
+ job_init_kwargs = {k: v for k, v in run_kwargs.items() if k in job_fields}
+ return job_init_kwargs
+
+
+def _run_tidy3d(
+ simulation: td.Simulation, task_name: str, **run_kwargs
+) -> tuple[td.SimulationData, str]:
+ """Run a simulation without any tracers using regular web.run()."""
+
+ job_init_kwargs = parse_run_kwargs(**run_kwargs)
+ job = Job(simulation=simulation, task_name=task_name, **job_init_kwargs)
+ td.log.info(f"running {job.simulation_type} simulation with '_run_tidy3d()'")
+ if job.simulation_type == "autograd_fwd":
+ verbose = run_kwargs.get("verbose", False)
+ upload_sim_fields_keys(run_kwargs["sim_fields_keys"], task_id=job.task_id, verbose=verbose)
+ path = run_kwargs.get("path", DEFAULT_DATA_PATH)
+ if task_name.endswith("_adjoint"):
+ path_parts = basename(path).split(".")
+ path = join(dirname(path), path_parts[0] + "_adjoint." + ".".join(path_parts[1:]))
+ data = job.run(path)
+ return data, job.task_id
+
+
+def _run_async_tidy3d(
+ simulations: dict[str, td.Simulation], **run_kwargs
+) -> tuple[BatchData, dict[str, str]]:
+ """Run a batch of simulations using regular web.run()."""
+
+ batch_init_kwargs = parse_run_kwargs(**run_kwargs)
+ path_dir = run_kwargs.pop("path_dir", None)
+ batch = Batch(simulations=simulations, **batch_init_kwargs)
+ td.log.info(f"running {batch.simulation_type} batch with '_run_async_tidy3d()'")
+
+ if batch.simulation_type == "autograd_fwd":
+ verbose = run_kwargs.get("verbose", False)
+ # Need to upload to get the task_ids
+ sims = {
+ task_name: sim.updated_copy(simulation_type="autograd_fwd", deep=False)
+ for task_name, sim in batch.simulations.items()
+ }
+ batch = batch.updated_copy(simulations=sims)
+
+ batch.upload()
+ task_ids = {key: job.task_id for key, job in batch.jobs.items()}
+ for task_name, sim_fields_keys in run_kwargs["sim_fields_keys_dict"].items():
+ task_id = task_ids[task_name]
+ upload_sim_fields_keys(sim_fields_keys, task_id=task_id, verbose=verbose)
+
+ if path_dir:
+ batch_data = batch.run(path_dir)
+ else:
+ batch_data = batch.run()
+
+ task_ids = {key: job.task_id for key, job in batch.jobs.items()}
+ return batch_data, task_ids
+
+
+def _run_async_tidy3d_bwd(
+ simulations: dict[str, td.Simulation],
+ **run_kwargs,
+) -> dict[str, AutogradFieldMap]:
+ """Run a batch of adjoint simulations using regular web.run()."""
+
+ batch_init_kwargs = parse_run_kwargs(**run_kwargs)
+ _ = run_kwargs.pop("path_dir", None)
+ batch = Batch(simulations=simulations, **batch_init_kwargs)
+ td.log.info(f"running {batch.simulation_type} batch with '_run_async_tidy3d_bwd()'")
+
+ batch.start()
+ batch.monitor()
+
+ vjp_traced_fields_dict = {}
+ for task_name, job in batch.jobs.items():
+ task_id = job.task_id
+ vjp = get_vjp_traced_fields(task_id_adj=task_id, verbose=batch.verbose)
+ vjp_traced_fields_dict[task_name] = vjp
+
+ return vjp_traced_fields_dict
diff --git a/tidy3d/plugins/smatrix/web/api/autograd/utils.py b/tidy3d/plugins/smatrix/web/api/autograd/utils.py
new file mode 100644
index 0000000000..7189096531
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/autograd/utils.py
@@ -0,0 +1,110 @@
+# utility functions for autograd web API
+from __future__ import annotations
+
+import typing
+
+import pydantic as pd
+
+import tidy3d as td
+from tidy3d.components.autograd.types import AutogradFieldMap, dict_ag
+from tidy3d.components.base import Tidy3dBaseModel
+from tidy3d.components.types import ArrayLike, tidycomplex
+
+""" E and D field gradient map calculation helpers. """
+
+
+def get_derivative_maps(
+ fld_fwd: td.FieldData,
+ eps_fwd: td.PermittivityData,
+ fld_adj: td.FieldData,
+ eps_adj: td.PermittivityData,
+) -> dict[str, td.FieldData]:
+ """Get electric and displacement field derivative maps."""
+ der_map_E = derivative_map_E(fld_fwd=fld_fwd, fld_adj=fld_adj)
+ der_map_D = derivative_map_D(fld_fwd=fld_fwd, eps_fwd=eps_fwd, fld_adj=fld_adj, eps_adj=eps_adj)
+ return {"E": der_map_E, "D": der_map_D}
+
+
+def derivative_map_E(fld_fwd: td.FieldData, fld_adj: td.FieldData) -> td.FieldData:
+ """Get td.FieldData where the Ex, Ey, Ez components store the gradients w.r.t. these."""
+ return multiply_field_data(fld_fwd, fld_adj)
+
+
+def derivative_map_D(
+ fld_fwd: td.FieldData,
+ eps_fwd: td.PermittivityData,
+ fld_adj: td.FieldData,
+ eps_adj: td.PermittivityData,
+) -> td.FieldData:
+ """Get td.FieldData where the Ex, Ey, Ez components store the gradients w.r.t. D fields."""
+ fwd_D = E_to_D(fld_data=fld_fwd, eps_data=eps_fwd)
+ adj_D = E_to_D(fld_data=fld_adj, eps_data=eps_adj)
+ return multiply_field_data(fwd_D, adj_D)
+
+
+def E_to_D(fld_data: td.FieldData, eps_data: td.PermittivityData) -> td.FieldData:
+ """Convert electric field to displacement field."""
+ return multiply_field_data(fld_data, eps_data)
+
+
+def multiply_field_data(
+ fld_1: td.FieldData, fld_2: typing.Union[td.FieldData, td.PermittivityData]
+) -> td.FieldData:
+ """Elementwise multiply two field data objects, writes data into ``fld_1`` copy."""
+
+ def get_field_key(dim: str, fld_data: typing.Union[td.FieldData, td.PermittivityData]) -> str:
+ """Get the key corresponding to the scalar field along this dimension."""
+ return f"E{dim}" if isinstance(fld_data, td.FieldData) else f"eps_{dim}{dim}"
+
+ field_components = {}
+ for dim in "xyz":
+ key_1 = get_field_key(dim=dim, fld_data=fld_1)
+ key_2 = get_field_key(dim=dim, fld_data=fld_2)
+ cmp_1 = fld_1.field_components[key_1]
+ cmp_2 = fld_2.field_components[key_2]
+ mult = cmp_1 * cmp_2
+ field_components[key_1] = mult
+ return fld_1.updated_copy(**field_components)
+
+
+class Tracer(Tidy3dBaseModel):
+ """Class to store a single traced field."""
+
+ path: tuple[typing.Any, ...] = pd.Field(
+ ...,
+ title="Path to the traced object in the model dictionary.",
+ )
+
+ data: typing.Union[float, tidycomplex, ArrayLike] = pd.Field(..., title="Tracing data")
+
+
+class FieldMap(Tidy3dBaseModel):
+ """Class to store a collection of traced fields."""
+
+ tracers: tuple[Tracer, ...] = pd.Field(
+ ...,
+ title="Collection of Tracers.",
+ )
+
+ @property
+ def to_autograd_field_map(self) -> AutogradFieldMap:
+ """Convert to ``AutogradFieldMap`` autograd dictionary."""
+ return dict_ag({tracer.path: tracer.data for tracer in self.tracers})
+
+ @classmethod
+ def from_autograd_field_map(cls, autograd_field_map) -> FieldMap:
+ """Initialize from an ``AutogradFieldMap`` autograd dictionary."""
+ tracers = []
+ for path, data in autograd_field_map.items():
+ tracers.append(Tracer(path=path, data=data))
+
+ return cls(tracers=tuple(tracers))
+
+
+class TracerKeys(Tidy3dBaseModel):
+ """Class to store a collection of tracer keys."""
+
+ keys: tuple[tuple[typing.Any, ...], ...] = pd.Field(
+ ...,
+ title="Collection of tracer keys.",
+ )
diff --git a/tidy3d/plugins/smatrix/web/api/cacert.pem b/tidy3d/plugins/smatrix/web/api/cacert.pem
new file mode 100644
index 0000000000..df9e4e3c75
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/cacert.pem
@@ -0,0 +1,4527 @@
+
+# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
+# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA
+# Label: "GlobalSign Root CA"
+# Serial: 4835703278459707669005204
+# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a
+# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c
+# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99
+-----BEGIN CERTIFICATE-----
+MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
+A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
+b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
+MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
+YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
+aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
+jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
+xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
+1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
+snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
+U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
+9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
+AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
+yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
+38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
+AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
+DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
+HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
+# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
+# Label: "Entrust.net Premium 2048 Secure Server CA"
+# Serial: 946069240
+# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90
+# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31
+# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
+RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
+bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
+IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
+MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
+LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
+YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
+A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
+K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
+sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
+MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
+XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
+HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
+4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
+j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
+U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
+zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
+u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
+bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
+fF6adulZkMV8gzURZVE=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
+# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust
+# Label: "Baltimore CyberTrust Root"
+# Serial: 33554617
+# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4
+# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74
+# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
+RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
+VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
+DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
+ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
+VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
+mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
+IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
+mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
+XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
+dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
+jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
+BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
+DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
+9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
+jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
+Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
+ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
+R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
+-----END CERTIFICATE-----
+
+# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
+# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc.
+# Label: "Entrust Root Certification Authority"
+# Serial: 1164660820
+# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4
+# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9
+# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c
+-----BEGIN CERTIFICATE-----
+MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
+Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
+KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
+cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
+NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
+NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
+ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
+BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
+Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
+4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
+KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
+rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
+94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
+sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
+gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
+kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
+vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
+A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
+O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
+AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
+9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
+eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
+0vdXcDazv/wor3ElhVsT/h5/WrQ8
+-----END CERTIFICATE-----
+
+# Issuer: CN=AAA Certificate Services O=Comodo CA Limited
+# Subject: CN=AAA Certificate Services O=Comodo CA Limited
+# Label: "Comodo AAA Services root"
+# Serial: 1
+# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0
+# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49
+# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4
+-----BEGIN CERTIFICATE-----
+MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
+MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
+GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
+YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
+GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
+BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
+3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
+YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
+rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
+ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
+oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
+MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
+QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
+b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
+AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
+GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
+Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
+G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
+l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
+smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 2"
+# Serial: 1289
+# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b
+# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7
+# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86
+-----BEGIN CERTIFICATE-----
+MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
+GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
+Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
+WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
+rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
++ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
+ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
+Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
+PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
+/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
+oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
+yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
+EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
+A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
+MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
+ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
+BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
+g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
+fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
+WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
+B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
+hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
+TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
+mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
+ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
+4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
+8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 3"
+# Serial: 1478
+# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf
+# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85
+# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35
+-----BEGIN CERTIFICATE-----
+MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
+GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
+b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
+BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
+YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
+V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
+4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
+H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
+8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
+vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
+mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
+btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
+T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
+WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
+c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
+4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
+VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
+CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
+aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
+aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
+dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
+czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
+A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
+TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
+Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
+7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
+d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
++LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
+4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
+t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
+DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
+k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
+zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
+Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
+mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
+4SVhM7JZG+Ju1zdXtg2pEto=
+-----END CERTIFICATE-----
+
+# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1
+# Subject: O=SECOM Trust.net OU=Security Communication RootCA1
+# Label: "Security Communication Root CA"
+# Serial: 0
+# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a
+# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7
+# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c
+-----BEGIN CERTIFICATE-----
+MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
+MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
+dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
+WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
+VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
+9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
+DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
+Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
+QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
+xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
+A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
+kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
+Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
+Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
+JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
+RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
+# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
+# Label: "XRamp Global CA Root"
+# Serial: 107108908803651509692980124233745014957
+# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1
+# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6
+# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2
+-----BEGIN CERTIFICATE-----
+MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
+gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
+MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
+UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
+NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
+dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
+dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
+38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
+KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
+DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
+qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
+JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
+PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
+BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
+jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
+eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
+ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
+vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
+qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
+IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
+i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
+O+7ETPTsJ3xCwnR8gooJybQDJbw=
+-----END CERTIFICATE-----
+
+# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
+# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
+# Label: "Go Daddy Class 2 CA"
+# Serial: 0
+# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67
+# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4
+# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4
+-----BEGIN CERTIFICATE-----
+MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
+MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
+YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
+MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
+ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
+MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
+ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
+PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
+wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
+EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
+avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
+sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
+/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
+IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
+ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
+OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
+TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
+HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
+dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
+ReYNnyicsbkqWletNw+vHX/bvZ8=
+-----END CERTIFICATE-----
+
+# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
+# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
+# Label: "Starfield Class 2 CA"
+# Serial: 0
+# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24
+# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a
+# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58
+-----BEGIN CERTIFICATE-----
+MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
+MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
+U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
+NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
+ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
+ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
+DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
+8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
+X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
+K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
+1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
+A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
+zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
+YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
+bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
+L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
+eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
+xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
+VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
+WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Assured ID Root CA"
+# Serial: 17154717934120587862167794914071425081
+# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72
+# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43
+# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c
+-----BEGIN CERTIFICATE-----
+MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
+JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
+mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
+VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
+AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
+AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
+pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
+dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
+fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
+NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
+H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
++o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root CA"
+# Serial: 10944719598952040374951832963794454346
+# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e
+# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36
+# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61
+-----BEGIN CERTIFICATE-----
+MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
+CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
+nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
+43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
+T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
+gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
+TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
+DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
+hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
+06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
+PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
+YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
+CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert High Assurance EV Root CA"
+# Serial: 3553400076410547919724730734378100087
+# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a
+# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25
+# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
+ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
+MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
+LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
+RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
+PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
+xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
+Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
+hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
+EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
+FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
+nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
+eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
+hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
+Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
+vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
++OkuE6N36B9K
+-----END CERTIFICATE-----
+
+# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG
+# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG
+# Label: "SwissSign Gold CA - G2"
+# Serial: 13492815561806991280
+# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93
+# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61
+# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95
+-----BEGIN CERTIFICATE-----
+MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
+biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
+MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
+d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
+76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
+bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
+6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
+emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
+MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
+MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
+MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
+FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
+aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
+gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
+qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
+lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
+8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
+L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
+45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
+UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
+O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
+bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
+GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
+77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
+hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
+92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
+Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
+ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
+Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
+-----END CERTIFICATE-----
+
+# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG
+# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG
+# Label: "SwissSign Silver CA - G2"
+# Serial: 5700383053117599563
+# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13
+# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb
+# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5
+-----BEGIN CERTIFICATE-----
+MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
+BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
+IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
+RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
+U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
+MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
+Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
+YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
+nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
+6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
+eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
+c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
+MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
+HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
+jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
+5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
+rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
+wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
+cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
+AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
+WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
+xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
+2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
+IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
+aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
+em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
+dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
+OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
+tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
+-----END CERTIFICATE-----
+
+# Issuer: CN=SecureTrust CA O=SecureTrust Corporation
+# Subject: CN=SecureTrust CA O=SecureTrust Corporation
+# Label: "SecureTrust CA"
+# Serial: 17199774589125277788362757014266862032
+# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1
+# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11
+# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73
+-----BEGIN CERTIFICATE-----
+MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
+MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
+cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
+Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
+0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
+wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
+7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
+8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
+BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
+JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
+6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
+3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
+D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
+CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
+3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Secure Global CA O=SecureTrust Corporation
+# Subject: CN=Secure Global CA O=SecureTrust Corporation
+# Label: "Secure Global CA"
+# Serial: 9751836167731051554232119481456978597
+# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de
+# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b
+# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69
+-----BEGIN CERTIFICATE-----
+MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
+MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
+GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
+MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
+Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
+iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
+/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
+jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
+HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
+sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
+gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
+KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
+AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
+URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
+H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
+I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
+iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
+f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
+-----END CERTIFICATE-----
+
+# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
+# Subject: CN=COMODO Certification Authority O=COMODO CA Limited
+# Label: "COMODO Certification Authority"
+# Serial: 104350513648249232941998508985834464573
+# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75
+# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b
+# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66
+-----BEGIN CERTIFICATE-----
+MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
+gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
+BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
+MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
+YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
+RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
+aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
+UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
+2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
+Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
++2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
+nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
+/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
+PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
+QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
+SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
+IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
+RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
+zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
+BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
+ZQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
+# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
+# Label: "COMODO ECC Certification Authority"
+# Serial: 41578283867086692638256921589707938090
+# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23
+# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11
+# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
+IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
+MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
+ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
+T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
+FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
+cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
+BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
+fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
+GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certigna O=Dhimyotis
+# Subject: CN=Certigna O=Dhimyotis
+# Label: "Certigna"
+# Serial: 18364802974209362175
+# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff
+# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97
+# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d
+-----BEGIN CERTIFICATE-----
+MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
+BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
+DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
+BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
+QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
+gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
+zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
+130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
+JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
+ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
+AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
+AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
+9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
+bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
+fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
+HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
+t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
+WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
+-----END CERTIFICATE-----
+
+# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
+# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority
+# Label: "ePKI Root Certification Authority"
+# Serial: 28956088682735189655030529057352760477
+# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3
+# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0
+# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5
+-----BEGIN CERTIFICATE-----
+MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
+Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
+IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
+SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
+SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
+ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
+DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
+TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
+fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
+sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
+WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
+nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
+dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
+NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
+AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
+MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
+ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
+uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
+PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
+JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
+gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
+j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
+5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
+o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
+/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
+Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
+W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
+hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
+-----END CERTIFICATE-----
+
+# Issuer: O=certSIGN OU=certSIGN ROOT CA
+# Subject: O=certSIGN OU=certSIGN ROOT CA
+# Label: "certSIGN ROOT CA"
+# Serial: 35210227249154
+# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17
+# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b
+# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb
+-----BEGIN CERTIFICATE-----
+MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
+AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
+QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
+MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
+0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
+UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
+RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
+OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
+JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
+AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
+BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
+LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
+MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
+44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
+Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
+i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
+9u6wWk5JRFRYX0KD
+-----END CERTIFICATE-----
+
+# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
+# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
+# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny"
+# Serial: 80544274841616
+# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88
+# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91
+# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post
+# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post
+# Label: "Hongkong Post Root CA 1"
+# Serial: 1000
+# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca
+# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58
+# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2
+-----BEGIN CERTIFICATE-----
+MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
+FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
+Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
+A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
+b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
+jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
+PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
+ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
+nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
+q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
+MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
+mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
+7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
+oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
+EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
+fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
+AmvZWg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc.
+# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc.
+# Label: "SecureSign RootCA11"
+# Serial: 1
+# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26
+# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3
+# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12
+-----BEGIN CERTIFICATE-----
+MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
+MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
+A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
+MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
+Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
+QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
+i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
+h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
+MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
+UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
+8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
+h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
+VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
+AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
+KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
+X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
+QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
+pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
+QSdJQO7e5iNEOdyhIta6A/I=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
+# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
+# Label: "Microsec e-Szigno Root CA 2009"
+# Serial: 14014712776195784473
+# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1
+# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e
+# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
+# Label: "GlobalSign Root CA - R3"
+# Serial: 4835703278459759426209954
+# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28
+# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad
+# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+
+# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068"
+# Serial: 6047274297262753887
+# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3
+# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa
+# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
+MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
+VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
+VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
+cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
+ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
+AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
+661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
+am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
+ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
+PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
+3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
+SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
+3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
+ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
+StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
+Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
+jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
+-----END CERTIFICATE-----
+
+# Issuer: CN=Izenpe.com O=IZENPE S.A.
+# Subject: CN=Izenpe.com O=IZENPE S.A.
+# Label: "Izenpe.com"
+# Serial: 917563065490389241595536686991402621
+# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73
+# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19
+# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Label: "Go Daddy Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01
+# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b
+# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Label: "Starfield Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96
+# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e
+# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Label: "Starfield Services Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2
+# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f
+# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
+ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
+dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
+OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
+8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
+Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
+hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
+6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
+AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
+bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
+ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
+qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
+0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
+sSi6
+-----END CERTIFICATE-----
+
+# Issuer: CN=AffirmTrust Commercial O=AffirmTrust
+# Subject: CN=AffirmTrust Commercial O=AffirmTrust
+# Label: "AffirmTrust Commercial"
+# Serial: 8608355977964138876
+# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7
+# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7
+# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
+Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
+ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
+MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
+yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
+VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
+nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
+XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
+vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
+Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
+N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
+nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
+-----END CERTIFICATE-----
+
+# Issuer: CN=AffirmTrust Networking O=AffirmTrust
+# Subject: CN=AffirmTrust Networking O=AffirmTrust
+# Label: "AffirmTrust Networking"
+# Serial: 8957382827206547757
+# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f
+# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f
+# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
+dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
+MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
+cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
+YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
+kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
+QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
+6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
+yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
+QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
+KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
+tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
+QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
+Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
+olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
+x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
+-----END CERTIFICATE-----
+
+# Issuer: CN=AffirmTrust Premium O=AffirmTrust
+# Subject: CN=AffirmTrust Premium O=AffirmTrust
+# Label: "AffirmTrust Premium"
+# Serial: 7893706540734352110
+# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57
+# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27
+# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
+BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
+dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
+A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
+cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
+qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
+JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
++jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
+s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
+HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
+70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
+V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
+qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
+5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
+C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
+OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
+FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
+KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
+Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
+8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
+MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
+0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
+u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
+u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
+YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
+GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
+RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
+KeC2uAloGRwYQw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust
+# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust
+# Label: "AffirmTrust Premium ECC"
+# Serial: 8401224907861490260
+# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d
+# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb
+# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23
+-----BEGIN CERTIFICATE-----
+MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
+VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
+cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
+BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
+VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
+0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
+ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
+A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
+A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
+aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
+flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Network CA"
+# Serial: 279744
+# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78
+# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e
+# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
+MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
+ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
+cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
+WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
+Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
+IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
+UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
+TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
+BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
+kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
+AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
+HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
+sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
+I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
+J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
+VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
+# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
+# Label: "TWCA Root Certification Authority"
+# Serial: 1
+# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79
+# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48
+# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
+MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
+V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
+WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
+LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
+AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
+K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
+RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
+rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
+3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
+hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
+MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
+XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
+lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
+aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
+YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
+# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
+# Label: "Security Communication RootCA2"
+# Serial: 0
+# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43
+# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74
+# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
+DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
+dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
+YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
+OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
+zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
+VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
+hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
+ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
+awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
+OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
+coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
+okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
+t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
+1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
+SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
+# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
+# Label: "Actalis Authentication Root CA"
+# Serial: 6271844772424770508
+# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6
+# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac
+# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
+BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
+MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
+SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
+ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
+UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
+4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
+KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
+gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
+rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
+51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
+be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
+KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
+v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
+fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
+jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
+ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
+e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
+jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
+WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
+SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
+pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
+X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
+fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
+K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
+ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
+LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
+LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
+# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
+# Label: "Buypass Class 2 Root CA"
+# Serial: 2
+# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29
+# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99
+# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
+6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
+L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
+1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
+MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
+QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
+arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
+Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
+FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
+P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
+9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
+uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
+9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
+OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
+KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
+DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
+H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
+I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
+5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
+3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
+Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
+# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
+# Label: "Buypass Class 3 Root CA"
+# Serial: 2
+# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec
+# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57
+# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
+ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
+N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
+tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
+0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
+/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
+KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
+zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
+O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
+34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
+K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
+Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
+QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
+IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
+HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
+O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
+033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
+dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
+kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
+3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
+u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
+4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Label: "T-TeleSec GlobalRoot Class 3"
+# Serial: 1
+# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef
+# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1
+# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
+8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
+RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
+hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
+ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
+EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
+A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
+WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
+1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
+6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
+91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
+TpPDpFQUWw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
+# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
+# Label: "D-TRUST Root Class 3 CA 2 2009"
+# Serial: 623603
+# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f
+# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0
+# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
+ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
+HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
+UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
+tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
+ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
+lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
+/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
+A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
+dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
+MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
+cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
+L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
+BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
+acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
+zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
+PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
+Johw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
+# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
+# Label: "D-TRUST Root Class 3 CA 2 EV 2009"
+# Serial: 623604
+# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6
+# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83
+# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
+NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
+BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
+ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
+3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
+qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
+p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
+HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
+ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
+HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
+Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
+c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
+RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
+dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
+Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
+3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
+CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
+xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
+KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+
+# Issuer: CN=CA Disig Root R2 O=Disig a.s.
+# Subject: CN=CA Disig Root R2 O=Disig a.s.
+# Label: "CA Disig Root R2"
+# Serial: 10572350602393338211
+# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03
+# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71
+# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
+MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
+NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
+PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
+x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
+QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
+yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
+QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
+H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
+QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
+i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
+nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
+rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
+hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
+GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
+lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
+TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
+nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
+gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
+G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
+zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
+L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+
+# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
+# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
+# Label: "ACCVRAIZ1"
+# Serial: 6828503384748696800
+# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02
+# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17
+# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
+AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
+CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
+BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
+VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
+qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
+HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
+G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
+lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
+IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
+0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
+k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
+4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
+m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
+cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
+uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
+KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
+ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
+AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
+VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
+CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
+cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
+QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
+7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
+cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
+QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
+czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
+aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
+aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
+BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
+D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
+JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
+AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
+vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
+tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
+7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
+h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
+d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
+pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+
+# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
+# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
+# Label: "TWCA Global Root CA"
+# Serial: 3262
+# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96
+# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65
+# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
+EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
+VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
+NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
+B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
+10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
+0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
+MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
+zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
+46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
+yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
+laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
+oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
+BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
+qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
+4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
+1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
+H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
+RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
+15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
+6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
+nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
+wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
+aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
+KwbQBM0=
+-----END CERTIFICATE-----
+
+# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera
+# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera
+# Label: "TeliaSonera Root CA v1"
+# Serial: 199041966741090107964904287217786801558
+# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c
+# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37
+# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89
+-----BEGIN CERTIFICATE-----
+MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
+NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
+b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
+VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
+VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
+7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
+Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
+/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
+81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
+dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
+Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
+sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
+pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
+slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
+arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
+VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
+9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
+dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
+0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
+TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
+Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
+Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
+OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
+vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
+t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
+HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
+SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
+-----END CERTIFICATE-----
+
+# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi
+# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi
+# Label: "E-Tugra Certification Authority"
+# Serial: 7667447206703254355
+# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49
+# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39
+# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
+BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
+aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
+BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
+Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
+MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
+em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
+ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
+B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
+D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
+Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
+q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
+k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
+fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
+dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
+ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
+zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
+rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
+U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
+Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
+XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
+Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
+HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
+GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
+77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
+vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
+FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
+yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
+AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
+y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
+NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Label: "T-TeleSec GlobalRoot Class 2"
+# Serial: 1
+# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a
+# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9
+# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
+AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
+FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
+1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
+jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
+wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
+WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
+NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
+uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
+IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
+g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
+BSeOE6Fuwg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Atos TrustedRoot 2011 O=Atos
+# Subject: CN=Atos TrustedRoot 2011 O=Atos
+# Label: "Atos TrustedRoot 2011"
+# Serial: 6643877497813316402
+# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56
+# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21
+# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
+AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
+EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
+FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
+REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
+Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
+VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
+SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
+4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
+cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
+eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
+A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
+DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
+vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
+DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
+maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
+lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
+KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 1 G3"
+# Serial: 687049649626669250736271037606554624078720034195
+# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab
+# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67
+# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
+MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
+wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
+rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
+68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
+4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
+UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
+abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
+3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
+KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
+hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
+Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
+zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
+ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
+cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
+qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
+YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
+b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
+8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
+NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
+ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
+q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
+nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 2 G3"
+# Serial: 390156079458959257446133169266079962026824725800
+# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06
+# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36
+# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
+MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
+qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
+n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
+c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
+o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
+IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
+IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
+8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
+vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
+7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
+cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
+ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
+roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
+W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
+lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
+csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
+dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
+KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
+HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
+WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 3 G3"
+# Serial: 268090761170461462463995952157327242137089239581
+# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7
+# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d
+# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
+MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
+/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
+FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
+U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
+ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
+FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
+A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
+eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
+sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
+VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
+A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
+ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
+FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
+oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
+u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
+0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
+3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
+8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
+PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
+ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Assured ID Root G2"
+# Serial: 15385348160840213938643033620894905419
+# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d
+# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f
+# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
+n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
+biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
+EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
+bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
+YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
+BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
+QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
+0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
+lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
+B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
+ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
+IhNzbM8m9Yop5w==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Assured ID Root G3"
+# Serial: 15459312981008553731928384953135426796
+# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb
+# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89
+# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2
+-----BEGIN CERTIFICATE-----
+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
+RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
+Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
+RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
+AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
+JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
+6pZjamVFkpUBtA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root G2"
+# Serial: 4293743540046975378534879503202253541
+# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44
+# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4
+# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root G3"
+# Serial: 7089244469030293291760083333884364146
+# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca
+# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e
+# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Trusted Root G4"
+# Serial: 7451500558977370777930084869016614236
+# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49
+# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4
+# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----END CERTIFICATE-----
+
+# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited
+# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited
+# Label: "COMODO RSA Certification Authority"
+# Serial: 101909084537582093308941363524873193117
+# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18
+# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4
+# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34
+-----BEGIN CERTIFICATE-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
+
+# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Label: "USERTrust RSA Certification Authority"
+# Serial: 2645093764781058787591871645665788717
+# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5
+# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e
+# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+
+# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
+# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
+# Label: "USERTrust ECC Certification Authority"
+# Serial: 123013823720199481456569720443997572134
+# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1
+# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0
+# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
+eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
+JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
+VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
+I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
+o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
+A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
+zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
+RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
+# Label: "GlobalSign ECC Root CA - R5"
+# Serial: 32785792099990507226680698011560947931244
+# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08
+# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa
+# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24
+-----BEGIN CERTIFICATE-----
+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
+8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
+hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
+KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
+515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
+xwy8p2Fp8fc74SrL+SvzZpA3
+-----END CERTIFICATE-----
+
+# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
+# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
+# Label: "IdenTrust Commercial Root CA 1"
+# Serial: 13298821034946342390520003877796839426
+# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7
+# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25
+# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
+VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
+MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
+JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
+3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
+S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
+bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
+T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
+vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
+Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
+dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
+c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
+l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
+iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
+ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
+6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
+LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
+nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
+W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
+AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
+l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
+4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
+mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
+7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
+-----END CERTIFICATE-----
+
+# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
+# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
+# Label: "IdenTrust Public Sector Root CA 1"
+# Serial: 13298821034946342390521976156843933698
+# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba
+# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd
+# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
+VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
+MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
+MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
+ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
+RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
+bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
+/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
+3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
+EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
+9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
+GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
+2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
+WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
+W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
+AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
+t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
+DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
+TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
+lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
+mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
+WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
+tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
+GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
+8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
+-----END CERTIFICATE-----
+
+# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only
+# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only
+# Label: "Entrust Root Certification Authority - G2"
+# Serial: 1246989352
+# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2
+# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4
+# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39
+-----BEGIN CERTIFICATE-----
+MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
+VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
+cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
+IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
+dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
+NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
+dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
+dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
+aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
+YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
+RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
+cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
+wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
+U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
+jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
+BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
+jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
+Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
+1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
+nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
+VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only
+# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only
+# Label: "Entrust Root Certification Authority - EC1"
+# Serial: 51543124481930649114116133369
+# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc
+# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47
+# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5
+-----BEGIN CERTIFICATE-----
+MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
+A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
+d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
+dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
+RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
+MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
+VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
+L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
+Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
+ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
+A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
+ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
+Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
+BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
+R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
+hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
+-----END CERTIFICATE-----
+
+# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority
+# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority
+# Label: "CFCA EV ROOT"
+# Serial: 407555286
+# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30
+# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83
+# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
+TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
+MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
+aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
+T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
+sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
+TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
+/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
+7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
+EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
+hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
+a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
+aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
+TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
+PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
+cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
+tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
+BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
+ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
+ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
+jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
+ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
+P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
+xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
+Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
+5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
+/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
+AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
+5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
+# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
+# Label: "OISTE WISeKey Global Root GB CA"
+# Serial: 157768595616588414422159278966750757568
+# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d
+# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed
+# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
+MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
+Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
+YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x
+CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG
+b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
+bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3
+HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx
+WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX
+1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk
+u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P
+99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r
+M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB
+BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh
+cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5
+gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO
+ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf
+aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
+Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
+# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
+# Label: "SZAFIR ROOT CA2"
+# Serial: 357043034767186914217277344587386743377558296292
+# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99
+# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de
+# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe
+-----BEGIN CERTIFICATE-----
+MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL
+BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6
+ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw
+NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L
+cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg
+Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN
+QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT
+3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw
+3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6
+3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5
+BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN
+XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF
+AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw
+8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG
+nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP
+oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy
+d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg
+LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Network CA 2"
+# Serial: 44979900017204383099463764357512596969
+# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2
+# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92
+# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04
+-----BEGIN CERTIFICATE-----
+MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB
+gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu
+QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG
+A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz
+OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ
+VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3
+b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA
+DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn
+0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB
+OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE
+fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E
+Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m
+o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i
+sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW
+OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez
+Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS
+adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n
+3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ
+F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf
+CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29
+XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm
+djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/
+WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb
+AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq
+P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko
+b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj
+XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P
+5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi
+DrW5viSP
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Label: "Hellenic Academic and Research Institutions RootCA 2015"
+# Serial: 0
+# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce
+# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6
+# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36
+-----BEGIN CERTIFICATE-----
+MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix
+DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k
+IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT
+N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v
+dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG
+A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh
+ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx
+QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
+dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA
+4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0
+AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10
+4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C
+ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV
+9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD
+gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6
+Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq
+NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko
+LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
+Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd
+ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I
+XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI
+M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot
+9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V
+Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea
+j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh
+X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ
+l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf
+bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4
+pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK
+e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0
+vm9qp/UsQu0yrbYhnr68
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015"
+# Serial: 0
+# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef
+# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66
+# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33
+-----BEGIN CERTIFICATE-----
+MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN
+BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl
+bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv
+b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ
+BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj
+YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5
+MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0
+dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg
+QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa
+jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi
+C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep
+lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
+TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
+-----END CERTIFICATE-----
+
+# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
+# Subject: CN=ISRG Root X1 O=Internet Security Research Group
+# Label: "ISRG Root X1"
+# Serial: 172886928669790476064670243504169061120
+# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
+# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
+# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----
+
+# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
+# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
+# Label: "AC RAIZ FNMT-RCM"
+# Serial: 485876308206448804701554682760554759
+# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d
+# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20
+# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
+CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
+WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
+BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG
+Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/
+yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf
+BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz
+WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF
+tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z
+374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC
+IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL
+mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7
+wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS
+MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2
+ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet
+UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H
+YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3
+LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
+nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1
+RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM
+LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf
+77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N
+JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm
+fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp
+6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp
+1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B
+9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok
+RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv
+uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 1 O=Amazon
+# Subject: CN=Amazon Root CA 1 O=Amazon
+# Label: "Amazon Root CA 1"
+# Serial: 143266978916655856878034712317230054538369994
+# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6
+# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16
+# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
+ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
+9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
+IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
+VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
+93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
+jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
+A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
+U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
+N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
+o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
+5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
+rqXRfboQnoZsG4q5WTP468SQvvG5
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 2 O=Amazon
+# Subject: CN=Amazon Root CA 2 O=Amazon
+# Label: "Amazon Root CA 2"
+# Serial: 143266982885963551818349160658925006970653239
+# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66
+# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a
+# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK
+gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ
+W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg
+1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K
+8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r
+2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me
+z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR
+8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj
+mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz
+7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6
++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI
+0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB
+Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm
+UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2
+LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS
+k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl
+7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm
+btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl
+urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+
+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63
+n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
+76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H
+9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT
+4PsJYGw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 3 O=Amazon
+# Subject: CN=Amazon Root CA 3 O=Amazon
+# Label: "Amazon Root CA 3"
+# Serial: 143266986699090766294700635381230934788665930
+# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87
+# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e
+# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4
+-----BEGIN CERTIFICATE-----
+MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
+Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
+A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
+Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
+ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
+ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
+BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
+YyRIHN8wfdVoOw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 4 O=Amazon
+# Subject: CN=Amazon Root CA 4 O=Amazon
+# Label: "Amazon Root CA 4"
+# Serial: 143266989758080763974105200630763877849284878
+# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd
+# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be
+# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92
+-----BEGIN CERTIFICATE-----
+MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
+Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
+A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
+Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
+9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
+M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
+MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
+CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
+1KyLa2tJElMzrdfkviT8tQp21KW8EA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
+# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
+# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1"
+# Serial: 1
+# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49
+# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca
+# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16
+-----BEGIN CERTIFICATE-----
+MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp
+bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w
+KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0
+BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy
+dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG
+EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll
+IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU
+QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT
+TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg
+LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7
+a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr
+LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr
+N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X
+YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/
+iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f
+AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH
+V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
+AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf
+IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4
+lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c
+8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf
+lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
+# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
+# Label: "GDCA TrustAUTH R5 ROOT"
+# Serial: 9009899650740120186
+# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4
+# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4
+# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93
+-----BEGIN CERTIFICATE-----
+MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE
+BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
+IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0
+MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV
+BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w
+HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj
+Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj
+TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u
+KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj
+qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm
+MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12
+ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP
+zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk
+L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC
+jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA
+HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC
+AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
+p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm
+DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5
+COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry
+L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf
+JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg
+IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io
+2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV
+09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ
+XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq
+T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe
+MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
+# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
+# Label: "SSL.com Root Certification Authority RSA"
+# Serial: 8875640296558310041
+# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29
+# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb
+# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69
+-----BEGIN CERTIFICATE-----
+MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
+BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK
+DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz
+OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
+dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
+bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R
+xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX
+qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC
+C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3
+6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh
+/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF
+YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E
+JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc
+US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8
+ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm
++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi
+M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G
+A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV
+cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc
+Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs
+PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/
+q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0
+cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr
+a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I
+H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y
+K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu
+nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf
+oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY
+Ic2wBlX7Jz9TkHCpBB5XJ7k=
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Label: "SSL.com Root Certification Authority ECC"
+# Serial: 8495723813297216424
+# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e
+# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a
+# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65
+-----BEGIN CERTIFICATE-----
+MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
+VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
+U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
+WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
+b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
+b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
+7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
+CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
+EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
+VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
+kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
+gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
+# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
+# Label: "SSL.com EV Root Certification Authority RSA R2"
+# Serial: 6248227494352943350
+# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95
+# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a
+# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c
+-----BEGIN CERTIFICATE-----
+MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
+CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy
+MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G
+A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD
+DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq
+M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf
+OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa
+4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9
+HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR
+aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA
+b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ
+Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV
+PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO
+pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu
+UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY
+MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
+HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4
+9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW
+s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5
+Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg
+cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM
+79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz
+/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt
+ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm
+Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK
+QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ
+w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi
+S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07
+mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
+# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
+# Label: "SSL.com EV Root Certification Authority ECC"
+# Serial: 3182246526754555285
+# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90
+# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d
+# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8
+-----BEGIN CERTIFICATE-----
+MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC
+VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
+U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx
+NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
+dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv
+bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA
+VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku
+WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP
+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
+5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ
+ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
+h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
+# Label: "GlobalSign Root CA - R6"
+# Serial: 1417766617973444989252670301619537
+# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
+# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
+# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
+MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
+MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
+MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
+xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
+ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
+aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
+LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
+1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
+k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
+SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
+bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
+WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
+rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
+MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
+bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
+nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
+Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
+55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
+vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
+cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
+oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
+nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
+pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
+JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
+8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
+5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
+# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
+# Label: "OISTE WISeKey Global Root GC CA"
+# Serial: 44084345621038548146064804565436152554
+# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
+# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
+# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
+-----BEGIN CERTIFICATE-----
+MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
+CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
+bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
+Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
+BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
+ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
+b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
+eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
+p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
+rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
+57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
+Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
+-----END CERTIFICATE-----
+
+# Issuer: CN=UCA Global G2 Root O=UniTrust
+# Subject: CN=UCA Global G2 Root O=UniTrust
+# Label: "UCA Global G2 Root"
+# Serial: 124779693093741543919145257850076631279
+# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
+# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
+# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
+MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
+bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
+CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
+b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
+b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
+kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
+VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
+VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
+C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
+tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
+D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
+j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
+NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
+iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
+O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
+ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
+L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
+1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
+1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
+b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
+PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
+y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
+EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
+DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
+YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
+UB+K+wb1whnw0A==
+-----END CERTIFICATE-----
+
+# Issuer: CN=UCA Extended Validation Root O=UniTrust
+# Subject: CN=UCA Extended Validation Root O=UniTrust
+# Label: "UCA Extended Validation Root"
+# Serial: 106100277556486529736699587978573607008
+# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
+# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
+# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
+MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
+eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
+MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
+BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
+D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
+sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
+O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
+sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
+c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
+VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
+KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
+TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
+sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
+1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
+fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
+l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
+ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
+VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
+c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
+4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
+t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
+2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
+vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
+xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
+cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
+fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
+# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
+# Label: "Certigna Root CA"
+# Serial: 269714418870597844693661054334862075617
+# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
+# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
+# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
+-----BEGIN CERTIFICATE-----
+MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
+WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
+MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
+MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
+VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
+BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
+ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
+CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
+I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
+TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
+C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
+ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
+IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
+Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
+JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
+hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
+GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
+1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
+L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
+dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
+aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
+hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
+6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
+HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
+0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
+lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
+o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
+gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
+faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
+Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
+jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
+3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign Root CA - G1"
+# Serial: 235931866688319308814040
+# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
+# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
+# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
+VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
+ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
+MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
+MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
+Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
+f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
+8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
+d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
+tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
+Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
+o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
+PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
+wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
+GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
+6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
+RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
+iN66zB+Afko=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign ECC Root CA - G3"
+# Serial: 287880440101571086945156
+# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
+# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
+# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
+-----BEGIN CERTIFICATE-----
+MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
+EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
+bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
+RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
+TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
+b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
+djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
+WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
+fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
+zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
+hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
+CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
++JbNR6iC8hZVdyR+EhCVBCyj
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign Root CA - C1"
+# Serial: 825510296613316004955058
+# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
+# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
+# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
+-----BEGIN CERTIFICATE-----
+MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
+A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
+SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
+dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
+BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
+HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
+3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
+GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
+xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
+aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
+TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
+/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
+kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
+YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
+WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign ECC Root CA - C3"
+# Serial: 582948710642506000014504
+# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
+# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
+# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
+-----BEGIN CERTIFICATE-----
+MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
+EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
+IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
+IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
+MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
+sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
+BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
+Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
+3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
+0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Label: "Hongkong Post Root CA 3"
+# Serial: 46170865288971385588281144162979347873371282084
+# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
+# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
+# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
+-----BEGIN CERTIFICATE-----
+MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
+BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
+SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
+a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
+NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
+CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
+Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
+dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
+VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
+9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
+2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
+vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
+bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
+x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
+l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
+TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
+Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
+i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
+DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
+7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
+MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
+gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
+GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
+3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
+Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
+l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
+JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
+L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
+LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
+mpv0
+-----END CERTIFICATE-----
+
+# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only
+# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only
+# Label: "Entrust Root Certification Authority - G4"
+# Serial: 289383649854506086828220374796556676440
+# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88
+# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01
+# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88
+-----BEGIN CERTIFICATE-----
+MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw
+gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL
+Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg
+MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw
+BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0
+MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1
+c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ
+bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg
+Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B
+AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ
+2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E
+T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j
+5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM
+C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T
+DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX
+wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A
+2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm
+nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
+dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl
+N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj
+c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS
+5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS
+Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr
+hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/
+B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI
+AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw
+H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+
+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk
+2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol
+IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk
+5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY
+n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
+# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
+# Label: "Microsoft ECC Root Certificate Authority 2017"
+# Serial: 136839042543790627607696632466672567020
+# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67
+# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5
+# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02
+-----BEGIN CERTIFICATE-----
+MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
+VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
+MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV
+UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy
+b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR
+ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb
+hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3
+FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV
+L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB
+iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
+# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
+# Label: "Microsoft RSA Root Certificate Authority 2017"
+# Serial: 40975477897264996090493496164228220339
+# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47
+# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74
+# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0
+-----BEGIN CERTIFICATE-----
+MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
+MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
+NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG
+EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N
+aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ
+Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0
+ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1
+HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm
+gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ
+jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc
+aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG
+YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6
+W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K
+UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH
++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q
+W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC
+LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC
+gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6
+tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh
+SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2
+TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3
+pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR
+xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp
+GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9
+dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN
+AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
+RA+GsCyRxj3qrg+E
+-----END CERTIFICATE-----
+
+# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
+# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
+# Label: "e-Szigno Root CA 2017"
+# Serial: 411379200276854331539784714
+# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98
+# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1
+# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99
+-----BEGIN CERTIFICATE-----
+MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
+BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
+LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
+b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
+THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
+IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
+xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
+Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
+eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
+jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
++efcMQ==
+-----END CERTIFICATE-----
+
+# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2
+# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2
+# Label: "certSIGN Root CA G2"
+# Serial: 313609486401300475190
+# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7
+# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32
+# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
+BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
+Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
+BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ
+R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF
+dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw
+vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ
+uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp
+n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs
+cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW
+xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P
+rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF
+DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx
+DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy
+LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C
+eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ
+d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq
+kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
+b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl
+qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0
+OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c
+NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk
+ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO
+pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj
+03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk
+PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE
+1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX
+QRBdJ3NghVdJIgc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
+# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc.
+# Label: "Trustwave Global Certification Authority"
+# Serial: 1846098327275375458322922162
+# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e
+# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5
+# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8
+-----BEGIN CERTIFICATE-----
+MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw
+CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x
+ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1
+c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx
+OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI
+SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI
+b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn
+swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu
+7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8
+1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW
+80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP
+JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l
+RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw
+hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10
+coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc
+BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n
+twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
+EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud
+DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W
+0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe
+uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q
+lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB
+aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE
+sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT
+MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe
+qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh
+VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8
+h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9
+EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK
+yeC2nOnOcXHebD8WpHk=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
+# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc.
+# Label: "Trustwave Global ECC P256 Certification Authority"
+# Serial: 4151900041497450638097112925
+# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54
+# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf
+# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4
+-----BEGIN CERTIFICATE-----
+MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD
+VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
+BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
+YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
+NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G
+A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
+d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
+Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG
+SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN
+FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w
+DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw
+CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh
+DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
+-----END CERTIFICATE-----
+
+# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
+# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc.
+# Label: "Trustwave Global ECC P384 Certification Authority"
+# Serial: 2704997926503831671788816187
+# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6
+# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2
+# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97
+-----BEGIN CERTIFICATE-----
+MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD
+VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf
+BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3
+YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x
+NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G
+A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0
+d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF
+Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ
+j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF
+1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G
+A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3
+AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC
+MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu
+Sw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
+# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
+# Label: "NAVER Global Root Certification Authority"
+# Serial: 9013692873798656336226253319739695165984492813
+# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b
+# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1
+# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65
+-----BEGIN CERTIFICATE-----
+MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM
+BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG
+T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx
+CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD
+b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA
+iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH
+38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE
+HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz
+kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP
+szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq
+vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf
+nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG
+YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo
+0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a
+CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K
+AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I
+36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN
+qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj
+cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm
++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL
+hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe
+lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7
+p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8
+piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR
+LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX
+5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO
+dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul
+9XXeifdy
+-----END CERTIFICATE-----
+
+# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
+# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
+# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS"
+# Serial: 131542671362353147877283741781055151509
+# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb
+# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a
+# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb
+-----BEGIN CERTIFICATE-----
+MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw
+CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw
+FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S
+Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5
+MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL
+DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS
+QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH
+sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK
+Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu
+SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC
+MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy
+v+c=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa
+# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa
+# Label: "GlobalSign Root R46"
+# Serial: 1552617688466950547958867513931858518042577
+# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef
+# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90
+# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA
+MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
+VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy
+MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt
+c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ
+OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG
+vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud
+316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo
+0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE
+y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF
+zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE
++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN
+I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs
+x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa
+ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC
+4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4
+7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
+JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti
+2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk
+pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF
+FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt
+rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk
+ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5
+u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP
+4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6
+N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3
+vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa
+# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa
+# Label: "GlobalSign Root E46"
+# Serial: 1552617690338932563915843282459653771421763
+# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f
+# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84
+# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58
+-----BEGIN CERTIFICATE-----
+MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx
+CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD
+ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw
+MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex
+HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq
+R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd
+yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ
+7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8
++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH
+# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH
+# Label: "GLOBALTRUST 2020"
+# Serial: 109160994242082918454945253
+# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8
+# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2
+# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a
+-----BEGIN CERTIFICATE-----
+MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG
+A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw
+FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx
+MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u
+aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b
+RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z
+YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3
+QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw
+yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+
+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ
+SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH
+r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0
+4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me
+dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw
+q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2
+nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu
+H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
+VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC
+XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd
+6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf
++I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi
+kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7
+wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB
+TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C
+MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn
+4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I
+aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy
+qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
+# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
+# Label: "ANF Secure Server Root CA"
+# Serial: 996390341000653745
+# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96
+# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74
+# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99
+-----BEGIN CERTIFICATE-----
+MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV
+BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk
+YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV
+BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN
+MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF
+UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD
+VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v
+dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj
+cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q
+yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH
+2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX
+H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL
+zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR
+p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz
+W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/
+SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn
+LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3
+n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B
+u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
+o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L
+9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej
+rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK
+pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0
+vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq
+OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ
+/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9
+2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI
++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2
+MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo
+tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Label: "Certum EC-384 CA"
+# Serial: 160250656287871593594747141429395092468
+# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1
+# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed
+# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6
+-----BEGIN CERTIFICATE-----
+MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw
+CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw
+JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT
+EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0
+WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT
+LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX
+BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE
+KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm
+Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8
+EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J
+UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn
+nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Root CA"
+# Serial: 40870380103424195783807378461123655149
+# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29
+# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5
+# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd
+-----BEGIN CERTIFICATE-----
+MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6
+MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
+MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV
+BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw
+MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg
+U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ
+n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q
+p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq
+NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF
+8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3
+HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa
+mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi
+7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF
+ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P
+qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ
+v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6
+Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
+vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD
+ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4
+WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo
+zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR
+5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ
+GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf
+5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq
+0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D
+P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM
+qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP
+0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf
+E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
+-----END CERTIFICATE-----
+
+# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Label: "TunTrust Root CA"
+# Serial: 108534058042236574382096126452369648152337120275
+# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4
+# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb
+# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
+Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
+b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG
+EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u
+IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ
+n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd
+2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF
+VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ
+GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF
+li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU
+r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2
+eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb
+MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg
+jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB
+7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW
+5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE
+ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
+90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z
+xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu
+QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4
+FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH
+22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP
+xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn
+dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5
+Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b
+nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ
+CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH
+u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj
+d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS RSA Root CA 2021"
+# Serial: 76817823531813593706434026085292783742
+# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91
+# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d
+# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
+Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL
+MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
+YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv
+b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l
+mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE
+4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv
+a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M
+pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw
+Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b
+LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY
+AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB
+AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq
+E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr
+W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ
+CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU
+X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3
+f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja
+H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP
+JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P
+zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt
+jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0
+/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT
+BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79
+aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW
+xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
+63ZTGI0RmLo=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS ECC Root CA 2021"
+# Serial: 137515985548005187474074462014555733966
+# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
+# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
+# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
+-----BEGIN CERTIFICATE-----
+MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
+CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
+cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
+dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG
+A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
+aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg
+Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7
+KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y
+STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD
+AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw
+SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN
+nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
+-----END CERTIFICATE-----
+
+# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068"
+# Serial: 1977337328857672817
+# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3
+# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe
+# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
+MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
+tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
+IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
+AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
+ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
+iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
+Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
+hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
+Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
+EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
+1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
+CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
+5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
+f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
+ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
+GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus ECC Root CA"
+# Serial: 630369271402956006249506845124680065938238527194
+# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85
+# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1
+# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3
+-----BEGIN CERTIFICATE-----
+MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw
+RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY
+BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz
+MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u
+LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0
+v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd
+e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw
+V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA
+AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG
+GJTO
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus Root CA"
+# Serial: 387574501246983434957692974888460947164905180485
+# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc
+# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7
+# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87
+-----BEGIN CERTIFICATE-----
+MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL
+BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x
+FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx
+MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s
+THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc
+IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU
+AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+
+GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9
+8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH
+flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt
+J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim
+0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN
+pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ
+UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW
+OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB
+AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet
+8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
+nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j
+bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM
+Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv
+TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS
+S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr
+I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9
+b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB
+UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P
+Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven
+sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=
+-----END CERTIFICATE-----
+
+# Issuer: CN=ISRG Root X2 O=Internet Security Research Group
+# Subject: CN=ISRG Root X2 O=Internet Security Research Group
+# Label: "ISRG Root X2"
+# Serial: 87493402998870891108772069816698636114
+# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
+# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
+# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+
+# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Label: "HiPKI Root CA - G1"
+# Serial: 60966262342023497858655262305426234976
+# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3
+# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60
+# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc
+-----BEGIN CERTIFICATE-----
+MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
+Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3
+YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw
+qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv
+Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6
+lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz
+Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ
+KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK
+FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj
+HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr
+y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ
+/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM
+a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6
+fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
+SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
+7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc
+SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza
+ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc
+XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg
+iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho
+L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF
+Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr
+kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+
+vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU
+YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Label: "GlobalSign ECC Root CA - R4"
+# Serial: 159662223612894884239637590694
+# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc
+# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28
+# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2
+-----BEGIN CERTIFICATE-----
+MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
+VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
+MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
+UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
+uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
+HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
+bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
+# Subject: CN=GTS Root R1 O=Google Trust Services LLC
+# Label: "GTS Root R1"
+# Serial: 159662320309726417404178440727
+# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
+# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
+# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
+# Subject: CN=GTS Root R2 O=Google Trust Services LLC
+# Label: "GTS Root R2"
+# Serial: 159662449406622349769042896298
+# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc
+# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94
+# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt
+nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY
+6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu
+MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k
+RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg
+f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV
++3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo
+dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW
+Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa
+G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq
+gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H
+vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
+0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC
+B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u
+NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg
+yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev
+HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6
+xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR
+TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg
+JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV
+7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl
+6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
+# Subject: CN=GTS Root R3 O=Google Trust Services LLC
+# Label: "GTS Root R3"
+# Serial: 159662495401136852707857743206
+# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73
+# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46
+# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
+jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
+4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
+VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
+ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
+# Subject: CN=GTS Root R4 O=Google Trust Services LLC
+# Label: "GTS Root R4"
+# Serial: 159662532700760215368942768210
+# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
+# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
+# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Label: "Telia Root CA v2"
+# Serial: 7288924052977061235122729490515358
+# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48
+# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd
+# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
+CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
+AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
+NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ
+MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq
+AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9
+vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9
+lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD
+n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT
+7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o
+6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC
+TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6
+WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R
+DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI
+pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj
+YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy
+rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
+8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi
+0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM
+A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS
+SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K
+TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF
+6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er
+3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt
+Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT
+VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW
+ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA
+rBPuUBQemMc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST BR Root CA 1 2020"
+# Serial: 165870826978392376648679885835942448534
+# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed
+# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67
+# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
+NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS
+zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0
+QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/
+VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW
+wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV
+dWNbFJWcHwHP2NVypw87
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST EV Root CA 1 2020"
+# Serial: 126288379621884218666039612629459926992
+# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e
+# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07
+# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
+NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC
+/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD
+wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3
+OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
+y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
+gfM0agPnIjhQW+0ZT0MW
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS ECC P384 Root G5"
+# Serial: 13129116028163249804115411775095713523
+# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed
+# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee
+# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05
+-----BEGIN CERTIFICATE-----
+MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
+Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
+MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
+bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
+7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
+0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
+B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
+BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
+LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
+DXZDjC5Ty3zfDBeWUA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS RSA4096 Root G5"
+# Serial: 11930366277458970227240571539258396554
+# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1
+# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35
+# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
+HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
+NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
+IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
+2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
+wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
+pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
+nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
+sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
+Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
+Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
+KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
+XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
+tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
+TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
+AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
+GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
+PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
+O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
+REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
+AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
+/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
+MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
+qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
+ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root R1 O=Certainly
+# Subject: CN=Certainly Root R1 O=Certainly
+# Label: "Certainly Root R1"
+# Serial: 188833316161142517227353805653483829216
+# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12
+# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af
+# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
+PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
+dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
+YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
+1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
+vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
+aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
+1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
+r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
+cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
+wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
+6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
+2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
+Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
+eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
+d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
+PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
+8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
+1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
+rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
+taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
+lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
+yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
+Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
+yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
+wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
+OV+KmalBWQewLK8=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root E1 O=Certainly
+# Subject: CN=Certainly Root E1 O=Certainly
+# Label: "Certainly Root E1"
+# Serial: 8168531406727139161245376702891150584
+# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9
+# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b
+# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
+CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
+bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
+eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
+QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
+hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
+ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
+BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
+-----END CERTIFICATE-----
+
+# Issuer: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Label: "E-Tugra Global Root CA RSA v3"
+# Serial: 75951268308633135324246244059508261641472512052
+# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4
+# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9
+# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2
+-----BEGIN CERTIFICATE-----
+MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQEL
+BQAwgYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUt
+VHVncmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYw
+JAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIFJTQSB2MzAeFw0yMDAzMTgw
+OTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMG
+QW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1
+Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBD
+QSBSU0EgdjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J7
+7gnJY9LTQ91ew6aEOErxjYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscx
+uj7X/iWpKo429NEvx7epXTPcMHD4QGxLsqYxYdE0PD0xesevxKenhOGXpOhL9hd8
+7jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF/YP9f4RtNGx/ardLAQO/
+rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8qQedmCeFL
+l+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bG
+wzrwbMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4
+znKS4iicvObpCdg604nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBO
+M/J+JjKsBY04pOZ2PJ8QaQ5tndLBeSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK
+5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiMbIedBi3x7+PmBvrFZhNb/FAH
+nnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbgh3cXTJ2w2Amo
+DVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
+AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSy
+tK7mLfcm1ap1LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEL
+BQADggIBAImocn+M684uGMQQgC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ
+6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN438o2Fi+CiJ+8EUdPdk3ILY7r3y18
+Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/qln0F7psTpURs+APQ
+3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3sSdPk
+vmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn9
+9t2HVhjYsCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQ
+mhty3QUBjYZgv6Rn7rWlDdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YA
+VSgU7NbHEqIbZULpkejLPoeJVF3Zr52XnGnnCv8PWniLYypMfUeUP95L6VPQMPHF
+9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFHIK+WEj5jlB0E5y67hscM
+moi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiXYY60MGo8
+bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ
+-----END CERTIFICATE-----
+
+# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center
+# Label: "E-Tugra Global Root CA ECC v3"
+# Serial: 218504919822255052842371958738296604628416471745
+# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64
+# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84
+# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13
+-----BEGIN CERTIFICATE-----
+MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw
+gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn
+cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD
+VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2
+NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r
+YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh
+IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF
+Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ
+KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK
+fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB
+Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C
+MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp
+ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6
+7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx
+vmjkI6TZraE3
+-----END CERTIFICATE-----
+
+# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD.
+# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD.
+# Label: "Security Communication RootCA3"
+# Serial: 16247922307909811815
+# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26
+# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a
+# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94
+-----BEGIN CERTIFICATE-----
+MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV
+BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw
+JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2
+MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
+U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg
+Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r
+CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA
+lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG
+TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7
+9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7
+8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4
+g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we
+GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst
++3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M
+0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ
+T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw
+HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS
+YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA
+FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd
+9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI
+UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+
+OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke
+gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf
+iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV
+nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD
+2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI//
+1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad
+TdJ0MN1kURXbg4NR16/9M51NZg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Label: "Security Communication ECC RootCA1"
+# Serial: 15446673492073852651
+# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86
+# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41
+# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11
+-----BEGIN CERTIFICATE-----
+MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
+AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
+VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
+NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT
+HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5
+IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl
+dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK
+ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu
+9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O
+be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
+-----END CERTIFICATE-----
diff --git a/tidy3d/plugins/smatrix/web/api/connect_util.py b/tidy3d/plugins/smatrix/web/api/connect_util.py
new file mode 100644
index 0000000000..137fe0d684
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/connect_util.py
@@ -0,0 +1,72 @@
+"""connect util for webapi."""
+
+from __future__ import annotations
+
+import time
+from functools import wraps
+
+from requests import ReadTimeout
+from requests.exceptions import ConnectionError as ConnErr
+from requests.exceptions import JSONDecodeError
+from urllib3.exceptions import NewConnectionError
+
+from tidy3d.exceptions import WebError
+from tidy3d.log import log
+
+# number of seconds to keep re-trying connection before erroring
+CONNECTION_RETRY_TIME = 180
+# time between checking task status
+REFRESH_TIME = 2
+
+
+def wait_for_connection(decorated_fn=None, wait_time_sec: float = CONNECTION_RETRY_TIME):
+ """Causes function to ignore connection errors and retry for ``wait_time_sec`` secs."""
+
+ def decorator(web_fn):
+ """Decorator returned by @wait_for_connection()"""
+
+ @wraps(web_fn)
+ def web_fn_wrapped(*args, **kwargs):
+ """Function to return including connection waiting."""
+ time_start = time.time()
+ warned_previously = False
+
+ while (time.time() - time_start) < wait_time_sec:
+ try:
+ return web_fn(*args, **kwargs)
+ except (ConnErr, ConnectionError, NewConnectionError, ReadTimeout, JSONDecodeError):
+ if not warned_previously:
+ log.warning(f"No connection: Retrying for {wait_time_sec} seconds.")
+ warned_previously = True
+ time.sleep(REFRESH_TIME)
+
+ raise WebError("No internet connection: giving up on connection waiting.")
+
+ return web_fn_wrapped
+
+ if decorated_fn:
+ return decorator(decorated_fn)
+
+ return decorator
+
+
+def get_time_steps_str(time_steps) -> str:
+ """get_time_steps_str"""
+ if time_steps < 1000:
+ time_steps_str = f"{time_steps}"
+ elif 1000 <= time_steps < 1000 * 1000:
+ time_steps_str = f"{time_steps / 1000}K"
+ else:
+ time_steps_str = f"{time_steps / 1000 / 1000}M"
+ return time_steps_str
+
+
+def get_grid_points_str(grid_points) -> str:
+ """get_grid_points_str"""
+ if grid_points < 1000:
+ grid_points_str = f"{grid_points}"
+ elif 1000 <= grid_points < 1000 * 1000:
+ grid_points_str = f"{grid_points / 1000}K"
+ else:
+ grid_points_str = f"{grid_points / 1000 / 1000}M"
+ return grid_points_str
diff --git a/tidy3d/plugins/smatrix/web/api/container.py b/tidy3d/plugins/smatrix/web/api/container.py
new file mode 100644
index 0000000000..6b0f78379e
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/container.py
@@ -0,0 +1,1102 @@
+"""higher level wrappers for webapi functions for individual (Job) and batch (Batch) tasks."""
+
+from __future__ import annotations
+
+import concurrent
+import os
+import time
+from abc import ABC
+from collections.abc import Mapping
+from concurrent.futures import ThreadPoolExecutor
+from typing import Literal, Optional
+
+import pydantic.v1 as pd
+from rich.progress import BarColumn, Progress, TaskProgressColumn, TextColumn, TimeElapsedColumn
+
+from tidy3d.components.base import Tidy3dBaseModel, cached_property
+from tidy3d.components.mode.mode_solver import ModeSolver
+from tidy3d.components.types import annotate_type
+from tidy3d.exceptions import DataError
+from tidy3d.log import get_logging_console, log
+from tidy3d.web.api import webapi as web
+from tidy3d.web.core.constants import TaskId, TaskName
+from tidy3d.web.core.task_core import Folder
+from tidy3d.web.core.task_info import RunInfo, TaskInfo
+from tidy3d.web.core.types import PayType
+
+from .tidy3d_stub import SimulationDataType, SimulationType
+
+# Max # of workers for parallel upload / download: above 10, performance is same but with warnings
+DEFAULT_NUM_WORKERS = 10
+DEFAULT_DATA_PATH = "simulation_data.hdf5"
+DEFAULT_DATA_DIR = "."
+BATCH_MONITOR_PROGRESS_REFRESH_TIME = 0.02
+
+BatchCategoryType = Literal["tidy3d", "microwave", "tidy3d_design"]
+
+
+class WebContainer(Tidy3dBaseModel, ABC):
+ """Base class for :class:`Job` and :class:`Batch`, technically not used"""
+
+ from abc import abstractmethod
+
+ @staticmethod
+ @abstractmethod
+ def _check_path_dir(path: str) -> None:
+ """Make sure local output directory exists and create it if not."""
+
+ @staticmethod
+ def _check_folder(
+ folder_name: str,
+ projects_endpoint: str = "tidy3d/projects",
+ project_endpoint: str = "tidy3d/project",
+ ) -> None:
+ """Make sure ``folder_name`` exists on the web UI and create it if not."""
+ Folder.get(
+ folder_name,
+ create=True,
+ projects_endpoint=projects_endpoint,
+ project_endpoint=project_endpoint,
+ )
+
+
+class Job(WebContainer):
+ """
+ Interface for managing the running of a :class:`.Simulation` on server.
+
+ Notes
+ -----
+
+ This class provides a more convenient way to manage single simulations, mainly because it eliminates the need
+ for keeping track of the ``task_id`` and original :class:`.Simulation`.
+
+ We can get the cost estimate of running the task before actually running it. This prevents us from
+ accidentally running large jobs that we set up by mistake. The estimated cost is the maximum cost
+ corresponding to running all the time steps.
+
+ Another convenient thing about :class:`Job` objects is that they can be saved and loaded just like other
+ ``tidy3d`` components.
+
+ Examples
+ --------
+
+ Once you've created a ``job`` object using :class:`tidy3d.web.api.container.Job`, you can upload it to our servers with:
+
+ .. code-block:: python
+
+ tidy3d.web.upload(simulation, task_name="task_name", verbose=verbose)`
+
+ It will not run until you explicitly tell it to do so with:
+
+ .. code-block:: python
+
+ tidy3d.web.api.webapi.start(job.task_id)
+
+ To monitor the simulation's progress and wait for its completion, use
+
+ .. code-block:: python
+
+ tidy3d.web.api.webapi.monitor(job.task_id, verbose=verbose)
+
+ After running the simulation, you can load the results using for example:
+
+ .. code-block:: python
+
+ sim_data = tidy3d.web.api.webapi.load(job.task_id, path="out/simulation.hdf5", verbose=verbose)
+
+ The job container has a convenient method to save and load the results of a job that has already finished,
+ without needing to know the task_id, as below:
+
+ .. code-block:: python
+
+ # Saves the job metadata to a single file.
+ job.to_file("data/job.json")
+
+ # You can exit the session, break here, or continue in new session.
+
+ # Load the job metadata from file.
+ job_loaded = tidy3d.web.api.container.Job.from_file("data/job.json")
+
+ # Download the data from the server and load it into a SimulationData object.
+ sim_data = job_loaded.load(path="data/sim.hdf5")
+
+
+ See Also
+ --------
+
+ :meth:`tidy3d.web.api.webapi.run_async`
+ Submits a set of :class:`.Simulation` objects to server, starts running, monitors progress,
+ downloads, and loads results as a :class:`.BatchData` object.
+
+ :class:`Batch`
+ Interface for submitting several :class:`Simulation` objects to sever.
+
+ **Notebooks**
+ * `Running simulations through the cloud <../../notebooks/WebAPI.html>`_
+ * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_
+ * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_
+ """
+
+ simulation: SimulationType = pd.Field(
+ ...,
+ title="simulation",
+ description="Simulation to run as a 'task'.",
+ discriminator="type",
+ )
+
+ task_name: TaskName = pd.Field(..., title="Task Name", description="Unique name of the task.")
+
+ folder_name: str = pd.Field(
+ "default", title="Folder Name", description="Name of folder to store task on web UI."
+ )
+
+ callback_url: str = pd.Field(
+ None,
+ title="Callback URL",
+ description="Http PUT url to receive simulation finish event. "
+ "The body content is a json file with fields "
+ "``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
+ )
+
+ solver_version: str = pd.Field(
+ None,
+ title="Solver Version",
+ description="Custom solver version to use, "
+ "otherwise uses default for the current front end version.",
+ )
+
+ verbose: bool = pd.Field(
+ True, title="Verbose", description="Whether to print info messages and progressbars."
+ )
+
+ simulation_type: BatchCategoryType = pd.Field(
+ "tidy3d",
+ title="Simulation Type",
+ description="Type of simulation, used internally only.",
+ )
+
+ parent_tasks: tuple[TaskId, ...] = pd.Field(
+ None, title="Parent Tasks", description="Tuple of parent task ids, used internally only."
+ )
+
+ task_id_cached: TaskId = pd.Field(
+ None,
+ title="Task ID (Cached)",
+ description="Optional field to specify ``task_id``. Only used as a workaround internally "
+ "so that ``task_id`` is written when ``Job.to_file()`` and then the proper task is loaded "
+ "from ``Job.from_file()``. We recommend leaving unset as setting this field along with "
+ "fields that were not used to create the task will cause errors.",
+ )
+
+ reduce_simulation: Literal["auto", True, False] = pd.Field(
+ "auto",
+ title="Reduce Simulation",
+ description="Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.",
+ )
+
+ pay_type: PayType = pd.Field(
+ PayType.AUTO,
+ title="Payment Type",
+ description="Specify the payment method.",
+ )
+
+ _upload_fields = (
+ "simulation",
+ "task_name",
+ "folder_name",
+ "callback_url",
+ "verbose",
+ "simulation_type",
+ "parent_tasks",
+ "solver_version",
+ "reduce_simulation",
+ )
+
+ def to_file(self, fname: str) -> None:
+ """Exports :class:`Tidy3dBaseModel` instance to .yaml, .json, or .hdf5 file
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml or .json file to save the :class:`Tidy3dBaseModel` to.
+
+ Example
+ -------
+ >>> simulation.to_file(fname='folder/sim.json') # doctest: +SKIP
+ """
+ task_id_cached = self._cached_properties.get("task_id")
+ self = self.updated_copy(task_id_cached=task_id_cached)
+ super(Job, self).to_file(fname=fname) # noqa: UP008
+
+ def run(self, path: str = DEFAULT_DATA_PATH) -> SimulationDataType:
+ """Run :class:`Job` all the way through and return data.
+
+ Parameters
+ ----------
+ path_dir : str = "./simulation_data.hdf5"
+ Base directory where data will be downloaded, by default current working directory.
+
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ Object containing simulation results.
+ """
+ self.upload()
+ self.start()
+ self.monitor()
+ return self.load(path=path)
+
+ @cached_property
+ def task_id(self) -> TaskId:
+ """The task ID for this ``Job``. Uploads the ``Job`` if it hasn't already been uploaded."""
+ if self.task_id_cached:
+ return self.task_id_cached
+ self._check_folder(self.folder_name)
+ return self._upload()
+
+ def _upload(self) -> TaskId:
+ """Upload this job and return the task ID for handling."""
+ # upload kwargs with all fields except task_id
+ upload_kwargs = {key: getattr(self, key) for key in self._upload_fields}
+ task_id = web.upload(**upload_kwargs)
+ return task_id
+
+ def upload(self) -> None:
+ """Upload this ``Job``."""
+ _ = self.task_id
+
+ def get_info(self) -> TaskInfo:
+ """Return information about a :class:`Job`.
+
+ Returns
+ -------
+ :class:`TaskInfo`
+ :class:`TaskInfo` object containing info about status, size, credits of task and others.
+ """
+
+ return web.get_info(task_id=self.task_id)
+
+ @property
+ def status(self):
+ """Return current status of :class:`Job`."""
+ return self.get_info().status
+
+ def start(self) -> None:
+ """Start running a :class:`Job`.
+
+ Note
+ ----
+ To monitor progress of the :class:`Job`, call :meth:`Job.monitor` after started.
+ """
+ web.start(self.task_id, solver_version=self.solver_version, pay_type=self.pay_type)
+
+ def get_run_info(self) -> RunInfo:
+ """Return information about the running :class:`Job`.
+
+ Returns
+ -------
+ :class:`RunInfo`
+ Task run information.
+ """
+ return web.get_run_info(task_id=self.task_id)
+
+ def monitor(self) -> None:
+ """Monitor progress of running :class:`Job`.
+
+ Note
+ ----
+ To load the output of completed simulation into :class:`.SimulationData` objects,
+ call :meth:`Job.load`.
+ """
+ web.monitor(self.task_id, verbose=self.verbose)
+
+ def download(self, path: str = DEFAULT_DATA_PATH) -> None:
+ """Download results of simulation.
+
+ Parameters
+ ----------
+ path : str = "./simulation_data.hdf5"
+ Path to download data as ``.hdf5`` file (including filename).
+
+ Note
+ ----
+ To load the data after download, use :meth:`Job.load`.
+ """
+ self._check_path_dir(path=path)
+ web.download(task_id=self.task_id, path=path, verbose=self.verbose)
+
+ def load(self, path: str = DEFAULT_DATA_PATH) -> SimulationDataType:
+ """Download job results and load them into a data object.
+
+ Parameters
+ ----------
+ path : str = "./simulation_data.hdf5"
+ Path to download data as ``.hdf5`` file (including filename).
+
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ Object containing simulation results.
+ """
+ self._check_path_dir(path=path)
+ data = web.load(task_id=self.task_id, path=path, verbose=self.verbose)
+ if isinstance(self.simulation, ModeSolver):
+ self.simulation._patch_data(data=data)
+ return data
+
+ def delete(self) -> None:
+ """Delete server-side data associated with :class:`Job`."""
+ web.delete(self.task_id)
+
+ def real_cost(self, verbose: bool = True) -> float:
+ """Get the billed cost for the task associated with this job.
+
+ Parameters
+ ----------
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+
+ Returns
+ -------
+ float
+ Billed cost of the task in FlexCredits.
+ """
+ return web.real_cost(self.task_id, verbose=verbose)
+
+ def estimate_cost(self, verbose: bool = True) -> float:
+ """Compute the maximum FlexCredit charge for a given :class:`.Job`.
+
+ Parameters
+ ----------
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+
+ Returns
+ -------
+ float
+ Estimated cost of the task in FlexCredits.
+
+ Note
+ ----
+ Cost is calculated assuming the simulation runs for
+ the full ``run_time``. If early shut-off is triggered, the cost is adjusted proportionately.
+ """
+ return web.estimate_cost(self.task_id, verbose=verbose, solver_version=self.solver_version)
+
+ @staticmethod
+ def _check_path_dir(path: str) -> None:
+ """Make sure parent directory of ``path`` exists and create it if not.
+
+ Parameters
+ ----------
+ path : str
+ Path to file to be created (including filename).
+ """
+ parent_dir = os.path.dirname(path)
+ if len(parent_dir) > 0 and not os.path.exists(parent_dir):
+ os.makedirs(parent_dir, exist_ok=True)
+
+
+class BatchData(Tidy3dBaseModel, Mapping):
+ """
+ Holds a collection of :class:`.SimulationData` returned by :class:`Batch`.
+
+ Notes
+ -----
+
+ When the batch is completed, the output is not a :class:`.SimulationData` but rather a :class:`BatchData`. The
+ data within this :class:`BatchData` object can either be indexed directly ``batch_results[task_name]`` or can be looped
+ through ``batch_results.items()`` to get the :class:`.SimulationData` for each task.
+
+ See Also
+ --------
+
+ :class:`Batch`:
+ Interface for submitting several :class:`.Simulation` objects to sever.
+
+ :class:`.SimulationData`:
+ Stores data from a collection of :class:`.Monitor` objects in a :class:`.Simulation`.
+
+ **Notebooks**
+ * `Running simulations through the cloud <../../notebooks/WebAPI.html>`_
+ * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_
+ """
+
+ task_paths: dict[TaskName, str] = pd.Field(
+ ...,
+ title="Data Paths",
+ description="Mapping of task_name to path to corresponding data for each task in batch.",
+ )
+
+ task_ids: dict[TaskName, str] = pd.Field(
+ ..., title="Task IDs", description="Mapping of task_name to task_id for each task in batch."
+ )
+
+ verbose: bool = pd.Field(
+ True, title="Verbose", description="Whether to print info messages and progressbars."
+ )
+
+ def load_sim_data(self, task_name: str) -> SimulationDataType:
+ """Load a simulation data object from file by task name."""
+ task_data_path = self.task_paths[task_name]
+ task_id = self.task_ids[task_name]
+ web.get_info(task_id)
+
+ return web.load(task_id=task_id, path=task_data_path, verbose=False)
+
+ def __getitem__(self, task_name: TaskName) -> SimulationDataType:
+ """Get the simulation data object for a given ``task_name``."""
+ return self.load_sim_data(task_name)
+
+ def __iter__(self):
+ """Iterate over the task names."""
+ return iter(self.task_paths)
+
+ def __len__(self):
+ """Return the number of tasks in the batch."""
+ return len(self.task_paths)
+
+ @classmethod
+ def load(cls, path_dir: str = DEFAULT_DATA_DIR, replace_existing: bool = False) -> BatchData:
+ """Load :class:`Batch` from file, download results, and load them.
+
+ Parameters
+ ----------
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default current working directory.
+ A `batch.hdf5` file must be present in the directory.
+ replace_existing : bool = False
+ Downloads the data even if path exists (overwriting the existing).
+
+ Returns
+ ------
+ :class:`BatchData`
+ Contains Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ for each Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] in :class:`Batch`.
+ """
+
+ batch_file = Batch._batch_path(path_dir=path_dir)
+ batch = Batch.from_file(batch_file)
+ return batch.load(path_dir=path_dir, replace_existing=replace_existing)
+
+
+class Batch(WebContainer):
+ """
+ Interface for submitting several :class:`Simulation` objects to sever.
+
+ Notes
+ -----
+
+ Commonly one needs to submit a batch of :class:`Simulation`. The built-in :class:`Batch` object is the best way to upload,
+ start, monitor, and load a series of tasks. The batch object is like a :class:`Job`, but stores task metadata
+ for a series of simulations.
+
+ See Also
+ --------
+
+ :meth:`tidy3d.web.api.webapi.run_async`
+ Submits a set of :class:`.Simulation` objects to server, starts running, monitors progress,
+ downloads, and loads results as a :class:`.BatchData` object.
+
+ :class:`Job`:
+ Interface for managing the running of a Simulation on server.
+
+ **Notebooks**
+ * `Running simulations through the cloud <../../notebooks/WebAPI.html>`_
+ * `Performing parallel / batch processing of simulations <../../notebooks/ParameterScan.html>`_
+ * `Inverse taper edge coupler <../../notebooks/EdgeCoupler.html>`_
+ """
+
+ simulations: dict[TaskName, annotate_type(SimulationType)] = pd.Field(
+ ...,
+ title="Simulations",
+ description="Mapping of task names to Simulations to run as a batch.",
+ )
+
+ folder_name: str = pd.Field(
+ "default",
+ title="Folder Name",
+ description="Name of folder to store member of each batch on web UI.",
+ )
+
+ verbose: bool = pd.Field(
+ True, title="Verbose", description="Whether to print info messages and progressbars."
+ )
+
+ solver_version: str = pd.Field(
+ None,
+ title="Solver Version",
+ description="Custom solver version to use, "
+ "otherwise uses default for the current front end version.",
+ )
+
+ callback_url: str = pd.Field(
+ None,
+ title="Callback URL",
+ description="Http PUT url to receive simulation finish event. "
+ "The body content is a json file with fields "
+ "``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
+ )
+
+ simulation_type: BatchCategoryType = pd.Field(
+ "tidy3d",
+ title="Simulation Type",
+ description="Type of each simulation in the batch, used internally only.",
+ )
+
+ parent_tasks: dict[str, tuple[TaskId, ...]] = pd.Field(
+ None,
+ title="Parent Tasks",
+ description="Collection of parent task ids for each job in batch, used internally only.",
+ )
+
+ num_workers: Optional[pd.PositiveInt] = pd.Field(
+ DEFAULT_NUM_WORKERS,
+ title="Number of Workers",
+ description="Number of workers for multi-threading upload and download of batch. "
+ "Corresponds to ``max_workers`` argument passed to "
+ "``concurrent.futures.ThreadPoolExecutor``. When left ``None``, will pass the maximum "
+ "number of threads available on the system.",
+ )
+
+ reduce_simulation: Literal["auto", True, False] = pd.Field(
+ "auto",
+ title="Reduce Simulation",
+ description="Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.",
+ )
+
+ pay_type: PayType = pd.Field(
+ PayType.AUTO,
+ title="Payment Type",
+ description="Specify the payment method.",
+ )
+
+ jobs_cached: dict[TaskName, Job] = pd.Field(
+ None,
+ title="Jobs (Cached)",
+ description="Optional field to specify ``jobs``. Only used as a workaround internally "
+ "so that ``jobs`` is written when ``Batch.to_file()`` and then the proper task is loaded "
+ "from ``Batch.from_file()``. We recommend leaving unset as setting this field along with "
+ "fields that were not used to create the task will cause errors.",
+ )
+
+ _job_type = Job
+
+ def run(self, path_dir: str = DEFAULT_DATA_DIR) -> BatchData:
+ """Upload and run each simulation in :class:`Batch`.
+
+ Parameters
+ ----------
+ path_dir : str
+ Base directory where data will be downloaded, by default current working directory.
+
+ Returns
+ ------
+ :class:`BatchData`
+ Contains Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData] for
+ each Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] in :class:`Batch`.
+
+ Note
+ ----
+ A typical usage might look like:
+
+ >>> from tidy3d.web.api.container import Batch
+ >>> custom_batch = Batch()
+ >>> batch_data = custom_batch.run() # doctest: +SKIP
+ >>> for task_name, sim_data in batch_data.items(): # doctest: +SKIP
+ ... # do something with data. # doctest: +SKIP
+
+ ``bach_data`` does not store all of the data objects in memory,
+ rather it iterates over the task names and loads the corresponding
+ data from file one by one. If no file exists for that task, it downloads it.
+ """
+ self._check_path_dir(path_dir)
+ self.upload()
+ self.start()
+ self.monitor()
+ return self.load(path_dir=path_dir)
+
+ @cached_property
+ def jobs(self) -> dict[TaskName, Job]:
+ """Create a series of tasks in the :class:`.Batch` and upload them to server.
+
+ Note
+ ----
+ To start the simulations running, must call :meth:`Batch.start` after uploaded.
+ """
+
+ if self.jobs_cached is not None:
+ return self.jobs_cached
+
+ # the type of job to upload (to generalize to subclasses)
+ JobType = self._job_type
+ self_dict = self.dict()
+
+ jobs = {}
+ for task_name, simulation in self.simulations.items():
+ job_kwargs = {}
+
+ for key in JobType._upload_fields:
+ if key in self_dict:
+ job_kwargs[key] = self_dict.get(key)
+
+ job_kwargs["task_name"] = task_name
+ job_kwargs["simulation"] = simulation
+ job_kwargs["verbose"] = False
+ job_kwargs["solver_version"] = self.solver_version
+ job_kwargs["pay_type"] = self.pay_type
+ job_kwargs["reduce_simulation"] = self.reduce_simulation
+ if self.parent_tasks and task_name in self.parent_tasks:
+ job_kwargs["parent_tasks"] = self.parent_tasks[task_name]
+ job = JobType(**job_kwargs)
+ jobs[task_name] = job
+ return jobs
+
+ def to_file(self, fname: str) -> None:
+ """Exports :class:`Tidy3dBaseModel` instance to .yaml, .json, or .hdf5 file
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .yaml or .json file to save the :class:`Tidy3dBaseModel` to.
+
+ Example
+ -------
+ >>> simulation.to_file(fname='folder/sim.json') # doctest: +SKIP
+ """
+ jobs_cached = self._cached_properties.get("jobs")
+ if jobs_cached is not None:
+ jobs = {}
+ for key, job in jobs_cached.items():
+ task_id = job._cached_properties.get("task_id")
+ jobs[key] = job.updated_copy(task_id_cached=task_id)
+ self = self.updated_copy(jobs_cached=jobs)
+ super(Batch, self).to_file(fname=fname) # noqa: UP008
+
+ @property
+ def num_jobs(self) -> int:
+ """Number of jobs in the batch."""
+ return len(self.jobs)
+
+ def upload(self) -> None:
+ """Upload a series of tasks associated with this ``Batch`` using multi-threading."""
+ self._check_folder(self.folder_name)
+ with ThreadPoolExecutor(max_workers=self.num_workers) as executor:
+ futures = [executor.submit(job.upload) for _, job in self.jobs.items()]
+
+ # progressbar (number of tasks uploaded)
+ if self.verbose:
+ console = get_logging_console()
+ progress_columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ TaskProgressColumn(),
+ TimeElapsedColumn(),
+ )
+ with Progress(*progress_columns, console=console, transient=False) as progress:
+ pbar_message = f"Uploading data for {self.num_jobs} tasks"
+ pbar = progress.add_task(pbar_message, total=self.num_jobs)
+ completed = 0
+ for _ in concurrent.futures.as_completed(futures):
+ completed += 1
+ progress.update(pbar, completed=completed)
+
+ def get_info(self) -> dict[TaskName, TaskInfo]:
+ """Get information about each task in the :class:`Batch`.
+
+ Returns
+ -------
+ Dict[str, :class:`TaskInfo`]
+ Mapping of task name to data about task associated with each task.
+ """
+ info_dict = {}
+ for task_name, job in self.jobs.items():
+ task_info = job.get_info()
+ info_dict[task_name] = task_info
+ return info_dict
+
+ def start(self) -> None:
+ """Start running all tasks in the :class:`Batch`.
+
+ Note
+ ----
+ To monitor the running simulations, can call :meth:`Batch.monitor`.
+ """
+ if self.verbose:
+ console = get_logging_console()
+ console.log(f"Started working on Batch containing {self.num_jobs} tasks.")
+
+ with ThreadPoolExecutor(max_workers=self.num_workers) as executor:
+ for _, job in self.jobs.items():
+ executor.submit(job.start)
+
+ def get_run_info(self) -> dict[TaskName, RunInfo]:
+ """get information about a each of the tasks in the :class:`Batch`.
+
+ Returns
+ -------
+ Dict[str: :class:`RunInfo`]
+ Maps task names to run info for each task in the :class:`Batch`.
+ """
+ run_info_dict = {}
+ for task_name, job in self.jobs.items():
+ run_info = job.get_run_info()
+ run_info_dict[task_name] = run_info
+ return run_info_dict
+
+ def monitor(self) -> None:
+ """Monitor progress of each of the running tasks."""
+
+ def pbar_description(
+ task_name: str, status: str, max_name_length: int, status_width: int
+ ) -> str:
+ """Make a progressbar description based on the status."""
+ # if task name too long, truncate and add ...
+ if len(task_name) > max_name_length - 3: # -3 to leave room for ...
+ task_name = task_name[: (max_name_length - 3)] + "..."
+
+ # right-align status
+ task_part = f"{task_name:<{max_name_length}}"
+
+ if "error" in status or "diverge" in status or "aborted" in status:
+ status_part = f"→ [red]{status:<{status_width}}"
+ elif status == "success":
+ status_part = f"→ [green]{status:<{status_width}}"
+ elif status == "queued" or status == "queued_solver" or status == "aborting":
+ status_part = f"→ [yellow]{status:<{status_width}}"
+ elif status in ["preprocess", "postprocess", "running"]:
+ status_part = f"→ [blue]{status:<{status_width}}"
+ else:
+ status_part = f"→ {status:<{status_width}}"
+
+ return f"{task_part} {status_part}"
+
+ run_statuses = [
+ "draft",
+ "queued",
+ "preprocess",
+ "queued_solver",
+ "running",
+ "postprocess",
+ "visualize",
+ "success",
+ "aborting",
+ ]
+ end_statuses = (
+ "preprocess_success",
+ "postprocess_success",
+ "success",
+ "error",
+ "errored",
+ "diverged",
+ "diverge",
+ "deleted",
+ "draft",
+ "aborted",
+ )
+
+ max_task_name = max(len(task_name) for task_name in self.jobs.keys())
+ max_name_length = min(30, max(max_task_name, 15))
+ status_width = max(
+ max(len(status) for status in run_statuses), max(len(status) for status in end_statuses)
+ )
+
+ if self.verbose:
+ console = get_logging_console()
+
+ self.estimate_cost()
+ console.log(
+ "Use 'Batch.real_cost()' to "
+ "get the billed FlexCredit cost after the Batch has completed."
+ )
+
+ progress_columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(bar_width=25),
+ TaskProgressColumn(),
+ TimeElapsedColumn(),
+ )
+
+ with Progress(*progress_columns, console=console, transient=False) as progress:
+ # create progress bars
+ pbar_tasks = {}
+ for task_name, job in self.jobs.items():
+ status = job.status
+ description = pbar_description(task_name, status, max_name_length, status_width)
+ completed = run_statuses.index(status) if status in run_statuses else 0
+ pbar = progress.add_task(
+ description, total=len(run_statuses) - 1, completed=completed
+ )
+ pbar_tasks[task_name] = pbar
+
+ while any(job.status not in end_statuses for job in self.jobs.values()):
+ updates = []
+ for task_name, job in self.jobs.items():
+ status = job.status
+ if status in run_statuses:
+ updates.append(
+ (
+ pbar_tasks[task_name],
+ pbar_description(
+ task_name, status, max_name_length, status_width
+ ),
+ run_statuses.index(status),
+ )
+ )
+
+ for pbar, description, completed in updates:
+ progress.update(
+ pbar, description=description, completed=completed, refresh=False
+ )
+
+ progress.refresh()
+ time.sleep(BATCH_MONITOR_PROGRESS_REFRESH_TIME)
+
+ updates = []
+ for task_name, job in self.jobs.items():
+ updates.append(
+ (
+ pbar_tasks[task_name],
+ pbar_description(task_name, job.status, max_name_length, status_width),
+ len(run_statuses) - 1,
+ )
+ )
+
+ for pbar, description, completed in updates:
+ progress.update(
+ pbar, description=description, completed=completed, refresh=False
+ )
+
+ progress.refresh()
+ console.log("Batch complete.")
+
+ else:
+ while any(job.status not in end_statuses for job in self.jobs.values()):
+ time.sleep(web.REFRESH_TIME)
+
+ @staticmethod
+ def _job_data_path(task_id: TaskId, path_dir: str = DEFAULT_DATA_DIR):
+ """Default path to data of a single :class:`Job` in :class:`Batch`.
+
+ Parameters
+ ----------
+ task_id : str
+ task_id corresponding to a :class:`Job`.
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default, the current working directory.
+
+ Returns
+ -------
+ str
+ Full path to the data file.
+ """
+ return os.path.join(path_dir, f"{task_id!s}.hdf5")
+
+ @staticmethod
+ def _batch_path(path_dir: str = DEFAULT_DATA_DIR):
+ """Default path to save :class:`Batch` hdf5 file.
+
+ Parameters
+ ----------
+ path_dir : str = './'
+ Base directory where the batch.hdf5 will be downloaded,
+ by default, the current working directory.
+
+ Returns
+ -------
+ str
+ Full path to the batch file.
+ """
+ return os.path.join(path_dir, "batch.hdf5")
+
+ def download(self, path_dir: str = DEFAULT_DATA_DIR, replace_existing: bool = False) -> None:
+ """Download results of each task.
+
+ Parameters
+ ----------
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default the current working directory.
+ replace_existing : bool = False
+ Downloads the data even if path exists (overwriting the existing).
+
+ Note
+ ----
+ To load and iterate through the data, use :meth:`Batch.items()`.
+
+ The data for each task will be named as ``{path_dir}/{task_id}.hdf5``.
+ The :class:`Batch` hdf5 file will be automatically saved as ``{path_dir}/batch.hdf5``,
+ allowing one to load this :class:`Batch` later using ``batch = Batch.from_file()``.
+ """
+ self._check_path_dir(path_dir=path_dir)
+ self.to_file(self._batch_path(path_dir=path_dir))
+
+ num_existing = 0
+ for _, job in self.jobs.items():
+ job_path_str = self._job_data_path(task_id=job.task_id, path_dir=path_dir)
+ if os.path.exists(job_path_str):
+ num_existing += 1
+ if num_existing > 0:
+ files_plural = "files have" if num_existing > 1 else "file has"
+ log.warning(
+ f"{num_existing} {files_plural} already been downloaded "
+ f"and will be skipped. To forcibly overwrite existing files, invoke "
+ "the load or download function with `replace_existing=True`.",
+ log_once=True,
+ )
+
+ with ThreadPoolExecutor(max_workers=self.num_workers) as executor:
+ fns = []
+ for task_name, job in self.jobs.items():
+ job_path_str = self._job_data_path(task_id=job.task_id, path_dir=path_dir)
+ if os.path.exists(job_path_str):
+ if replace_existing:
+ log.info(f"File '{job_path_str}' already exists. Overwriting.")
+ else:
+ log.info(f"File '{job_path_str}' already exists. Skipping.")
+ continue
+ if "error" in job.status:
+ log.warning(f"Not downloading '{task_name}' as the task errored.")
+ continue
+
+ def fn(job=job, job_path_str=job_path_str) -> None:
+ return job.download(path=job_path_str)
+
+ fns.append(fn)
+
+ futures = [executor.submit(fn) for fn in fns]
+
+ if self.verbose:
+ console = get_logging_console()
+ progress_columns = (
+ TextColumn("[progress.description]{task.description}"),
+ BarColumn(),
+ TaskProgressColumn(),
+ TimeElapsedColumn(),
+ )
+ with Progress(*progress_columns, console=console, transient=False) as progress:
+ pbar_message = f"Downloading data for {len(fns)} tasks"
+ pbar = progress.add_task(pbar_message, total=len(fns))
+ completed = 0
+ for _ in concurrent.futures.as_completed(futures):
+ completed += 1
+ progress.update(pbar, completed=completed)
+
+ def load(self, path_dir: str = DEFAULT_DATA_DIR, replace_existing: bool = False) -> BatchData:
+ """Download results and load them into :class:`.BatchData` object.
+
+ Parameters
+ ----------
+ path_dir : str = './'
+ Base directory where data will be downloaded, by default current working directory.
+ replace_existing : bool = False
+ Downloads the data even if path exists (overwriting the existing).
+
+ Returns
+ ------
+ :class:`BatchData`
+ Contains Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] for each
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] in :class:`Batch`.
+
+ The :class:`Batch` hdf5 file will be automatically saved as ``{path_dir}/batch.hdf5``,
+ allowing one to load this :class:`Batch` later using ``batch = Batch.from_file()``.
+ """
+ self._check_path_dir(path_dir=path_dir)
+ self.download(path_dir=path_dir, replace_existing=replace_existing)
+
+ if self.jobs is None:
+ raise DataError("Can't load batch results, hasn't been uploaded.")
+
+ task_paths = {}
+ task_ids = {}
+ for task_name, job in self.jobs.items():
+ if "error" in job.status:
+ log.warning(f"Not loading '{task_name}' as the task errored.")
+ continue
+
+ task_paths[task_name] = self._job_data_path(task_id=job.task_id, path_dir=path_dir)
+ task_ids[task_name] = self.jobs[task_name].task_id
+
+ data = BatchData(task_paths=task_paths, task_ids=task_ids, verbose=self.verbose)
+
+ for task_name, job in self.jobs.items():
+ if isinstance(job.simulation, ModeSolver):
+ job_data = data[task_name]
+ job.simulation._patch_data(data=job_data)
+
+ return data
+
+ def delete(self) -> None:
+ """Delete server-side data associated with each task in the batch."""
+ for _, job in self.jobs.items():
+ job.delete()
+
+ def real_cost(self, verbose: bool = True) -> float:
+ """Get the sum of billed costs for each task associated with this batch.
+
+ Parameters
+ ----------
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+
+ Returns
+ -------
+ float
+ Billed cost for the entire :class:`.Batch`.
+ """
+ real_cost_sum = 0.0
+ for _, job in self.jobs.items():
+ cost_job = job.real_cost(verbose=False)
+ if cost_job is not None:
+ real_cost_sum += cost_job
+
+ real_cost_sum = real_cost_sum or None # convert to None if 0
+
+ if real_cost_sum and verbose:
+ console = get_logging_console()
+ console.log(f"Total billed flex credit cost: {real_cost_sum:1.3f}.")
+ return real_cost_sum
+
+ def estimate_cost(self, verbose: bool = True) -> float:
+ """Compute the maximum FlexCredit charge for a given :class:`.Batch`.
+
+ Parameters
+ ----------
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+
+ Note
+ ----
+ Cost is calculated assuming the simulation runs for
+ the full ``run_time``. If early shut-off is triggered, the cost is adjusted proportionately.
+
+ Returns
+ -------
+ float
+ Estimated total cost of the tasks in FlexCredits.
+ """
+ job_costs = [job.estimate_cost(verbose=False) for _, job in self.jobs.items()]
+ if any(cost is None for cost in job_costs):
+ batch_cost = None
+ else:
+ batch_cost = sum(job_costs)
+
+ if verbose:
+ console = get_logging_console()
+ if batch_cost is not None and batch_cost > 0:
+ console.log(f"Maximum FlexCredit cost: {batch_cost:1.3f} for the whole batch.")
+ else:
+ console.log("Could not get estimated batch cost!")
+
+ return batch_cost
+
+ @staticmethod
+ def _check_path_dir(path_dir: str) -> None:
+ """Make sure ``path_dir`` exists and create it if not.
+
+ Parameters
+ ----------
+ path_dir : str
+ Directory path where files will be saved.
+ """
+ if len(path_dir) > 0 and not os.path.exists(path_dir):
+ os.makedirs(path_dir, exist_ok=True)
diff --git a/tidy3d/plugins/smatrix/web/api/material_fitter.py b/tidy3d/plugins/smatrix/web/api/material_fitter.py
new file mode 100644
index 0000000000..82bb158736
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/material_fitter.py
@@ -0,0 +1,127 @@
+"""Material Fitter API."""
+
+from __future__ import annotations
+
+import os
+import tempfile
+from enum import Enum
+from typing import Optional
+from uuid import uuid4
+
+import numpy as np
+import requests
+from pydantic.v1 import BaseModel, Field
+
+from tidy3d.plugins.dispersion import DispersionFitter
+from tidy3d.web.core.http_util import http
+from tidy3d.web.core.types import Submittable
+
+
+class ConstraintEnum(str, Enum):
+ """Constraint Enum."""
+
+ HARD = "hard"
+ SOFT = "soft"
+
+
+class FitterOptions(BaseModel):
+ """Fitter Options."""
+
+ num_poles: Optional[int] = 1
+ num_tries: Optional[int] = 50
+ tolerance_rms: Optional[float] = 1e-2
+ min_wvl: Optional[float] = None
+ max_wvl: Optional[float] = None
+ bound_amp: Optional[float] = None
+ bound_eps_inf: Optional[float] = 1.0
+ bound_f: Optional[float] = None
+ constraint: ConstraintEnum = ConstraintEnum.HARD
+ nlopt_maxeval: int = 5000
+
+
+class _FitterRequest(BaseModel):
+ """Fitter request."""
+
+ fileName: str
+ jsonInput: str
+ resourcePath: str
+
+
+class MaterialFitterTask(Submittable):
+ """Material Fitter Task."""
+
+ id: str = Field(title="Task ID", description="Task ID")
+ dispersion_fitter: DispersionFitter = Field(
+ title="Dispersion Fitter", description="Dispersion Fitter data"
+ )
+ status: str = Field(title="Task Status", description="Task Status")
+ file_name: str = Field(
+ ..., title="file name", description="fitter data file name", alias="fileName"
+ )
+ resource_path: str = Field(
+ ..., title="resource path", description="resource path", alias="resourcePath"
+ )
+
+ @classmethod
+ def submit(cls, fitter: DispersionFitter, options: FitterOptions) -> MaterialFitterTask:
+ """Create and kickoff fitter task.
+
+ Parameters
+ ----------
+ fitter: DispersionFitter
+ material fitter data.
+ options: FitterOptions
+ fitter options
+ """
+ if not isinstance(fitter, DispersionFitter):
+ raise TypeError(f"fitter must be an instance of 'DispersionFitter', got {type(fitter)}")
+ if not isinstance(options, FitterOptions):
+ raise TypeError(f"options must be an instance of 'FitterOptions', got {type(options)}")
+ data = np.asarray(list(zip(fitter.wvl_um, fitter.n_data, fitter.k_data)))
+ with tempfile.NamedTemporaryFile(suffix=".csv") as temp:
+ np.savetxt(temp, data, delimiter=",", header="Wavelength,n,k")
+ uid = str(uuid4())
+ url = http.get(
+ f"tidy3d/fitter/{uid}/signedUrl?filepath={os.path.basename(temp.name)}&method=PUT"
+ )
+ temp.seek(0)
+ resp = requests.put(
+ url,
+ data=temp.read(),
+ headers={"Content-Type": "application/octet-stream"},
+ timeout=60,
+ )
+ if resp.raise_for_status():
+ raise resp.raise_for_status()
+ fitter_req = _FitterRequest(
+ fileName=os.path.basename(temp.name),
+ jsonInput=options.json(exclude_none=True),
+ resourcePath=uid,
+ )
+ resp = http.post("tidy3d/fitter/fit", json=fitter_req.dict())
+ return cls(dispersion_fitter=fitter, **resp)
+
+ def sync_status(self) -> None:
+ """Sync the status from server and update self.status."""
+ resp = http.get(f"tidy3d/fitter/{self.id}")
+ self.status = resp["status"]
+
+ def save_to_library(self, name: str) -> bool:
+ """Save the fitted material to the material library.
+
+ Parameters
+ ----------
+ name: str
+ The name in material library.
+
+ Returns
+ ---------
+ success: bool
+ True if success, False otherwise.
+ """
+
+ if self.status != "COMPLETED":
+ print("Task is not completed, please use sync_status to get latest status.")
+ return False
+ resp = http.post("tidy3d/fitter/save", json={"id": self.id, "fitterName": name})
+ return resp
diff --git a/tidy3d/plugins/smatrix/web/api/material_libray.py b/tidy3d/plugins/smatrix/web/api/material_libray.py
new file mode 100644
index 0000000000..98bfeaa0b2
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/material_libray.py
@@ -0,0 +1,44 @@
+"""Material Library API."""
+
+from __future__ import annotations
+
+import builtins
+import json
+from typing import Optional
+
+from pydantic.v1 import Field, parse_obj_as, validator
+
+from tidy3d.components.medium import MediumType
+from tidy3d.web.core.http_util import http
+from tidy3d.web.core.types import Queryable
+
+
+class MaterialLibray(Queryable, smart_union=True):
+ """Material Library Resource interface."""
+
+ id: str = Field(title="Material Library ID", description="Material Library ID")
+ name: str = Field(title="Material Library Name", description="Material Library Name")
+ medium: Optional[MediumType] = Field(title="medium", description="medium", alias="calcResult")
+ medium_type: Optional[str] = Field(
+ title="medium type", description="medium type", alias="mediumType"
+ )
+ json_input: Optional[dict] = Field(
+ title="json input", description="original input", alias="jsonInput"
+ )
+
+ @validator("medium", "json_input", pre=True)
+ def parse_result(cls, values):
+ """Automatically parsing medium and json_input from string to object."""
+ return json.loads(values)
+
+ @classmethod
+ def list(cls) -> builtins.list[MaterialLibray]:
+ """List all material libraries.
+
+ Returns
+ -------
+ tasks : List[:class:`.MaterialLibray`]
+ List of material libraries/
+ """
+ resp = http.get("tidy3d/libraries")
+ return parse_obj_as(list[MaterialLibray], resp) if resp else None
diff --git a/tidy3d/plugins/smatrix/web/api/mode.py b/tidy3d/plugins/smatrix/web/api/mode.py
new file mode 100644
index 0000000000..02364f105b
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/mode.py
@@ -0,0 +1,674 @@
+"""Web API for mode solver"""
+
+from __future__ import annotations
+
+import os
+import pathlib
+import tempfile
+import time
+from datetime import datetime
+from typing import Callable, Literal, Optional, Union
+
+import pydantic.v1 as pydantic
+from botocore.exceptions import ClientError
+from joblib import Parallel, delayed
+from rich.progress import Progress
+
+from tidy3d.components.data.monitor_data import ModeSolverData
+from tidy3d.components.eme.simulation import EMESimulation
+from tidy3d.components.medium import AbstractCustomMedium
+from tidy3d.components.simulation import Simulation
+from tidy3d.exceptions import SetupError, WebError
+from tidy3d.log import get_logging_console, log
+from tidy3d.plugins.mode.mode_solver import MODE_MONITOR_NAME, ModeSolver
+from tidy3d.version import __version__
+from tidy3d.web.core.core_config import get_logger_console
+from tidy3d.web.core.environment import Env
+from tidy3d.web.core.http_util import http
+from tidy3d.web.core.s3utils import download_file, download_gz_file, upload_file
+from tidy3d.web.core.task_core import Folder
+from tidy3d.web.core.types import PayType, ResourceLifecycle, Submittable
+
+SIMULATION_JSON = "simulation.json"
+SIM_FILE_HDF5_GZ = "simulation.hdf5.gz"
+MODESOLVER_API = "tidy3d/modesolver/py"
+MODESOLVER_JSON = "mode_solver.json"
+MODESOLVER_HDF5 = "mode_solver.hdf5"
+MODESOLVER_GZ = "mode_solver.hdf5.gz"
+
+MODESOLVER_LOG = "output/result.log"
+MODESOLVER_RESULT = "output/result.hdf5"
+MODESOLVER_RESULT_GZ = "output/mode_solver_data.hdf5.gz"
+
+DEFAULT_NUM_WORKERS = 10
+DEFAULT_MAX_RETRIES = 3
+DEFAULT_RETRY_DELAY = 10 # in seconds
+
+
+def run(
+ mode_solver: ModeSolver,
+ task_name: str = "Untitled",
+ mode_solver_name: str = "mode_solver",
+ folder_name: str = "Mode Solver",
+ results_file: str = "mode_solver.hdf5",
+ verbose: bool = True,
+ progress_callback_upload: Optional[Callable[[float], None]] = None,
+ progress_callback_download: Optional[Callable[[float], None]] = None,
+ reduce_simulation: Literal["auto", True, False] = "auto",
+ pay_type: Union[PayType, str] = PayType.AUTO,
+) -> ModeSolverData:
+ """Submits a :class:`.ModeSolver` to server, starts running, monitors progress, downloads,
+ and loads results as a :class:`.ModeSolverData` object.
+
+ Parameters
+ ----------
+ mode_solver : :class:`.ModeSolver`
+ Mode solver to upload to server.
+ task_name : str = "Untitled"
+ Name of task.
+ mode_solver_name: str = "mode_solver"
+ The name of the mode solver to create the in task.
+ folder_name : str = "Mode Solver"
+ Name of folder to store task on web UI.
+ results_file : str = "mode_solver.hdf5"
+ Path to download results file (.hdf5).
+ verbose : bool = True
+ If ``True``, will print status, otherwise, will run silently.
+ progress_callback_upload : Callable[[float], None] = None
+ Optional callback function called when uploading file with ``bytes_in_chunk`` as argument.
+ progress_callback_download : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+ reduce_simulation : Literal["auto", True, False] = "auto"
+ Restrict simulation to mode solver region. If "auto", then simulation is automatically
+ restricted if it contains custom mediums.
+ pay_type: Union[PayType, str] = PayType.AUTO
+ Which method to pay the simulation.
+ Returns
+ -------
+ :class:`.ModeSolverData`
+ Mode solver data with the calculated results.
+ """
+ log_level = "DEBUG" if verbose else "INFO"
+ if verbose:
+ console = get_logging_console()
+
+ if reduce_simulation == "auto":
+ sim_mediums = mode_solver.simulation.scene.mediums
+ contains_custom = any(isinstance(med, AbstractCustomMedium) for med in sim_mediums)
+ reduce_simulation = contains_custom
+
+ if reduce_simulation:
+ log.warning(
+ "The associated 'Simulation' object contains custom mediums. It will be "
+ "automatically restricted to the mode solver plane to reduce data for uploading. "
+ "To force uploading the original 'Simulation' object use 'reduce_simulation=False'."
+ " Setting 'reduce_simulation=True' will force simulation reduction in all cases and"
+ " silence this warning."
+ )
+
+ if reduce_simulation:
+ mode_solver = mode_solver.reduced_simulation_copy
+
+ task = ModeSolverTask.create(mode_solver, task_name, mode_solver_name, folder_name)
+ if verbose:
+ console.log(
+ f"Mode solver created with task_id='{task.task_id}', solver_id='{task.solver_id}'."
+ )
+ task.upload(verbose=verbose, progress_callback=progress_callback_upload)
+ task.submit(pay_type=pay_type)
+
+ # Wait for task to finish
+ prev_status = "draft"
+ status = task.status
+ while status not in ("success", "error", "diverged", "deleted"):
+ if status != prev_status:
+ log.log(log_level, f"Mode solver status: {status}")
+ if verbose:
+ console.log(f"Mode solver status: {status}")
+ prev_status = status
+ time.sleep(0.5)
+ status = task.get_info().status
+
+ if status == "error":
+ raise WebError("Error running mode solver.")
+
+ log.log(log_level, f"Mode solver status: {status}")
+ if verbose:
+ console.log(f"Mode solver status: {status}")
+
+ if status != "success":
+ # Our cache discards None, so the user is able to re-run
+ return None
+
+ return task.get_result(
+ to_file=results_file, verbose=verbose, progress_callback=progress_callback_download
+ )
+
+
+def run_batch(
+ mode_solvers: list[ModeSolver],
+ task_name: str = "BatchModeSolver",
+ folder_name: str = "BatchModeSolvers",
+ results_files: Optional[list[str]] = None,
+ verbose: bool = True,
+ max_workers: int = DEFAULT_NUM_WORKERS,
+ max_retries: int = DEFAULT_MAX_RETRIES,
+ retry_delay: float = DEFAULT_RETRY_DELAY,
+ progress_callback_upload: Optional[Callable[[float], None]] = None,
+ progress_callback_download: Optional[Callable[[float], None]] = None,
+) -> list[ModeSolverData]:
+ """
+ Submits a batch of ModeSolver to the server concurrently, manages progress, and retrieves results.
+
+ Parameters
+ ----------
+ mode_solvers : List[ModeSolver]
+ List of mode solvers to be submitted to the server.
+ task_name : str
+ Base name for tasks. Each task in the batch will have a unique index appended to this base name.
+ folder_name : str
+ Name of the folder where tasks are stored on the server's web UI.
+ results_files : List[str], optional
+ List of file paths where the results for each ModeSolver should be downloaded. If None, a default path based on the folder name and index is used.
+ verbose : bool
+ If True, displays a progress bar. If False, runs silently.
+ max_workers : int
+ Maximum number of concurrent workers to use for processing the batch of simulations.
+ max_retries : int
+ Maximum number of retries for each simulation in case of failure before giving up.
+ retry_delay : int
+ Delay in seconds between retries when a simulation fails.
+ progress_callback_upload : Callable[[float], None], optional
+ Optional callback function called when uploading file with ``bytes_in_chunk`` as argument.
+ progress_callback_download : Callable[[float], None], optional
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+
+
+ Returns
+ -------
+ List[ModeSolverData]
+ A list of ModeSolverData objects containing the results from each simulation in the batch. ``None`` is placed in the list for simulations that fail after all retries.
+ """
+ console = get_logging_console()
+
+ if not all(isinstance(x, ModeSolver) for x in mode_solvers):
+ console.log(
+ "Validation Error: All items in `mode_solvers` must be instances of `ModeSolver`."
+ )
+ return []
+
+ num_mode_solvers = len(mode_solvers)
+
+ if results_files is None:
+ results_files = [f"mode_solver_batch_results_{i}.hdf5" for i in range(num_mode_solvers)]
+
+ def handle_mode_solver(index, progress, pbar):
+ retries = 0
+ while retries <= max_retries:
+ try:
+ result = run(
+ mode_solver=mode_solvers[index],
+ task_name=f"{task_name}_{index}",
+ mode_solver_name=f"mode_solver_batch_{index}",
+ folder_name=folder_name,
+ results_file=results_files[index],
+ verbose=False,
+ progress_callback_upload=progress_callback_upload,
+ progress_callback_download=progress_callback_download,
+ )
+ if verbose:
+ progress.update(pbar, advance=1)
+ return result
+ except Exception as e:
+ console.log(f"Error in mode solver {index}: {e!s}")
+ if retries < max_retries:
+ time.sleep(retry_delay)
+ retries += 1
+ else:
+ console.log(f"The {index}-th mode solver failed after {max_retries} tries.")
+ if verbose:
+ progress.update(pbar, advance=1)
+ return None
+
+ if verbose:
+ console.log(f"[cyan]Running a batch of [deep_pink4]{num_mode_solvers} mode solvers.\n")
+
+ # Create the common folder before running the parallel computation
+ _ = Folder.create(folder_name=folder_name)
+
+ with Progress(console=console) as progress:
+ pbar = progress.add_task("Status:", total=num_mode_solvers)
+ results = Parallel(n_jobs=max_workers, backend="threading")(
+ delayed(handle_mode_solver)(i, progress, pbar) for i in range(num_mode_solvers)
+ )
+ # Make sure the progress bar is complete
+ progress.update(pbar, completed=num_mode_solvers, refresh=True)
+ console.log("[green]A batch of `ModeSolver` tasks completed successfully!")
+ else:
+ results = Parallel(n_jobs=max_workers, backend="threading")(
+ delayed(handle_mode_solver)(i, None, None) for i in range(num_mode_solvers)
+ )
+
+ return results
+
+
+class ModeSolverTask(ResourceLifecycle, Submittable, extra=pydantic.Extra.allow):
+ """Interface for managing the running of a :class:`.ModeSolver` task on server."""
+
+ task_id: str = pydantic.Field(
+ None,
+ title="task_id",
+ description="Task ID number, set when the task is created, leave as None.",
+ alias="refId",
+ )
+
+ solver_id: str = pydantic.Field(
+ None,
+ title="solver",
+ description="Solver ID number, set when the task is created, leave as None.",
+ alias="id",
+ )
+
+ real_flex_unit: float = pydantic.Field(
+ None, title="real FlexCredits", description="Billed FlexCredits.", alias="charge"
+ )
+
+ created_at: Optional[datetime] = pydantic.Field(
+ title="created_at", description="Time at which this task was created.", alias="createdAt"
+ )
+
+ status: str = pydantic.Field(
+ None,
+ title="status",
+ description="Mode solver task status.",
+ )
+
+ file_type: str = pydantic.Field(
+ None,
+ title="file_type",
+ description="File type used to upload the mode solver.",
+ alias="fileType",
+ )
+
+ mode_solver: ModeSolver = pydantic.Field(
+ None,
+ title="mode_solver",
+ description="Mode solver being run by this task.",
+ )
+
+ @classmethod
+ def create(
+ cls,
+ mode_solver: ModeSolver,
+ task_name: str = "Untitled",
+ mode_solver_name: str = "mode_solver",
+ folder_name: str = "Mode Solver",
+ ) -> ModeSolverTask:
+ """Create a new mode solver task on the server.
+
+ Parameters
+ ----------
+ mode_solver: :class".ModeSolver"
+ The object that will be uploaded to server in the submitting phase.
+ task_name: str = "Untitled"
+ The name of the task.
+ mode_solver_name: str = "mode_solver"
+ The name of the mode solver to create the in task.
+ folder_name: str = "Mode Solver"
+ The name of the folder to store the task.
+
+ Returns
+ -------
+ :class:`ModeSolverTask`
+ :class:`ModeSolverTask` object containing information about the created task.
+ """
+ folder = Folder.get(folder_name, create=True)
+
+ mode_solver.validate_pre_upload()
+ if isinstance(mode_solver.simulation, Simulation):
+ mode_solver.simulation.validate_pre_upload(source_required=False)
+ elif isinstance(mode_solver.simulation, EMESimulation):
+ # TODO: replace this with native web api support
+ raise SetupError(
+ "'EMESimulation' is not yet supported in the "
+ "remote mode solver web api. Please instead call 'ModeSolver.to_fdtd_mode_solver' "
+ "before using the web api; this replaces the 'EMESimulation' with a 'Simulation' "
+ "that can be used in the remote mode solver. "
+ "Alternatively, you can add a 'ModeSolverMonitor' to the 'EMESimulation' "
+ "and use the EME solver web api."
+ )
+ # mode_solver.simulation.validate_pre_upload()
+ else:
+ raise SetupError("Simulation type not supported in the remote mode solver web api.")
+
+ response_body = {
+ "projectId": folder.folder_id,
+ "taskName": task_name,
+ "protocolVersion": __version__,
+ "modeSolverName": mode_solver_name,
+ "fileType": "Gz",
+ "source": "Python",
+ }
+
+ resp = http.post(MODESOLVER_API, response_body)
+
+ # TODO: actually fix the root cause later.
+ # sometimes POST for mode returns None and crashes the log.info call.
+ # For now this catches it and raises a better error that we can debug.
+ if resp is None:
+ raise WebError(
+ "'ModeSolver' POST request returned 'None'. If you received this error, please "
+ "raise an issue on the Tidy3D front end GitHub repository referencing the following"
+ f"response body '{response_body}'."
+ )
+
+ log.info(
+ "Mode solver created with task_id='%s', solver_id='%s'.", resp["refId"], resp["id"]
+ )
+ return ModeSolverTask(**resp, mode_solver=mode_solver)
+
+ @classmethod
+ def get(
+ cls,
+ task_id: str,
+ solver_id: str,
+ to_file: str = "mode_solver.hdf5",
+ sim_file: str = "simulation.hdf5",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> ModeSolverTask:
+ """Get mode solver task from the server by id.
+
+ Parameters
+ ----------
+ task_id: str
+ Unique identifier of the task on server.
+ solver_id: str
+ Unique identifier of the mode solver in the task.
+ to_file: str = "mode_solver.hdf5"
+ File to store the mode solver downloaded from the task.
+ sim_file: str = "simulation.hdf5"
+ File to store the simulation downloaded from the task.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ :class:`ModeSolverTask`
+ :class:`ModeSolverTask` object containing information about the task.
+ """
+ resp = http.get(f"{MODESOLVER_API}/{task_id}/{solver_id}")
+ task = ModeSolverTask(**resp)
+ mode_solver = task.get_modesolver(to_file, sim_file, verbose, progress_callback)
+ return task.copy(update={"mode_solver": mode_solver})
+
+ def get_info(self) -> ModeSolverTask:
+ """Get the current state of this task on the server.
+
+ Returns
+ -------
+ :class:`ModeSolverTask`
+ :class:`ModeSolverTask` object containing information about the task, without the mode
+ solver.
+ """
+ resp = http.get(f"{MODESOLVER_API}/{self.task_id}/{self.solver_id}")
+ return ModeSolverTask(**resp, mode_solver=self.mode_solver)
+
+ def upload(
+ self, verbose: bool = True, progress_callback: Optional[Callable[[float], None]] = None
+ ) -> None:
+ """Upload this task's 'mode_solver' to the server.
+
+ Parameters
+ ----------
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while uploading the data.
+ """
+ mode_solver = self.mode_solver.copy()
+
+ sim = mode_solver.simulation
+
+ # Upload simulation.hdf5.gz for GUI display
+ file, file_name = tempfile.mkstemp(".hdf5.gz")
+ os.close(file)
+ try:
+ sim.to_hdf5_gz(file_name)
+ upload_file(
+ self.task_id,
+ file_name,
+ SIM_FILE_HDF5_GZ,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ finally:
+ os.unlink(file_name)
+
+ # Upload a single HDF5 file with the full data
+ file, file_name = tempfile.mkstemp(".hdf5.gz")
+ os.close(file)
+ try:
+ mode_solver.to_hdf5_gz(file_name)
+ upload_file(
+ self.solver_id,
+ file_name,
+ MODESOLVER_GZ,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ finally:
+ os.unlink(file_name)
+
+ def submit(
+ self,
+ pay_type: Union[PayType, str] = PayType.AUTO,
+ ):
+ """Start the execution of this task.
+
+ The mode solver must be uploaded to the server with the :meth:`ModeSolverTask.upload` method
+ before this step.
+ """
+ # convert right before sending to API
+ pay_type = PayType(pay_type) if not isinstance(pay_type, PayType) else pay_type
+
+ http.post(
+ f"{MODESOLVER_API}/{self.task_id}/{self.solver_id}/run",
+ {
+ "enableCaching": Env.current.enable_caching,
+ "payType": pay_type.value,
+ },
+ )
+
+ def delete(self):
+ """Delete the mode solver and its corresponding task from the server."""
+ # Delete mode solver
+ http.delete(f"{MODESOLVER_API}/{self.task_id}/{self.solver_id}")
+ # Delete parent task
+ http.delete(f"tidy3d/tasks/{self.task_id}")
+
+ def abort(self):
+ """Abort the mode solver and its corresponding task from the server."""
+ return http.put(
+ "tidy3d/tasks/abort", json={"taskType": "MODE_SOLVER", "taskId": self.solver_id}
+ )
+
+ def get_modesolver(
+ self,
+ to_file: str = "mode_solver.hdf5",
+ sim_file: str = "simulation.hdf5",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> ModeSolver:
+ """Get mode solver associated with this task from the server.
+
+ Parameters
+ ----------
+ to_file: str = "mode_solver.hdf5"
+ File to store the mode solver downloaded from the task.
+ sim_file: str = "simulation.hdf5"
+ File to store the simulation downloaded from the task, if any.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ :class:`ModeSolver`
+ :class:`ModeSolver` object associated with this task.
+ """
+ if self.file_type == "Gz":
+ file, file_path = tempfile.mkstemp(".hdf5.gz")
+ os.close(file)
+ try:
+ download_file(
+ self.solver_id,
+ MODESOLVER_GZ,
+ to_file=file_path,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ mode_solver = ModeSolver.from_hdf5_gz(file_path)
+ finally:
+ os.unlink(file_path)
+
+ elif self.file_type == "Hdf5":
+ file, file_path = tempfile.mkstemp(".hdf5")
+ os.close(file)
+ try:
+ download_file(
+ self.solver_id,
+ MODESOLVER_HDF5,
+ to_file=file_path,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ mode_solver = ModeSolver.from_hdf5(file_path)
+ finally:
+ os.unlink(file_path)
+
+ else:
+ file, file_path = tempfile.mkstemp(".json")
+ os.close(file)
+ try:
+ download_file(
+ self.solver_id,
+ MODESOLVER_JSON,
+ to_file=file_path,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ mode_solver_dict = ModeSolver.dict_from_json(file_path)
+ finally:
+ os.unlink(file_path)
+
+ download_file(
+ self.task_id,
+ SIMULATION_JSON,
+ to_file=sim_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ mode_solver_dict["simulation"] = Simulation.from_json(sim_file)
+ mode_solver = ModeSolver.parse_obj(mode_solver_dict)
+
+ # Store requested mode solver file
+ mode_solver.to_file(to_file)
+
+ return mode_solver
+
+ def get_result(
+ self,
+ to_file: str = "mode_solver_data.hdf5",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> ModeSolverData:
+ """Get mode solver results for this task from the server.
+
+ Parameters
+ ----------
+ to_file: str = "mode_solver_data.hdf5"
+ File to store the mode solver downloaded from the task.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ :class:`.ModeSolverData`
+ Mode solver data with the calculated results.
+ """
+
+ file = None
+ try:
+ file = download_gz_file(
+ resource_id=self.solver_id,
+ remote_filename=MODESOLVER_RESULT_GZ,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ except ClientError:
+ if verbose:
+ console = get_logger_console()
+ console.log(f"Unable to download '{MODESOLVER_RESULT_GZ}'.")
+
+ if not file:
+ try:
+ file = download_file(
+ resource_id=self.solver_id,
+ remote_filename=MODESOLVER_RESULT,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ except Exception as e:
+ raise WebError(
+ "Failed to download the simulation data file from the server. "
+ "Please confirm that the task was successfully run."
+ ) from e
+
+ data = ModeSolverData.from_hdf5(to_file)
+ data = data.copy(
+ update={"monitor": self.mode_solver.to_mode_solver_monitor(name=MODE_MONITOR_NAME)}
+ )
+
+ self.mode_solver._cached_properties["data_raw"] = data
+
+ # Perform symmetry expansion
+ self.mode_solver._cached_properties.pop("data", None)
+ return self.mode_solver.data
+
+ def get_log(
+ self,
+ to_file: str = "mode_solver.log",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> pathlib.Path:
+ """Get execution log for this task from the server.
+
+ Parameters
+ ----------
+ to_file: str = "mode_solver.log"
+ File to store the mode solver downloaded from the task.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+ return download_file(
+ self.solver_id,
+ MODESOLVER_LOG,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
diff --git a/tidy3d/plugins/smatrix/web/api/tidy3d_stub.py b/tidy3d/plugins/smatrix/web/api/tidy3d_stub.py
new file mode 100644
index 0000000000..f66f9da53d
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/tidy3d_stub.py
@@ -0,0 +1,302 @@
+"""Stub for webapi"""
+
+from __future__ import annotations
+
+import json
+from typing import Callable, Optional, Union
+
+import pydantic.v1 as pd
+from pydantic.v1 import BaseModel
+
+from tidy3d import log
+from tidy3d.components.base import _get_valid_extension
+from tidy3d.components.data.monitor_data import ModeSolverData
+from tidy3d.components.data.sim_data import SimulationData
+from tidy3d.components.eme.data.sim_data import EMESimulationData
+from tidy3d.components.eme.simulation import EMESimulation
+from tidy3d.components.mode.data.sim_data import ModeSimulationData
+from tidy3d.components.mode.simulation import ModeSimulation
+from tidy3d.components.simulation import Simulation
+from tidy3d.components.tcad.data.sim_data import (
+ HeatChargeSimulationData,
+ HeatSimulationData,
+ VolumeMesherData,
+)
+from tidy3d.components.tcad.mesher import VolumeMesher
+from tidy3d.components.tcad.simulation.heat import HeatSimulation
+from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation
+from tidy3d.plugins.mode.mode_solver import ModeSolver
+from tidy3d.plugins.smatrix import (
+ ComponentModeler,
+ ComponentModelerData,
+ TerminalComponentModeler,
+ TerminalComponentModelerData,
+)
+from tidy3d.web.core.file_util import (
+ read_simulation_from_hdf5,
+ read_simulation_from_hdf5_gz,
+ read_simulation_from_json,
+)
+from tidy3d.web.core.stub import TaskStub, TaskStubData
+from tidy3d.web.core.types import TaskType
+
+SimulationType = Union[
+ Simulation,
+ HeatChargeSimulation,
+ HeatSimulation,
+ EMESimulation,
+ ModeSolver,
+ ModeSimulation,
+ VolumeMesher,
+ ComponentModeler,
+ TerminalComponentModeler,
+]
+SimulationDataType = Union[
+ SimulationData,
+ HeatChargeSimulationData,
+ HeatSimulationData,
+ EMESimulationData,
+ ModeSolverData,
+ ModeSimulationData,
+ ComponentModelerData,
+ TerminalComponentModelerData,
+]
+
+
+class Tidy3dStub(BaseModel, TaskStub):
+ simulation: SimulationType = pd.Field(discriminator="type")
+
+ @classmethod
+ def from_file(cls, file_path: str) -> SimulationType:
+ """Loads a Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ from .yaml, .json, or .hdf5 file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] from.
+
+ Returns
+ -------
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ An instance of the component class calling ``load``.
+
+ Example
+ -------
+ >>> simulation = Simulation.from_file(fname='folder/sim.json') # doctest: +SKIP
+ """
+ extension = _get_valid_extension(file_path)
+ if extension == ".json":
+ json_str = read_simulation_from_json(file_path)
+ elif extension == ".hdf5":
+ json_str = read_simulation_from_hdf5(file_path)
+ elif extension == ".hdf5.gz":
+ json_str = read_simulation_from_hdf5_gz(file_path)
+
+ data = json.loads(json_str)
+ type_ = data["type"]
+ if type_ == "Simulation":
+ sim = Simulation.from_file(file_path)
+ elif type_ == "ModeSolver":
+ sim = ModeSolver.from_file(file_path)
+ elif type_ == "HeatSimulation":
+ sim = HeatSimulation.from_file(file_path)
+ elif type_ == "HeatChargeSimulation":
+ sim = HeatChargeSimulation.from_file(file_path)
+ elif type_ == "EMESimulation":
+ sim = EMESimulation.from_file(file_path)
+ elif type_ == "ModeSimulation":
+ sim = ModeSimulation.from_file(file_path)
+ elif type_ == "VolumeMesher":
+ sim = VolumeMesher.from_file(file_path)
+ elif type_ == "ComponentModeler":
+ sim = ComponentModeler.from_file(file_path)
+ elif type_ == "TerminalComponentModeler":
+ sim = TerminalComponentModeler.from_file(file_path)
+
+ return sim
+
+ def to_file(
+ self,
+ file_path: str,
+ ):
+ """Exports Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] instance to .yaml, .json,
+ or .hdf5 file
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to save the :class:`Stub` to.
+
+ Example
+ -------
+ >>> simulation.to_file(fname='folder/sim.json') # doctest: +SKIP
+ """
+ self.simulation.to_file(file_path)
+
+ def to_hdf5_gz(self, fname: str, custom_encoders: Optional[list[Callable]] = None) -> None:
+ """Exports Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] instance to .hdf5.gz file.
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .hdf5.gz file to save
+ the Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`] to.
+ custom_encoders : List[Callable]
+ List of functions accepting (fname: str, group_path: str, value: Any) that take
+ the ``value`` supplied and write it to the hdf5 ``fname`` at ``group_path``.
+
+ Example
+ -------
+ >>> simulation.to_hdf5_gz(fname='folder/sim.hdf5.gz') # doctest: +SKIP
+ """
+
+ self.simulation.to_hdf5_gz(fname)
+
+ def get_type(self) -> str:
+ """Get simulation instance type.
+
+ Returns
+ -------
+ :class:`TaskType`
+ An instance Type of the component class calling ``load``.
+ """
+ if isinstance(self.simulation, Simulation):
+ return TaskType.FDTD.name
+ if isinstance(self.simulation, ModeSolver):
+ return TaskType.MODE_SOLVER.name
+ if isinstance(self.simulation, HeatSimulation):
+ return TaskType.HEAT.name
+ if isinstance(self.simulation, HeatChargeSimulation):
+ return TaskType.HEAT_CHARGE.name
+ if isinstance(self.simulation, EMESimulation):
+ return TaskType.EME.name
+ if isinstance(self.simulation, ModeSimulation):
+ return TaskType.MODE.name
+ elif isinstance(self.simulation, VolumeMesher):
+ return TaskType.VOLUME_MESH.name
+ elif isinstance(self.simulation, ComponentModeler):
+ return TaskType.COMPONENT_MODELER.name
+ elif isinstance(self.simulation, TerminalComponentModeler):
+ return TaskType.TERMINAL_COMPONENT_MODELER.name
+
+ def validate_pre_upload(self, source_required) -> None:
+ """Perform some pre-checks on instances of component"""
+ if isinstance(self.simulation, Simulation):
+ self.simulation.validate_pre_upload(source_required)
+ elif isinstance(self.simulation, EMESimulation):
+ self.simulation.validate_pre_upload()
+
+
+class Tidy3dStubData(BaseModel, TaskStubData):
+ """"""
+
+ data: SimulationDataType
+
+ @classmethod
+ def from_file(cls, file_path: str) -> SimulationDataType:
+ """Loads a Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ from .yaml, .json, or .hdf5 file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] from.
+
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ An instance of the component class calling ``load``.
+ """
+ extension = _get_valid_extension(file_path)
+ if extension == ".json":
+ json_str = read_simulation_from_json(file_path)
+ elif extension == ".hdf5":
+ json_str = read_simulation_from_hdf5(file_path)
+ elif extension == ".hdf5.gz":
+ json_str = read_simulation_from_hdf5_gz(file_path)
+
+ data = json.loads(json_str)
+ type_ = data["type"]
+ if type_ == "SimulationData":
+ sim_data = SimulationData.from_file(file_path)
+ elif type_ == "ModeSolverData":
+ sim_data = ModeSolverData.from_file(file_path)
+ elif type_ == "HeatSimulationData":
+ sim_data = HeatSimulationData.from_file(file_path)
+ elif type_ == "HeatChargeSimulationData":
+ sim_data = HeatChargeSimulationData.from_file(file_path)
+ elif type_ == "EMESimulationData":
+ sim_data = EMESimulationData.from_file(file_path)
+ elif type_ == "ModeSimulationData":
+ sim_data = ModeSimulationData.from_file(file_path)
+ elif type_ == "VolumeMesherData":
+ sim_data = VolumeMesherData.from_file(file_path)
+ elif type_ == "ComponentModelerData":
+ sim_data = ComponentModelerData.from_file(file_path)
+ elif type_ == "TerminalComponentModelerData":
+ sim_data = TerminalComponentModelerData.from_file(file_path)
+
+ return sim_data
+
+ def to_file(self, file_path: str):
+ """Exports Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] instance
+ to .yaml, .json, or .hdf5 file
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to save the
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] to.
+
+ Example
+ -------
+ >>> simulation.to_file(fname='folder/sim.json') # doctest: +SKIP
+ """
+ self.data.to_file(file_path)
+
+ @classmethod
+ def postprocess(cls, file_path: str) -> SimulationDataType:
+ """Load .yaml, .json, or .hdf5 file to
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] instance.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to save the
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`] to.
+
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ An instance of the component class calling ``load``.
+ """
+ stub_data = Tidy3dStubData.from_file(file_path)
+
+ check_log_msg = "For more information, check 'SimulationData.log' or use "
+ check_log_msg += "'web.download_log(task_id)'."
+ warned_about_warnings = False
+
+ if isinstance(stub_data, SimulationData):
+ final_decay_value = stub_data.final_decay_value
+ shutoff_value = stub_data.simulation.shutoff
+ if stub_data.diverged:
+ log.warning("The simulation has diverged! " + check_log_msg)
+ warned_about_warnings = True
+ elif (shutoff_value != 0) and (final_decay_value > shutoff_value):
+ log.warning(
+ f"Simulation final field decay value of {final_decay_value} is greater than "
+ f"the simulation shutoff threshold of {shutoff_value}. Consider running the "
+ "simulation again with a larger 'run_time' duration for more accurate results."
+ )
+
+ if (
+ not isinstance(stub_data, (ModeSolverData, ModeSimulationData))
+ and "WARNING" in stub_data.log
+ and not warned_about_warnings
+ ):
+ log.warning("Warning messages were found in the solver log. " + check_log_msg)
+
+ return stub_data
diff --git a/tidy3d/plugins/smatrix/web/api/webapi.py b/tidy3d/plugins/smatrix/web/api/webapi.py
new file mode 100644
index 0000000000..c45012499b
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/api/webapi.py
@@ -0,0 +1,1184 @@
+"""Provides lowest level, user-facing interface to server."""
+
+from __future__ import annotations
+
+import json
+import os
+import tempfile
+import time
+from typing import Callable, Literal, Optional, Union
+
+from requests import HTTPError
+from rich.progress import Progress
+
+from tidy3d.components.medium import AbstractCustomMedium
+from tidy3d.components.mode.mode_solver import ModeSolver
+from tidy3d.components.mode.simulation import ModeSimulation
+from tidy3d.exceptions import WebError
+from tidy3d.log import get_logging_console, log
+from tidy3d.web.core.account import Account
+from tidy3d.web.core.constants import (
+ MODE_DATA_HDF5_GZ,
+ MODE_FILE_HDF5_GZ,
+ SIM_FILE_HDF5,
+ SIM_FILE_HDF5_GZ,
+ SIMULATION_DATA_HDF5_GZ,
+ TaskId,
+)
+from tidy3d.web.core.environment import Env
+from tidy3d.web.core.task_core import Folder, SimulationTask
+from tidy3d.web.core.task_info import ChargeType, TaskInfo
+from tidy3d.web.core.types import PayType
+
+from .connect_util import REFRESH_TIME, get_grid_points_str, get_time_steps_str, wait_for_connection
+from .tidy3d_stub import SimulationDataType, SimulationType, Tidy3dStub, Tidy3dStubData
+
+# time between checking run status
+RUN_REFRESH_TIME = 1.0
+
+# file names when uploading to S3
+SIM_FILE_JSON = "simulation.json"
+
+# not all solvers are supported yet in GUI
+GUI_SUPPORTED_TASK_TYPES = ["FDTD", "MODE_SOLVER", "HEAT"]
+
+# if a solver is in beta stage, cost is subject to change
+BETA_TASK_TYPES = ["HEAT", "EME", "HEAT_CHARGE", "VOLUME_MESH"]
+
+# map task_type to solver name for display
+SOLVER_NAME = {
+ "FDTD": "FDTD",
+ "MODE_SOLVER": "Mode",
+ "MODE": "Mode",
+ "EME": "EME",
+ "HEAT": "Heat",
+ "HEAT_CHARGE": "HeatCharge",
+ "VOLUME_MESH": "VolumeMesher",
+}
+
+
+def _get_url(task_id: str) -> str:
+ """Get the URL for a task on our server."""
+ return f"{Env.current.website_endpoint}/workbench?taskId={task_id}"
+
+
+def _get_folder_url(folder_id: str) -> str:
+ """Get the URL for a task folder on our server."""
+ return f"{Env.current.website_endpoint}/folders/{folder_id}"
+
+
+@wait_for_connection
+def run(
+ simulation: SimulationType,
+ task_name: str,
+ folder_name: str = "default",
+ path: str = "simulation_data.hdf5",
+ callback_url: Optional[str] = None,
+ verbose: bool = True,
+ progress_callback_upload: Optional[Callable[[float], None]] = None,
+ progress_callback_download: Optional[Callable[[float], None]] = None,
+ solver_version: Optional[str] = None,
+ worker_group: Optional[str] = None,
+ simulation_type: str = "tidy3d",
+ parent_tasks: Optional[list[str]] = None,
+ reduce_simulation: Literal["auto", True, False] = "auto",
+ pay_type: Union[PayType, str] = PayType.AUTO,
+ priority: Optional[int] = None,
+) -> SimulationDataType:
+ """
+ Submits a :class:`.Simulation` to server, starts running, monitors progress, downloads,
+ and loads results as a :class:`.SimulationDataType` object.
+
+ Parameters
+ ----------
+ simulation : Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ Simulation to upload to server.
+ task_name : str
+ Name of task.
+ folder_name : str = "default"
+ Name of folder to store task on web UI.
+ path : str = "simulation_data.hdf5"
+ Path to download results file (.hdf5), including filename.
+ callback_url : str = None
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ simulation_type : str = "tidy3d"
+ Type of simulation being uploaded.
+ progress_callback_upload : Callable[[float], None] = None
+ Optional callback function called when uploading file with ``bytes_in_chunk`` as argument.
+ progress_callback_download : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+ solver_version: str = None
+ target solver version.
+ worker_group: str = None
+ worker group
+ reduce_simulation : Literal["auto", True, False] = "auto"
+ Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.
+ pay_type: Union[PayType, str] = PayType.AUTO
+ Which method to pay the simulation.
+ priority: int = None
+ Task priority for vGPU queue (1=lowest, 10=highest).
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ Object containing solver results for the supplied simulation.
+
+ Notes
+ -----
+
+ Submitting a simulation to our cloud server is very easily done by a simple web API call.
+
+ .. code-block:: python
+
+ sim_data = tidy3d.web.api.webapi.run(simulation, task_name='my_task', path='out/data.hdf5')
+
+ The :meth:`tidy3d.web.api.webapi.run()` method shows the simulation progress by default. When uploading a
+ simulation to the server without running it, you can use the :meth:`tidy3d.web.api.webapi.monitor`,
+ :meth:`tidy3d.web.api.container.Job.monitor`, or :meth:`tidy3d.web.api.container.Batch.monitor` methods to
+ display the progress of your simulation(s).
+
+ Examples
+ --------
+
+ To access the original :class:`.Simulation` object that created the simulation data you can use:
+
+ .. code-block:: python
+
+ # Run the simulation.
+ sim_data = web.run(simulation, task_name='task_name', path='out/sim.hdf5')
+
+ # Get a copy of the original simulation object.
+ sim_copy = sim_data.simulation
+
+ See Also
+ --------
+
+ :meth:`tidy3d.web.api.webapi.monitor`
+ Print the real time task progress until completion.
+
+ :meth:`tidy3d.web.api.container.Job.monitor`
+ Monitor progress of running :class:`Job`.
+
+ :meth:`tidy3d.web.api.container.Batch.monitor`
+ Monitor progress of each of the running tasks.
+ """
+ task_id = upload(
+ simulation=simulation,
+ task_name=task_name,
+ folder_name=folder_name,
+ callback_url=callback_url,
+ verbose=verbose,
+ progress_callback=progress_callback_upload,
+ simulation_type=simulation_type,
+ parent_tasks=parent_tasks,
+ solver_version=solver_version,
+ reduce_simulation=reduce_simulation,
+ )
+ start(
+ task_id,
+ solver_version=solver_version,
+ worker_group=worker_group,
+ pay_type=pay_type,
+ priority=priority,
+ )
+ monitor(task_id, verbose=verbose)
+ data = load(
+ task_id=task_id, path=path, verbose=verbose, progress_callback=progress_callback_download
+ )
+ if isinstance(simulation, ModeSolver):
+ simulation._patch_data(data=data)
+ return data
+
+
+@wait_for_connection
+def upload(
+ simulation: SimulationType,
+ task_name: str,
+ folder_name: str = "default",
+ callback_url: Optional[str] = None,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ simulation_type: str = "tidy3d",
+ parent_tasks: Optional[list[str]] = None,
+ source_required: bool = True,
+ solver_version: Optional[str] = None,
+ reduce_simulation: Literal["auto", True, False] = "auto",
+) -> TaskId:
+ """
+ Upload simulation to server, but do not start running :class:`.Simulation`.
+
+ Parameters
+ ----------
+ simulation : Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ Simulation to upload to server.
+ task_name : str
+ Name of task.
+ folder_name : str
+ Name of folder to store task on web UI
+ callback_url : str = None
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called when uploading file with ``bytes_in_chunk`` as argument.
+ simulation_type : str = "tidy3d"
+ Type of simulation being uploaded.
+ parent_tasks : List[str]
+ List of related task ids.
+ source_required: bool = True
+ If ``True``, simulations without sources will raise an error before being uploaded.
+ solver_version: str = None
+ target solver version.
+ reduce_simulation: Literal["auto", True, False] = "auto"
+ Whether to reduce structures in the simulation to the simulation domain only. Note: currently only implemented for the mode solver.
+
+ Returns
+ -------
+ str
+ Unique identifier of task on server.
+
+
+ Notes
+ -----
+
+ Once you've created a ``job`` object using :class:`tidy3d.web.api.container.Job`, you can upload it to our servers with:
+
+ .. code-block:: python
+
+ web.upload(simulation, task_name="task_name", verbose=verbose)
+
+ It will not run until you explicitly tell it to do so with :meth:`tidy3d.web.api.webapi.start`.
+
+ """
+
+ if isinstance(simulation, (ModeSolver, ModeSimulation)):
+ simulation = get_reduced_simulation(simulation, reduce_simulation)
+
+ stub = Tidy3dStub(simulation=simulation)
+ stub.validate_pre_upload(source_required=source_required)
+ log.debug("Creating task.")
+
+ task_type = stub.get_type()
+
+ task = SimulationTask.create(
+ task_type, task_name, folder_name, callback_url, simulation_type, parent_tasks, "Gz"
+ )
+ if verbose:
+ console = get_logging_console()
+ console.log(
+ f"Created task '{task_name}' with task_id '{task.task_id}' and task_type '{task_type}'."
+ )
+ if task_type in BETA_TASK_TYPES:
+ solver_name = SOLVER_NAME[task_type]
+ console.log(
+ f"Tidy3D's {solver_name} solver is currently in the beta stage. "
+ f"Cost of {solver_name} simulations is subject to change in the future."
+ )
+ if task_type in GUI_SUPPORTED_TASK_TYPES:
+ url = _get_url(task.task_id)
+ folder_url = _get_folder_url(task.folder_id)
+ console.log(f"View task using web UI at [link={url}]'{url}'[/link].")
+ console.log(f"Task folder: [link={folder_url}]'{task.folder_name}'[/link].")
+
+ remote_sim_file = SIM_FILE_HDF5_GZ
+ if task_type == "MODE_SOLVER":
+ remote_sim_file = MODE_FILE_HDF5_GZ
+
+ task.upload_simulation(
+ stub=stub,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ remote_sim_file=remote_sim_file,
+ )
+ estimate_cost(task_id=task.task_id, solver_version=solver_version, verbose=verbose)
+
+ task.validate_post_upload(parent_tasks=parent_tasks)
+
+ # log the url for the task in the web UI
+ log.debug(f"{Env.current.website_endpoint}/folders/{task.folder_id}/tasks/{task.task_id}")
+ return task.task_id
+
+
+def get_reduced_simulation(simulation, reduce_simulation):
+ """
+ Adjust the given simulation object based on the reduce_simulation parameter. Currently only
+ implemented for the mode solver.
+
+ Parameters
+ ----------
+ simulation : Simulation
+ The simulation object to be potentially reduced.
+ reduce_simulation : Literal["auto", True, False]
+ Determines whether to reduce the simulation. If "auto", the function will decide based on
+ the presence of custom mediums in the simulation.
+
+ Returns
+ -------
+ Simulation
+ The potentially reduced simulation object.
+ """
+
+ """
+ TODO: This only works for the mode solver, which is also why `simulation.simulation.scene` is
+ used below. After refactor to use the new ModeSimulation, it should be possible to put the call
+ to this function outside of the MODE_SOLVER check in the upload function. We could implement
+ dummy `reduced_simulation_copy` methods for the other solvers or also implement reductions
+ there. Note that if we do the latter we may want to also modify the warning below to only
+ happen if there are custom media *and* they extend beyond the simulation domain.
+ """
+ if reduce_simulation == "auto":
+ if isinstance(simulation, ModeSimulation):
+ sim_mediums = simulation.scene.mediums
+ else:
+ sim_mediums = simulation.simulation.scene.mediums
+ contains_custom = any(isinstance(med, AbstractCustomMedium) for med in sim_mediums)
+ reduce_simulation = contains_custom
+
+ if reduce_simulation:
+ log.warning(
+ f"The {type(simulation)} object contains custom mediums. It will be "
+ "automatically restricted to the solver domain to reduce data for uploading. "
+ "To force uploading the original object use 'reduce_simulation=False'."
+ " Setting 'reduce_simulation=True' will force simulation reduction in all cases and"
+ " silence this warning."
+ )
+ if reduce_simulation:
+ return simulation.reduced_simulation_copy
+ return simulation
+
+
+@wait_for_connection
+def get_info(task_id: TaskId, verbose: bool = True) -> TaskInfo:
+ """Return information about a task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ Returns
+ -------
+ :class:`TaskInfo`
+ Object containing information about status, size, credits of task.
+ """
+ task = SimulationTask.get(task_id, verbose)
+ if not task:
+ raise ValueError("Task not found.")
+ return TaskInfo(**{"taskId": task.task_id, "taskType": task.task_type, **task.dict()})
+
+
+@wait_for_connection
+def start(
+ task_id: TaskId,
+ solver_version: Optional[str] = None,
+ worker_group: Optional[str] = None,
+ pay_type: Union[PayType, str] = PayType.AUTO,
+ priority: Optional[int] = None,
+) -> None:
+ """Start running the simulation associated with task.
+
+ Parameters
+ ----------
+
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ solver_version: str = None
+ target solver version.
+ worker_group: str = None
+ worker group
+ pay_type: Union[PayType, str] = PayType.AUTO
+ Which method to pay the simulation
+ priority: int = None
+ Task priority for vGPU queue (1=lowest, 10=highest).
+ Note
+ ----
+ To monitor progress, can call :meth:`monitor` after starting simulation.
+ """
+ if priority is not None and (priority < 1 or priority > 10):
+ raise ValueError("Priority must be between '1' and '10' if specified.")
+ task = SimulationTask.get(task_id)
+ if not task:
+ raise ValueError("Task not found.")
+ task.submit(
+ solver_version=solver_version,
+ worker_group=worker_group,
+ pay_type=pay_type,
+ priority=priority,
+ )
+
+
+@wait_for_connection
+def get_run_info(task_id: TaskId) -> tuple[Optional[float], Optional[float]]:
+ """Gets the % done and field_decay for a running task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+
+ Returns
+ -------
+ perc_done : float
+ Percentage of run done (in terms of max number of time steps).
+ Is ``None`` if run info not available.
+ field_decay : float
+ Average field intensity normalized to max value (1.0).
+ Is ``None`` if run info not available.
+ """
+ task = SimulationTask(taskId=task_id)
+ return task.get_running_info()
+
+
+def get_status(task_id) -> str:
+ """Get the status of a task. Raises an error if status is "error".
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ """
+ task_info = get_info(task_id)
+ status = task_info.status
+ if status == "visualize":
+ return "success"
+ if status == "error":
+ try:
+ # Try to obtain the error message
+ task = SimulationTask(taskId=task_id)
+ with tempfile.NamedTemporaryFile(suffix=".json") as tmp_file:
+ task.get_error_json(to_file=tmp_file.name)
+ with open(tmp_file.name) as f:
+ error_content = json.load(f)
+ error_msg = error_content["msg"]
+ except Exception:
+ # If the error message could not be obtained, raise a generic error message
+ error_msg = "Error message could not be obtained, please contact customer support."
+
+ raise WebError(f"Error running task {task_id}! {error_msg}")
+ return status
+
+
+def monitor(task_id: TaskId, verbose: bool = True) -> None:
+ """
+ Print the real time task progress until completion.
+
+ Notes
+ -----
+
+ To monitor the simulation's progress and wait for its completion, use:
+
+ .. code-block:: python
+
+ tidy3d.web.api.webapi.monitor(job.task_id, verbose=verbose).
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+
+ Note
+ ----
+ To load results when finished, may call :meth:`load`.
+ """
+
+ console = get_logging_console() if verbose else None
+
+ task_info = get_info(task_id)
+
+ task_name = task_info.taskName
+
+ task_type = task_info.taskType
+
+ break_statuses = ("success", "error", "diverged", "deleted", "draft", "abort", "aborted")
+
+ def get_estimated_cost() -> float:
+ """Get estimated cost, if None, is not ready."""
+ task_info = get_info(task_id)
+ block_info = task_info.taskBlockInfo
+ if block_info and block_info.chargeType == ChargeType.FREE:
+ est_flex_unit = 0
+ grid_points = block_info.maxGridPoints
+ time_steps = block_info.maxTimeSteps
+ grid_points_str = get_grid_points_str(grid_points)
+ time_steps_str = get_time_steps_str(time_steps)
+ console.log(
+ f"You are running this simulation for FREE. Your current plan allows"
+ f" up to {block_info.maxFreeCount} free non-concurrent simulations per"
+ f" day (under {grid_points_str} grid points and {time_steps_str}"
+ f" time steps)"
+ )
+ else:
+ est_flex_unit = task_info.estFlexUnit
+ return est_flex_unit
+
+ def monitor_preprocess() -> None:
+ """Periodically check the status."""
+ status = get_status(task_id)
+ while status not in break_statuses and status != "running":
+ new_status = get_status(task_id)
+ if new_status != status:
+ status = new_status
+ if verbose and status != "running":
+ console.log(f"status = {status}")
+ time.sleep(REFRESH_TIME)
+
+ status = get_status(task_id)
+
+ if verbose:
+ console.log(f"status = {status}")
+
+ # already done
+ if status in break_statuses:
+ return
+
+ # preprocessing
+ if verbose:
+ console.log(
+ "To cancel the simulation, use 'web.abort(task_id)' or 'web.delete(task_id)' "
+ "or abort/delete the task in the web "
+ "UI. Terminating the Python script will not stop the job running on the cloud."
+ )
+ with console.status(f"[bold green]Waiting for '{task_name}'...", spinner="runner"):
+ monitor_preprocess()
+ else:
+ monitor_preprocess()
+
+ # if the estimated cost is ready, print it
+ if verbose:
+ get_estimated_cost()
+ console.log("starting up solver")
+
+ # while running but before the percentage done is available, keep waiting
+ while get_run_info(task_id)[0] is None and get_status(task_id) == "running":
+ time.sleep(REFRESH_TIME)
+
+ # while running but percentage done is available
+ if verbose:
+ # verbose case, update progressbar
+ console.log("running solver")
+ if task_type == "FDTD":
+ with Progress(console=console) as progress:
+ pbar_pd = progress.add_task("% done", total=100)
+ perc_done, _ = get_run_info(task_id)
+
+ while (
+ perc_done is not None and perc_done < 100 and get_status(task_id) == "running"
+ ):
+ perc_done, field_decay = get_run_info(task_id)
+ new_description = f"solver progress (field decay = {field_decay:.2e})"
+ progress.update(pbar_pd, completed=perc_done, description=new_description)
+ time.sleep(RUN_REFRESH_TIME)
+
+ perc_done, field_decay = get_run_info(task_id)
+ if perc_done is not None and perc_done < 100 and field_decay > 0:
+ console.log(f"early shutoff detected at {perc_done:1.0f}%, exiting.")
+
+ new_description = f"solver progress (field decay = {field_decay:.2e})"
+ progress.update(pbar_pd, completed=100, refresh=True, description=new_description)
+ elif task_type == "EME":
+ with Progress(console=console) as progress:
+ pbar_pd = progress.add_task("% done", total=100)
+ perc_done, _ = get_run_info(task_id)
+
+ while (
+ perc_done is not None and perc_done < 100 and get_status(task_id) == "running"
+ ):
+ perc_done, _ = get_run_info(task_id)
+ new_description = "solver progress"
+ progress.update(pbar_pd, completed=perc_done, description=new_description)
+ time.sleep(RUN_REFRESH_TIME)
+
+ perc_done, _ = get_run_info(task_id)
+ new_description = "solver progress"
+ progress.update(pbar_pd, completed=100, refresh=True, description=new_description)
+ else:
+ while get_status(task_id) == "running":
+ perc_done, _ = get_run_info(task_id)
+ time.sleep(RUN_REFRESH_TIME)
+
+ else:
+ # non-verbose case, just keep checking until status is not running or perc_done >= 100
+ perc_done, _ = get_run_info(task_id)
+ while perc_done is not None and perc_done < 100 and get_status(task_id) == "running":
+ perc_done, field_decay = get_run_info(task_id)
+ time.sleep(RUN_REFRESH_TIME)
+
+ # post processing
+ if verbose:
+ status = get_status(task_id)
+ if status != "running":
+ console.log(f"status = {status}")
+
+ with console.status(f"[bold green]Finishing '{task_name}'...", spinner="runner"):
+ while status not in break_statuses:
+ new_status = get_status(task_id)
+ if new_status != status:
+ status = new_status
+ console.log(f"status = {status}")
+ time.sleep(REFRESH_TIME)
+
+ if task_type in GUI_SUPPORTED_TASK_TYPES:
+ url = _get_url(task_id)
+ console.log(f"View simulation result at [blue underline][link={url}]'{url}'[/link].")
+ else:
+ while get_status(task_id) not in break_statuses:
+ time.sleep(REFRESH_TIME)
+
+
+@wait_for_connection
+def download(
+ task_id: TaskId,
+ path: str = "simulation_data.hdf5",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> None:
+ """Download results of task to file.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str = "simulation_data.hdf5"
+ Download path to .hdf5 data file (including filename).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+
+ """
+
+ task_info = get_info(task_id)
+ task_type = task_info.taskType
+
+ remote_data_file = SIMULATION_DATA_HDF5_GZ
+ if task_type == "MODE_SOLVER":
+ remote_data_file = MODE_DATA_HDF5_GZ
+
+ task = SimulationTask(taskId=task_id)
+ task.get_sim_data_hdf5(
+ path,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ remote_data_file=remote_data_file,
+ )
+
+
+@wait_for_connection
+def download_json(task_id: TaskId, path: str = SIM_FILE_JSON, verbose: bool = True) -> None:
+ """Download the ``.json`` file associated with the :class:`.Simulation` of a given task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str = "simulation.json"
+ Download path to .json file of simulation (including filename).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+
+ """
+
+ task = SimulationTask(taskId=task_id)
+ task.get_simulation_json(path, verbose=verbose)
+
+
+@wait_for_connection
+def download_hdf5(
+ task_id: TaskId,
+ path: str = SIM_FILE_HDF5,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> None:
+ """Download the ``.hdf5`` file associated with the :class:`.Simulation` of a given task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str = "simulation.hdf5"
+ Download path to .hdf5 file of simulation (including filename).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+
+ """
+ task_info = get_info(task_id)
+ task_type = task_info.taskType
+
+ remote_sim_file = SIM_FILE_HDF5_GZ
+ if task_type == "MODE_SOLVER":
+ remote_sim_file = MODE_FILE_HDF5_GZ
+
+ task = SimulationTask(taskId=task_id)
+ task.get_simulation_hdf5(
+ path, verbose=verbose, progress_callback=progress_callback, remote_sim_file=remote_sim_file
+ )
+
+
+@wait_for_connection
+def load_simulation(
+ task_id: TaskId, path: str = SIM_FILE_JSON, verbose: bool = True
+) -> SimulationType:
+ """Download the ``.json`` file of a task and load the associated simulation.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str = "simulation.json"
+ Download path to .json file of simulation (including filename).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+
+ Returns
+ -------
+ Union[:class:`.Simulation`, :class:`.HeatSimulation`, :class:`.EMESimulation`]
+ Simulation loaded from downloaded json file.
+ """
+
+ task = SimulationTask.get(task_id)
+ task.get_simulation_json(path, verbose=verbose)
+ return Tidy3dStub.from_file(path)
+
+
+@wait_for_connection
+def download_log(
+ task_id: TaskId,
+ path: str = "tidy3d.log",
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> None:
+ """Download the tidy3d log file associated with a task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str = "tidy3d.log"
+ Download path to log file (including filename).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+
+ Note
+ ----
+ To load downloaded results into data, call :meth:`load` with option ``replace_existing=False``.
+ """
+ task = SimulationTask(taskId=task_id)
+ task.get_log(path, verbose=verbose, progress_callback=progress_callback)
+
+
+@wait_for_connection
+def load(
+ task_id: TaskId,
+ path: str = "simulation_data.hdf5",
+ replace_existing: bool = True,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> SimulationDataType:
+ """
+ Download and Load simulation results into :class:`.SimulationData` object.
+
+ Notes
+ -----
+
+ After the simulation is complete, you can load the results into a :class:`.SimulationData` object by its
+ ``task_id`` using:
+
+ .. code-block:: python
+
+ sim_data = web.load(task_id, path="outt/sim.hdf5", verbose=verbose)
+
+ The :meth:`tidy3d.web.api.webapi.load` method is very convenient to load and postprocess results from simulations
+ created using Tidy3D GUI.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ path : str
+ Download path to .hdf5 data file (including filename).
+ replace_existing : bool = True
+ Downloads the data even if path exists (overwriting the existing).
+ verbose : bool = True
+ If ``True``, will print progressbars and status, otherwise, will run silently.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called when downloading file with ``bytes_in_chunk`` as argument.
+
+ Returns
+ -------
+ Union[:class:`.SimulationData`, :class:`.HeatSimulationData`, :class:`.EMESimulationData`]
+ Object containing simulation data.
+ """
+ if not os.path.exists(path) or replace_existing:
+ download(task_id=task_id, path=path, verbose=verbose, progress_callback=progress_callback)
+
+ if verbose:
+ console = get_logging_console()
+ console.log(f"loading simulation from {path}")
+
+ stub_data = Tidy3dStubData.postprocess(path)
+ return stub_data
+
+
+@wait_for_connection
+def delete(task_id: TaskId, versions: bool = False) -> TaskInfo:
+ """Delete server-side data associated with task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ versions : bool = False
+ If ``True``, delete all versions of the task in the task group. Otherwise, delete only the version associated with the task ID.
+
+ Returns
+ -------
+ TaskInfo
+ Object containing information about status, size, credits of task.
+ """
+ task = SimulationTask(taskId=task_id)
+ task.delete(versions=versions)
+ return TaskInfo(**{"taskId": task.task_id, **task.dict()})
+
+
+@wait_for_connection
+def delete_old(
+ days_old: int = 100,
+ folder: str = "default",
+) -> int:
+ """Delete all tasks older than a given amount of days.
+
+ Parameters
+ ----------
+ folder : str
+ Only allowed to delete in one folder at a time.
+ days_old : int = 100
+ Minimum number of days since the task creation.
+
+ Returns
+ -------
+ int
+ Total number of tasks deleted.
+ """
+
+ folder = Folder.get(folder)
+ if not folder:
+ return 0
+ return folder.delete_old(days_old)
+
+
+@wait_for_connection
+def abort(task_id: TaskId) -> TaskInfo:
+ """Abort server-side data associated with task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+
+ Returns
+ -------
+ TaskInfo
+ Object containing information about status, size, credits of task.
+ """
+
+ task = SimulationTask.get(task_id)
+ if not task:
+ raise ValueError("Task not found.")
+ task.abort()
+ console = get_logging_console()
+ url = _get_url(task.task_id)
+ console.log(
+ f"Task is aborting. View task using web UI at [link={url}]'{url}'[/link] to check the result."
+ )
+ return TaskInfo(**{"taskId": task.task_id, **task.dict()})
+
+
+@wait_for_connection
+def get_tasks(
+ num_tasks: Optional[int] = None, order: Literal["new", "old"] = "new", folder: str = "default"
+) -> list[dict]:
+ """Get a list with the metadata of the last ``num_tasks`` tasks.
+
+ Parameters
+ ----------
+ num_tasks : int = None
+ The number of tasks to return, or, if ``None``, return all.
+ order : Literal["new", "old"] = "new"
+ Return the tasks in order of newest-first or oldest-first.
+ folder: str = "default"
+ Folder from which to get the tasks.
+
+ Returns
+ -------
+ List[Dict]
+ List of dictionaries storing the information for each of the tasks last ``num_tasks`` tasks.
+ """
+ folder = Folder.get(folder, create=True)
+ tasks = folder.list_tasks()
+ if not tasks:
+ return []
+ if order == "new":
+ tasks = sorted(tasks, key=lambda t: t.created_at, reverse=True)
+ elif order == "old":
+ tasks = sorted(tasks, key=lambda t: t.created_at)
+ if num_tasks is not None:
+ tasks = tasks[:num_tasks]
+ return [task.dict() for task in tasks]
+
+
+@wait_for_connection
+def estimate_cost(
+ task_id: str, verbose: bool = True, solver_version: Optional[str] = None
+) -> float:
+ """Compute the maximum FlexCredit charge for a given task.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+ solver_version : str = None
+ Target solver version.
+
+ Returns
+ -------
+ float
+ Estimated maximum cost for :class:`.Simulation` associated with given ``task_id``.
+
+ Note
+ ----
+ Cost is calculated assuming the simulation runs for
+ the full ``run_time``. If early shut-off is triggered, the cost is adjusted proportionately.
+ A minimum simulation cost may also apply, which depends on the task details.
+
+ Notes
+ -----
+
+ We can get the cost estimate of running the task before actually running it. This prevents us from
+ accidentally running large jobs that we set up by mistake. The estimated cost is the maximum cost
+ corresponding to running all the time steps.
+
+ Examples
+ --------
+
+ Basic example:
+
+ .. code-block:: python
+
+ # initializes job, puts task on server (but doesn't run it)
+ job = web.Job(simulation=sim, task_name="job", verbose=verbose)
+
+ # estimate the maximum cost
+ estimated_cost = web.estimate_cost(job.task_id)
+
+ print(f'The estimated maximum cost is {estimated_cost:.3f} Flex Credits.')
+
+ """
+ task = SimulationTask.get(task_id)
+ if not task:
+ raise ValueError("Task not found.")
+
+ task.estimate_cost(solver_version=solver_version)
+ task_info = get_info(task_id)
+ status = task_info.metadataStatus
+
+ # Wait for a termination status
+ while status not in ["processed", "success", "error", "failed"]:
+ time.sleep(REFRESH_TIME)
+ task_info = get_info(task_id)
+ status = task_info.metadataStatus
+
+ if status in ["processed", "success"]:
+ if verbose:
+ console = get_logging_console()
+ console.log(
+ f"Maximum FlexCredit cost: {task_info.estFlexUnit:1.3f}. Minimum cost depends on "
+ "task execution details. Use 'web.real_cost(task_id)' to get the billed FlexCredit "
+ "cost after a simulation run."
+ )
+ fc_mode = task_info.estFlexCreditMode
+ fc_post = task_info.estFlexCreditPostProcess
+ if fc_mode:
+ console.log(f" {fc_mode:1.3f} FlexCredit of the total cost from mode solves.")
+ if fc_post:
+ console.log(f" {fc_post:1.3f} FlexCredit of the total cost from post-processing.")
+ return task_info.estFlexUnit
+
+ # Something went wrong
+ raise WebError("Could not get estimated cost!")
+
+
+@wait_for_connection
+def real_cost(task_id: str, verbose=True) -> float:
+ """Get the billed cost for given task after it has been run.
+
+ Parameters
+ ----------
+ task_id : str
+ Unique identifier of task on server. Returned by :meth:`upload`.
+ verbose : bool = True
+ Whether to log the cost and helpful messages.
+
+ Returns
+ -------
+ float
+ The flex credit cost that was billed for the given ``task_id``.
+
+ Note
+ ----
+ The billed cost may not be immediately available when the task status is set to ``success``,
+ but should be available shortly after.
+
+ Examples
+ --------
+
+ To obtain the cost of a simulation, you can use the function ``tidy3d.web.real_cost(task_id)``. In the example
+ below, a job is created, and its cost is estimated. After running the simulation, the real cost can be obtained.
+
+ .. code-block:: python
+
+ import time
+
+ # initializes job, puts task on server (but doesn't run it)
+ job = web.Job(simulation=sim, task_name="job", verbose=verbose)
+
+ # estimate the maximum cost
+ estimated_cost = web.estimate_cost(job.task_id)
+
+ print(f'The estimated maximum cost is {estimated_cost:.3f} Flex Credits.')
+
+ # Runs the simulation.
+ sim_data = job.run(path="data/sim_data.hdf5")
+
+ time.sleep(5)
+
+ # Get the billed FlexCredit cost after a simulation run.
+ cost = web.real_cost(job.task_id)
+ """
+ task_info = get_info(task_id)
+ flex_unit = task_info.realFlexUnit
+ ori_flex_unit = task_info.oriRealFlexUnit
+ if not flex_unit:
+ log.warning(
+ f"Billed FlexCredit for task '{task_id}' is not available. If the task has been "
+ "successfully run, it should be available shortly."
+ )
+ else:
+ if verbose:
+ console = get_logging_console()
+ console.log(f"Billed flex credit cost: {flex_unit:1.3f}.")
+ if flex_unit != ori_flex_unit and task_info.taskType == "FDTD":
+ console.log(
+ "Note: the task cost pro-rated due to early shutoff was below the minimum "
+ "threshold, due to fast shutoff. Decreasing the simulation 'run_time' should "
+ "decrease the estimated, and correspondingly the billed cost of such tasks."
+ )
+ return flex_unit
+
+
+@wait_for_connection
+def account(verbose=True) -> Account:
+ """Get account information including FlexCredit balance and usage limits.
+
+ Parameters
+ ----------
+ verbose : bool = True
+ If ``True``, prints account information including credit balance, expiration,
+ and free simulation counts.
+
+ Returns
+ -------
+ Account
+ Object containing account information such as credit balance, expiration dates,
+ and daily free simulation counts.
+
+ Examples
+ --------
+ Get account information:
+
+ .. code-block:: python
+
+ account_info = web.account()
+ # Displays:
+ # Current FlexCredit balance: 10.00 and expiration date: 2024-12-31 23:59:59.
+ # Remaining daily free simulations: 3.
+ """
+ account_info = Account.get()
+ if verbose and account_info:
+ console = get_logging_console()
+ credit = account_info.credit
+ credit_expiration = account_info.credit_expiration
+ cycle_type = account_info.allowance_cycle_type
+ cycle_amount = account_info.allowance_current_cycle_amount
+ cycle_end_date = account_info.allowance_current_cycle_end_date
+ free_simulation_counts = account_info.daily_free_simulation_counts
+
+ message = ""
+ if credit is not None:
+ message += f"Current FlexCredit balance: {credit:.2f}"
+ if credit_expiration is not None:
+ message += (
+ f" and expiration date: {credit_expiration.strftime('%Y-%m-%d %H:%M:%S')}. "
+ )
+ else:
+ message += ". "
+ if cycle_type is not None and cycle_amount is not None and cycle_end_date is not None:
+ cycle_end = cycle_end_date.strftime("%Y-%m-%d %H:%M:%S")
+ message += f"{cycle_type} FlexCredit balance: {cycle_amount:.2f} and expiration date: {cycle_end}. "
+ if free_simulation_counts is not None:
+ message += f"Remaining daily free simulations: {free_simulation_counts}."
+
+ console.log(message)
+
+ return account_info
+
+
+@wait_for_connection
+def test() -> None:
+ """Confirm whether Tidy3D authentication is configured.
+
+ Raises
+ ------
+ WebError
+ If Tidy3D authentication is not configured correctly.
+
+ Notes
+ -----
+ This method tests the authentication configuration by attempting to retrieve
+ the task list. If authentication is not properly set up, it will raise an
+ exception with instructions on how to configure authentication.
+
+ Examples
+ --------
+ Test authentication:
+
+ .. code-block:: python
+
+ web.test()
+ # If successful, displays:
+ # Authentication configured successfully!
+ """
+ try:
+ # note, this is a little slow, but the only call that doesn't require providing a task id.
+ get_tasks(num_tasks=0)
+ console = get_logging_console()
+ console.log("Authentication configured successfully!")
+ except (WebError, HTTPError) as e:
+ url = "https://docs.flexcompute.com/projects/tidy3d/en/latest/index.html"
+
+ raise WebError(
+ "Tidy3D not configured correctly. Please refer to our documentation for installation "
+ "instructions at "
+ f"[blue underline][link={url}]'{url}'[/link]."
+ ) from e
diff --git a/tidy3d/plugins/smatrix/web/cli/__init__.py b/tidy3d/plugins/smatrix/web/cli/__init__.py
new file mode 100644
index 0000000000..bd3bf238d0
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/__init__.py
@@ -0,0 +1,9 @@
+"""
+tidy3d command line tool.
+"""
+
+from __future__ import annotations
+
+from .app import tidy3d_cli
+
+__all__ = ["tidy3d_cli"]
diff --git a/tidy3d/plugins/smatrix/web/cli/app.py b/tidy3d/plugins/smatrix/web/cli/app.py
new file mode 100644
index 0000000000..76a9572f09
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/app.py
@@ -0,0 +1,138 @@
+"""
+Commandline interface for tidy3d.
+"""
+
+from __future__ import annotations
+
+import json
+import os.path
+import ssl
+
+import click
+import requests
+import toml
+
+from tidy3d.web.cli.constants import CONFIG_FILE, CREDENTIAL_FILE, TIDY3D_DIR
+from tidy3d.web.cli.migrate import migrate
+from tidy3d.web.core.constants import HEADER_APIKEY, KEY_APIKEY
+from tidy3d.web.core.environment import Env
+
+from .develop.index import develop
+
+# Prevent race condition on threads
+os.makedirs(TIDY3D_DIR, exist_ok=True)
+
+
+def get_description():
+ """Get the description for the config command.
+ Returns
+ -------
+ str
+ The description for the config command.
+ """
+
+ if os.path.exists(CONFIG_FILE):
+ with open(CONFIG_FILE, encoding="utf-8") as f:
+ content = f.read()
+ config = toml.loads(content)
+ return config.get(KEY_APIKEY, "")
+ return ""
+
+
+@click.group()
+def tidy3d_cli():
+ """
+ Tidy3d command line tool.
+ """
+
+
+@click.command()
+@click.option("--apikey", prompt=False)
+def configure(apikey):
+ """Click command to configure the api key.
+
+ Parameters
+ ----------
+ apikey : str
+ User input api key.
+ """
+ configure_fn(apikey)
+
+
+def configure_fn(apikey: str) -> None:
+ """Python function that tries to set configuration based on a provided API key.
+
+ Parameters
+ ----------
+ apikey : str
+ User input api key.
+ """
+
+ def auth(req):
+ """Enrich auth information to request.
+ Parameters
+ ----------
+ req : requests.Request
+ the request needs to add headers for auth.
+ Returns
+ -------
+ requests.Request
+ Enriched request.
+ """
+ req.headers[HEADER_APIKEY] = apikey
+ return req
+
+ if os.path.exists(CREDENTIAL_FILE):
+ with open(CREDENTIAL_FILE, encoding="utf-8") as fp:
+ auth_json = json.load(fp)
+ email = auth_json["email"]
+ password = auth_json["password"]
+ if email and password:
+ if migrate():
+ click.echo("Migrate successfully. auth.json is renamed to auth.json.bak.")
+ return
+
+ if not apikey:
+ current_apikey = get_description()
+ message = f"Current API key: [{current_apikey}]\n" if current_apikey else ""
+ apikey = click.prompt(f"{message}Please enter your api key", type=str)
+
+ try:
+ resp = requests.get(
+ f"{Env.current.web_api_endpoint}/apikey", auth=auth, verify=Env.current.ssl_verify
+ )
+ except (requests.exceptions.SSLError, ssl.SSLError):
+ resp = requests.get(f"{Env.current.web_api_endpoint}/apikey", auth=auth, verify=False)
+
+ if resp.status_code == 200:
+ click.echo("Configured successfully.")
+ with open(CONFIG_FILE, "w+", encoding="utf-8") as config_file:
+ toml_config = toml.loads(config_file.read())
+ toml_config.update({KEY_APIKEY: apikey})
+ config_file.write(toml.dumps(toml_config))
+ else:
+ click.echo("API key is invalid.")
+
+
+@click.command()
+def migration():
+ """Click command to migrate the credential to api key."""
+ migrate()
+
+
+@click.command()
+@click.argument("lsf_file")
+@click.argument("new_file")
+def convert(lsf_file, new_file):
+ """Click command to convert .lsf project into Tidy3D .py file"""
+ raise ValueError(
+ "The converter feature is deprecated. "
+ "To use this feature, please use the external tool at "
+ "'https://github.com/hirako22/Lumerical-to-Tidy3D-Converter'."
+ )
+
+
+tidy3d_cli.add_command(configure)
+tidy3d_cli.add_command(migration)
+tidy3d_cli.add_command(convert)
+tidy3d_cli.add_command(develop)
diff --git a/tidy3d/plugins/smatrix/web/cli/constants.py b/tidy3d/plugins/smatrix/web/cli/constants.py
new file mode 100644
index 0000000000..60961f28fe
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/constants.py
@@ -0,0 +1,16 @@
+"""Constants for the CLI."""
+
+from __future__ import annotations
+
+import os
+from os.path import expanduser
+
+TIDY3D_BASE_DIR = os.getenv("TIDY3D_BASE_DIR", f"{expanduser('~')}")
+
+if os.access(TIDY3D_BASE_DIR, os.W_OK):
+ TIDY3D_DIR = f"{TIDY3D_BASE_DIR}/.tidy3d"
+else:
+ TIDY3D_DIR = "/tmp/.tidy3d"
+
+CONFIG_FILE = TIDY3D_DIR + "/config"
+CREDENTIAL_FILE = TIDY3D_DIR + "/auth.json"
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/__init__.py b/tidy3d/plugins/smatrix/web/cli/develop/__init__.py
new file mode 100644
index 0000000000..98ef3b1300
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/__init__.py
@@ -0,0 +1,66 @@
+# Import from documentation.py
+from __future__ import annotations
+
+from .documentation import (
+ build_documentation,
+ # build_documentation_pdf,
+ build_documentation_from_remote_notebooks,
+ commit,
+ # convert_all_markdown_to_rst_command,
+ replace_in_files_command,
+)
+
+# Import from index.py
+from .index import develop
+
+# Import from install.py
+from .install import (
+ activate_correct_poetry_python,
+ configure_submodules,
+ get_install_directory_command,
+ install_development_environment,
+ install_in_poetry,
+ uninstall_development_environment,
+ update_submodules_remote,
+ verify_development_environment,
+ verify_pandoc_is_installed_and_version_less_than_3,
+ verify_pipx_is_installed,
+ verify_poetry_is_installed,
+ verify_sphinx_is_installed,
+)
+from .packaging import benchmark_timing_operations, benchmark_timing_operations_command
+
+# Import from tests.py
+from .tests import test_in_environment_command, test_options
+
+# Import from utils.py
+from .utils import echo_and_check_subprocess, echo_and_run_subprocess, get_install_directory
+
+__all__ = [
+ "activate_correct_poetry_python",
+ "benchmark_timing_operations",
+ "benchmark_timing_operations_command",
+ "build_documentation",
+ # "build_documentation_pdf",
+ "build_documentation_from_remote_notebooks",
+ "commit",
+ "configure_submodules",
+ "develop",
+ "echo_and_check_subprocess",
+ "echo_and_run_subprocess",
+ "get_install_directory",
+ "get_install_directory_command",
+ "install_development_environment",
+ "install_in_poetry",
+ # "convert_all_markdown_to_rst_command",
+ "replace_in_files_command",
+ "test_in_environment_command",
+ "test_options",
+ "uninstall_development_environment",
+ "update_submodules_remote",
+ "verify_development_environment",
+ "verify_pandoc_is_installed_and_version_less_than_3",
+ "verify_pipx_is_installed",
+ "verify_poetry_is_installed",
+ "verify_sphinx_is_installed",
+]
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/documentation.py b/tidy3d/plugins/smatrix/web/cli/develop/documentation.py
new file mode 100644
index 0000000000..9da6926577
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/documentation.py
@@ -0,0 +1,340 @@
+"""
+These are common operations CLI tools for the documentation build process.
+These functions are used to build the documentation. They are called from the CLI using the following command:
+
+ poetry run tidy3d develop build-docs
+
+The functions are also used to update the notebooks submodule and build the documentation using the following command:
+
+ poetry run tidy3d develop build-docs-remote-notebooks
+
+The functions are also used to convert all Markdown files to RST format using the following command:
+
+ poetry run tidy3d develop convert-all-markdown-to-rst
+"""
+
+from __future__ import annotations
+
+import json
+import os
+from typing import Optional
+
+import click
+
+from .index import develop
+from .utils import echo_and_check_subprocess, get_install_directory
+
+__all__ = [
+ "build_documentation",
+ # "build_documentation_pdf",
+ "build_documentation_from_remote_notebooks",
+ "commit",
+ # "convert_all_markdown_to_rst_command",
+ "replace_in_files_command",
+]
+
+
+def replace_in_files(
+ directory: str,
+ json_file_path: str,
+ selected_version: str,
+ dry_run=False,
+):
+ """
+ Recursively finds and replaces strings in files within a directory based on a given dictionary loaded from a JSON
+ file. The JSON file also includes a version selector. The function will print the file line and prompt for
+ confirmation before replacing each string.
+
+ Example JSON file:
+
+ {
+ "0.18.0": {
+ "tidy3d.someuniquestringa": "tidy3d.someuniquestring2",
+ "tidy3d.someuniquestringb": "tidy3d.someuniquestring2",
+ "tidy3d.someuniquestringc": "tidy3d.someuniquestring2"
+ }
+ }
+
+ Args:
+ - directory (str): The directory path to search for files.
+ - json_file_path (str): The path to the JSON file containing replacement instructions.
+ - selected_version (str): The version to select from the JSON file.
+ - dry_run (bool): If True, the function will not modify any files, but will print the changes that would be made.
+ """
+ allowed_extensions = (".py", ".rst", ".md", ".txt")
+ exceptions = []
+
+ # Load data from the JSON file
+ with open(json_file_path, encoding="utf-8") as json_file:
+ data = json.load(json_file)
+ replace_dict = data.get(str(selected_version), {})
+
+ for root, dirs, files in os.walk(directory):
+ # Exclude directories that start with a period ('.')
+ dirs[:] = [d for d in dirs if not d.startswith(".")]
+ for file in files:
+ file_path = os.path.join(root, file)
+ file_extension = os.path.splitext(file)[1].lower() # Get the file extension
+ if not file.startswith("."):
+ # Check if the file has an allowed extension
+ if file_extension in allowed_extensions:
+ # Read file content and process each line
+ with open(file_path, encoding="utf-8") as f:
+ try:
+ lines = f.readlines()
+
+ for i, line in enumerate(lines):
+ for find_str, replace_str in replace_dict.items():
+ if find_str in line:
+ print(f"File: {file_path} --- Line {i + 1}")
+ print(f"Original: {line.strip()}")
+ confirmation = input(
+ f"Replace '{find_str}' with '{replace_str}' in this line? (y/n): "
+ )
+ if confirmation.lower() == "y":
+ lines[i] = line.replace(find_str, replace_str)
+ if not dry_run:
+ print(f"Modified: {lines[i].strip()}")
+ else:
+ print(
+ f"Not modified because of dry run: {line.strip()}"
+ )
+
+ # Write the modified content back to the file if not in dry run mode
+ if not dry_run:
+ with open(file_path, "w", encoding="utf-8") as f:
+ f.writelines(lines)
+
+ except Exception as e: # Catch any exception
+ exceptions.append(e)
+
+ # At the end of the file/script:
+ if exceptions:
+ for ex in exceptions:
+ # Handle or print the exceptions as needed
+ print(f"An error occurred: {ex}")
+
+
+@develop.command(
+ name="commit",
+ help="Adds and commits the state of the repository and its notebook & faq submodule.",
+)
+@click.argument("message", type=str) # Specify the type as str for the 'message' argument
+@click.option(
+ "--submodule-path",
+ default=str(get_install_directory() / "docs" / "notebooks"),
+ help="Path to the submodule.",
+ type=str, # Specify the type as str for the 'submodule-path' option
+)
+def commit(message: str, submodule_path: str):
+ """
+ Add and commit changes in both the Git repository and its submodule.
+
+ This command performs git commit operations on both the main repository and the specified submodule
+ using the provided commit message.
+
+ Parameters
+ ----------
+ message : str
+ The commit message to use for both commits.
+ submodule_path : str
+ The relative path to the submodule.
+ """
+
+ def commit_repository(repository_path, commit_message):
+ """
+ Commit changes in the specified Git repository.
+
+ Args:
+ repo_path: Path to the repository.
+ message: Commit message.
+ """
+
+ echo_and_check_subprocess(["git", "-C", repository_path, "add", "."])
+ echo_and_check_subprocess(
+ ["git", "-C", repository_path, "commit", "--no-verify", "-am", commit_message]
+ )
+
+ # TODO fix errors when committing between the two repos.
+ # Commit to the submodule
+ commit_repository(submodule_path, message)
+ # Commit to the main repository
+ commit_repository("..", message)
+ return 0
+
+
+@develop.command(name="build-docs", help="Builds the sphinx documentation.")
+def build_documentation(args=None):
+ """
+ Build the Sphinx documentation.
+
+ This command triggers the Sphinx documentation build process in the current poetry environment.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the documentation build process.
+ """
+ # Runs the documentation build from the poetry environment
+ echo_and_check_subprocess(
+ ["poetry", "run", "python", "-m", "sphinx", "-j", "auto", "docs/", "_docs/"]
+ )
+ return 0
+
+
+# TODO: Fix the PDF build process
+# @develop.command(name="build-docs-pdf", help="Builds the sphinx documentation pdf.")
+# def build_documentation_pdf(args=None):
+# """
+# Build the Sphinx documentation in PDF format.
+#
+# This command initiates the process to build the Sphinx documentation and generates a PDF output.
+#
+# Parameters
+# ----------
+# args : optional
+# Additional arguments for the PDF documentation build process.
+# """
+# # Runs the documentation build from the poetry environment
+# echo_and_run_subprocess(
+# ["poetry", "run", "python", "-m", "sphinx", "-M", "latexpdf", "docs/", "_pdf/"]
+# )
+# return 0
+
+
+@develop.command(
+ name="build-docs-remote-notebooks", help="Updates notebooks submodule and builds documentation."
+)
+@click.option(
+ "-nb",
+ "--notebook-branch",
+ default="./docs/notebooks",
+ help="The remote branch from tidy3d-notebooks.",
+)
+def build_documentation_from_remote_notebooks(args=None):
+ """
+ Update the notebooks submodule and build documentation.
+
+ This command updates the notebook submodule from the remote repository and then builds the Sphinx documentation.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the process of updating notebooks and building documentation.
+ """
+ # Runs the documentation build from the poetry environment
+ echo_and_check_subprocess(["git", "submodule", "update", "--remote"])
+
+ print("Notebook submodule updated from remote.")
+ echo_and_check_subprocess(["poetry", "run", "python", "-m", "sphinx", "docs/", "_docs/"])
+ return 0
+
+
+# TODO decide if this is useful in any form.
+# @develop.command(
+# name="convert-all-markdown-to-rst",
+# help="Recursively find all markdown files and convert them to rst files that can be included in the sphinx "
+# "documentation",
+# )
+# @click.option(
+# "--directory",
+# "-d",
+# type=click.Path(exists=True, file_okay=False, readable=True),
+# default=".",
+# help="Directory to process (default is current directory)",
+# )
+# def convert_all_markdown_to_rst_command(directory: str):
+# """
+# This script converts all Markdown files in a given DIRECTORY to reStructuredText format.
+# """
+# if directory is None:
+# directory = get_install_directory()
+#
+# for root, _, files in os.walk(directory):
+# for file in files:
+# if file.endswith(".md"):
+# md_file = os.path.join(root, file)
+# rst_file = os.path.splitext(md_file)[0] + ".rst"
+#
+# # Confirmation for each file
+# if not click.confirm(f"Convert {md_file} to RST format?", default=True):
+# click.echo(f"Skipped {md_file}")
+# continue
+#
+# try:
+# # Convert using Pandoc
+# echo_and_check_subprocess(["pandoc", "-s", md_file, "-o", rst_file])
+# click.echo(f"Converted {md_file} to {rst_file}")
+# except subprocess.CalledProcessError as e:
+# click.echo(f"Error converting {md_file}: {e}", err=True)
+
+
+@click.option(
+ "--directory",
+ "-d",
+ type=click.Path(exists=True, file_okay=False, readable=True),
+ default=".",
+ help="Directory to process (default is current directory)",
+)
+@click.option(
+ "--json-dictionary",
+ "-j",
+ type=click.Path(exists=True, file_okay=True, readable=True),
+ help="JSON that contains the docstring version update files.",
+)
+@click.option(
+ "--selected-version",
+ "-v",
+ type=str,
+ help="Version to select from the JSON file",
+)
+@click.option(
+ "--dry-run",
+ type=bool,
+ default=True,
+ help="Dry run the replace in files command.",
+)
+@develop.command(
+ name="replace-in-files",
+ help="Recursively find and replace strings in files based on a JSON configuration.",
+)
+def replace_in_files_command(
+ directory: str, json_dictionary: Optional[str], selected_version: Optional[str], dry_run: bool
+):
+ """
+ Recursively finds and replaces strings in files within a directory based on a given dictionary loaded from a JSON
+ file. The JSON file also includes a version selector. The function will print the file line and prompt for
+ confirmation before replacing each string.
+
+ Example JSON file:
+
+ {
+ "0.18.0": {
+ "tidy3d.someuniquestringa": "tidy3d.someuniquestring2",
+ "tidy3d.someuniquestringb": "tidy3d.someuniquestring2",
+ "tidy3d.someuniquestringc": "tidy3d.someuniquestring2"
+ }
+ }
+
+ Usage:
+
+ poetry run tidy3d develop replace-in-files -d ./ -j ./docs/versions/test_replace_in_files.json -v 0.18.0 --dry-run True
+ poetry run tidy3d develop replace-in-files --directory ./ --json-dictionary ./docs/versions/test_replace_in_files.json --selected-version 0.18.0 --dry-run True
+
+ Args:
+ - directory (str): The directory path to search for files.
+ - json_file_path (str): The path to the JSON file containing replacement instructions.
+ - selected_version (str): The version to select from the JSON file.
+ """
+ # Raise helpful errors on missing arguments using the Click API
+ if json_dictionary is None:
+ raise click.BadParameter("JSON dictionary -j is required.")
+
+ if selected_version is None:
+ raise click.BadParameter("Selected version -v is required.")
+
+ if directory is None:
+ directory = get_install_directory()
+
+ replace_in_files(directory, json_dictionary, selected_version, dry_run)
+ return 0
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/index.py b/tidy3d/plugins/smatrix/web/cli/develop/index.py
new file mode 100644
index 0000000000..bbb63fef78
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/index.py
@@ -0,0 +1,19 @@
+"""Console script subcommand for tidy3d."""
+
+from __future__ import annotations
+
+import click
+
+__all__ = [
+ "develop",
+]
+
+
+@click.group(name="develop")
+def develop():
+ """
+ Development related command group in the CLI.
+
+ This command group includes several subcommands for various development tasks such as
+ verifying and setting up the development environment, building documentation, testing, and more.
+ """
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/install.py b/tidy3d/plugins/smatrix/web/cli/develop/install.py
new file mode 100644
index 0000000000..302cc4ac55
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/install.py
@@ -0,0 +1,406 @@
+"""
+This module contains the implementation of the `tidy3d develop` installation commands. These commands are used to
+install and configure the development environment for tidy3d. The commands are implemented using the Click library and
+are available as CLI commands when tidy3d is installed.
+"""
+
+from __future__ import annotations
+
+import platform
+import re
+import subprocess
+
+import click
+
+from .index import develop
+from .utils import echo_and_check_subprocess, echo_and_run_subprocess, get_install_directory
+
+__all__ = [
+ "activate_correct_poetry_python",
+ "configure_submodules",
+ "get_install_directory_command",
+ "install_development_environment",
+ "install_in_poetry",
+ "uninstall_development_environment",
+ "update_submodules_remote",
+ "verify_development_environment",
+ "verify_pandoc_is_installed_and_version_less_than_3",
+ "verify_pipx_is_installed",
+ "verify_poetry_is_installed",
+ "verify_sphinx_is_installed",
+]
+
+
+def activate_correct_poetry_python():
+ """
+ Activate the correct Python environment for Poetry based on the operating system.
+ """
+ if platform.system() == "Windows" or platform.system() == "Darwin":
+ echo_and_run_subprocess(["poetry", "env", "use", "python"])
+ elif platform.system() == "Linux":
+ try:
+ echo_and_run_subprocess(["poetry", "env", "use", "python"])
+ except subprocess.CalledProcessError:
+ echo_and_run_subprocess(["poetry", "env", "use", "python"])
+ except: # NOQA: E722
+ print("Do you have a python available in your terminal?")
+
+
+def configure_submodules(args=None):
+ """
+ Initialize and update the notebook submodule.
+
+ This command configures the notebook submodule by initializing it and updating it from the remote repository.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the configuration process.
+ """
+ echo_and_run_subprocess(["git", "submodule", "init"])
+ echo_and_run_subprocess(["git", "submodule", "update", "--remote"])
+ print("Submodules updated from remote.")
+ return 0
+
+
+def verify_pandoc_is_installed_and_version_less_than_3():
+ """
+ Check if Pandoc is installed and its version is less than 3.
+
+ Returns
+ -------
+ bool
+ True if Pandoc is installed and its version is less than 3, False otherwise.
+ """
+ try:
+ # Running 'pandoc --version' command
+ result = echo_and_run_subprocess(
+ ["pandoc", "--version"], capture_output=True, text=True, check=True
+ )
+
+ # Extracting the version number from the output
+ version_match = re.search(r"pandoc\s+(\d+\.\d+.\d+)", result.stdout)
+ if version_match:
+ version = version_match.group(1)
+ major_version = int(version.split(".")[0])
+
+ if major_version < 3:
+ print(f"Pandoc is installed with version {version}, which is less than 3.")
+ return True
+ print(f"Pandoc version {version} is installed, but it is not less than 3.")
+ return False
+ print("Pandoc version number could not be determined.")
+ return False
+
+ except subprocess.CalledProcessError:
+ # This exception is raised if the command returned a non-zero exit status
+ print("Pandoc is not installed or not found in the system PATH.")
+ return False
+
+
+def verify_pipx_is_installed():
+ """
+ Verify if pipx is installed on the system.
+
+ Returns
+ -------
+ bool
+ True if pipx is installed, False otherwise.
+ """
+ try:
+ # Running 'pipx --version' command
+ result = echo_and_run_subprocess(
+ ["pipx", "--version"], capture_output=True, text=True, check=True
+ )
+
+ # If the command was successful, it means pipx is installed
+ if result.returncode == 0:
+ print("pipx is installed: " + result.stdout)
+ return True
+ except subprocess.CalledProcessError:
+ # This exception is raised if the command returned a non-zero exit status
+ print("pipx is not installed or not found in the system PATH.")
+ return False
+
+
+def verify_poetry_is_installed():
+ """
+ Check if Poetry is installed on the system.
+
+ Returns
+ -------
+ bool
+ True if Poetry is installed, False otherwise.
+
+ Raises
+ ------
+ OSError
+ If Poetry is not installed or not found in the system PATH.
+ """
+ try:
+ # Running 'poetry --version' command
+ result = echo_and_run_subprocess(
+ ["poetry", "--version"], capture_output=True, text=True, check=True
+ )
+ # If the command was successful, we'll get the version info
+ if result.returncode == 0:
+ print("Poetry is installed: " + result.stdout)
+ return True
+ except subprocess.CalledProcessError as exc:
+ # This exception is raised if the command returned a non-zero exit status
+ raise OSError("Poetry is not installed or not found in the system PATH.") from exc
+
+
+def verify_sphinx_is_installed():
+ """
+ Verify if Sphinx is installed in the poetry environment.
+
+ Raises
+ ------
+ OSError
+ If Sphinx is not installed or not found in the poetry environment.
+ """
+ try:
+ # Running 'poetry --version' command
+ activate_correct_poetry_python()
+ result = echo_and_run_subprocess(
+ ["poetry", "run", "python -m", "sphinx --version"],
+ )
+ # If the command was successful, we'll get the version info
+ print("sphinx is installed: " + result.stdout)
+ except subprocess.CalledProcessError as exc:
+ # This exception is raised if the command returned a non-zero exit status
+ raise OSError("sphinx is not installed or not found in the poetry environment.") from exc
+
+
+@develop.command(name="get-install-directory", help="Gets the TIDY3D base directory.")
+def get_install_directory_command():
+ """
+ Get the tidy3d installation directory.
+
+ This command prints the absolute path of the installation directory of the tidy3d module.
+ """
+ print(get_install_directory())
+ return 0
+
+
+@develop.command(
+ name="install-dev-environment",
+ help="Installs and configures the full required development environment.",
+)
+def install_development_environment(args=None):
+ """Install and configure the full required development environment.
+
+ This command automates the installation of development tools like pipx, poetry, and pandoc, and sets up
+ the development environment according to 'The Detailed Lane' instructions. It is dependent on the
+ operating system and may require user interaction for certain steps.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the installation process.
+ """
+ # Verify and install pipx if required
+ try:
+ verify_pipx_is_installed()
+ except Exception as exc:
+ if platform.system() == "Windows":
+ echo_and_check_subprocess(["scoop", "install", "pipx"])
+ echo_and_check_subprocess(["pipx", "ensurepath"])
+ elif platform.system() == "Darwin":
+ echo_and_check_subprocess(["brew", "install", "pipx"])
+ echo_and_check_subprocess(["pipx", "ensurepath"])
+ elif platform.system() == "Linux":
+ echo_and_check_subprocess(["python3", "-m", "pip", "install", "--user", "pipx"])
+ echo_and_check_subprocess(["python3", "-m", "pipx", "ensurepath"])
+ else:
+ raise OSError(
+ "Unsupported operating system installation flow. Verify the subprocess commands in "
+ "tidy3d develop are compatible with your operating system."
+ ) from exc
+
+ # Verify and install poetry if required
+ try:
+ verify_poetry_is_installed()
+ except Exception as exc:
+ if platform.system() == "Windows" or platform.system() == "Darwin":
+ echo_and_check_subprocess(["pipx", "install", "poetry"])
+ elif platform.system() == "Linux":
+ echo_and_check_subprocess(["python3", "-m", "pipx", "install", "poetry"])
+ else:
+ raise OSError(
+ "Unsupported operating system installation flow. Verify the subprocess commands in "
+ "tidy3d develop are compatible with your operating system."
+ ) from exc
+
+ # Verify pandoc is installed
+ try:
+ verify_pandoc_is_installed_and_version_less_than_3()
+ except Exception as exc:
+ raise OSError(
+ "Please install pandoc < 3 depending on your platform: https://pandoc.org/installing.html . Then run this "
+ "command again. You can also follow our detailed instructions under the development guide."
+ ) from exc
+
+ # Makes sure that poetry uses the python environment active on the terminal.
+
+ activate_correct_poetry_python()
+ # Makes sure the package has installed all the development dependencies.
+ echo_and_check_subprocess(["poetry", "install", "-E", "dev"])
+ echo_and_check_subprocess(["poetry", "run", "pre-commit", "install"])
+
+ # Configure notebook submodule
+ try:
+ configure_submodules()
+ except: # NOQA: E722
+ print("Notebook submodule not configured.")
+
+ return 0
+
+
+@click.option(
+ "--env",
+ default="dev",
+ help="Poetry environment to install. Defaults to 'dev'.",
+ type=str,
+)
+@develop.command(
+ name="install-in-poetry", help="Just installs the tidy3d development package in poetry."
+)
+def install_in_poetry(env: str = "dev"):
+ """
+ Install the tidy3d development package in the poetry environment with the specified extra option, by default 'dev'.
+
+ This command ensures that the tidy3d package along with its development dependencies is installed in the current
+ poetry environment.
+
+ Parameters
+ ----------
+ env : str
+ The extra option to pass to poetry for installation. Defaults to 'dev'.
+ """
+ # Runs the documentation build from the poetry environment
+ activate_correct_poetry_python()
+ echo_and_run_subprocess(["poetry", "install", "-E", env])
+ return 0
+
+
+@develop.command(
+ name="uninstall-dev-environment", help="Uninstalls the tools installed by this CLI helper."
+)
+def uninstall_development_environment(args=None):
+ """
+ Uninstall the development environment and the tools installed by this CLI.
+
+ This command provides a clean-up mechanism to remove development tools like poetry, pipx, and pandoc
+ that were installed using this CLI. User confirmation is required before uninstallation.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the uninstallation process.
+ """
+ answer = input(
+ "This function will uninstall poetry, pipx and request you to uninstall pandoc. Are you sure you want to continue?"
+ )
+ if answer.lower() in ["y", "yes"]:
+ pass
+ elif answer.lower() in ["n", "no"]:
+ exit("Nothing has been uninstalled.")
+ else:
+ exit("Nothing has been uninstalled.")
+
+ # Verify and uninstall poetry if required
+ if verify_poetry_is_installed():
+ if platform.system() == "Windows":
+ echo_and_run_subprocess(["pipx", "uninstall", "poetry"])
+ elif platform.system() == "Darwin":
+ echo_and_run_subprocess(["brew", "uninstall", "poetry"])
+ echo_and_run_subprocess(["pipx", "uninstall", "poetry"])
+ elif platform.system() == "Linux":
+ echo_and_run_subprocess(["python3", "-m", "pipx", "uninstall", "poetry"])
+ else:
+ raise OSError(
+ "Unsupported operating system installation flow. Verify the subprocess commands in "
+ "tidy3d develop are compatible with your operating system."
+ )
+ else:
+ print("poetry is not found on the PATH. It is already uninstalled from PATH.")
+
+ # Verify and install pipx if required
+ if verify_pipx_is_installed():
+ if platform.system() == "Windows":
+ echo_and_run_subprocess(["python", "-m", "pip", "uninstall", "-y", "pipx"])
+ # TODO what's the deal here?
+ elif platform.system() == "Darwin":
+ echo_and_run_subprocess(["brew", "uninstall", "pipx"])
+ echo_and_run_subprocess(["python", "-m", "pip", "uninstall", "-y", "pipx"])
+ echo_and_run_subprocess(["rm", "-rf", "~/.local/pipx"])
+ elif platform.system() == "Linux":
+ echo_and_run_subprocess(["python3", "-m", "pip", "uninstall", "-y", "pipx"])
+ echo_and_run_subprocess(["rm", "-rf", "~/.local/pipx"])
+ else:
+ raise OSError(
+ "Unsupported operating system installation flow. Verify the subprocess commands in "
+ "tidy3d develop are compatible with your operating system."
+ )
+ else:
+ print("pipx is not found on the PATH. It is already uninstalled from PATH.")
+
+ # Verify pandoc is installed
+ if verify_pandoc_is_installed_and_version_less_than_3():
+ raise OSError(
+ "Please uninstall pandoc < 3 depending on your platform: https://pandoc.org/installing.html . Then run this "
+ "command again. You can also follow our detailed instructions under the development guide."
+ )
+ print("pandoc is not found on the PATH. It is already uninstalled from PATH.")
+
+ return 0
+
+
+@develop.command(name="update-submodules", help="Updates notebooks and FAQ submodule from remote")
+def update_submodules_remote(args=None):
+ """
+ Update the notebooks submodule from the remote repository.
+
+ This command updates the notebook submodule, ensuring it is synchronized with the latest version from the remote repository.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the update process.
+ """
+ # Runs the documentation build from the poetry environment
+ echo_and_check_subprocess(["git", "submodule", "update", "--remote"])
+ return 0
+
+
+@develop.command(name="verify-dev-environment", help="Verifies the development environment.")
+def verify_development_environment(args=None):
+ """
+ Verify that the current development environment conforms to the specified requirements.
+
+ This command checks various development dependencies like pipx, poetry, and pandoc, and ensures
+ they are properly installed and configured. It also performs a dry run of poetry installation to check
+ package configurations.
+
+ Parameters
+ ----------
+ args : optional
+ Additional arguments for the verification process.
+ """
+ # Does all the docs verifications
+ # Checks all the other development dependencies are properly installed
+ # Verify pipx is installed
+ verify_pipx_is_installed()
+ # Verify poetry is installed
+ verify_poetry_is_installed()
+ # Verify pandoc is installed
+ verify_pandoc_is_installed_and_version_less_than_3()
+ # Dry run the poetry install to understand the configuration
+ activate_correct_poetry_python()
+ echo_and_check_subprocess(["poetry", "install", "-E", "dev", "--dry-run"])
+ print(
+ "'poetry install -E dev' dry run on the 'poetry.lock' complete.\nManually verify packages are properly installed."
+ )
+ return 0
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/packaging.py b/tidy3d/plugins/smatrix/web/cli/develop/packaging.py
new file mode 100644
index 0000000000..0b92790dfb
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/packaging.py
@@ -0,0 +1,122 @@
+"""
+This module contains the functions and commands used to benchmark the timing of various operations in the codebase.
+
+The idea of this functionality is to be able to track the performance of various operations over time and normalise
+it for different hardware. For example, say we just need to use a certain section of the codebase, we can use this
+functionality to extract the timing performance of that specific operation and compare it to previous usages.
+"""
+
+from __future__ import annotations
+
+import pathlib
+import subprocess
+from pathlib import Path
+
+import click
+
+from .index import develop
+from .utils import echo_and_check_subprocess
+
+__all__ = [
+ "benchmark_timing_operations",
+ "benchmark_timing_operations_command",
+]
+
+output_timing_log = ["python", "-X", "importtime", "-c", "import tidy3d"]
+
+# Runs the import 100 times.
+average_test_import = [
+ "python",
+ str(Path("scripts", "benchmark_import.py")),
+]
+
+timing_commands = {
+ "output_timing_log": output_timing_log,
+ "average_test_import": average_test_import,
+}
+
+
+def benchmark_timing_operations(
+ timing_command: str, in_poetry_environment: bool = True, output_file: str = "import.log"
+):
+ """
+ This function is used to time and benchmark the timing performance of various operations in the codebase. The
+ idea of this functionality is to be able to track the performance of various operations over time and normalise
+ it for different hardware. For example, say we just need to use a certain section of the codebase, we can use
+ this functionality to extract the timing performance of that specific operation and compare it to previous usages.
+
+ Note that this is run within the top level of the tidy3d package. We can write specific files with specific
+ operations in the `tests` section and benchmark them properly using this. This function does not require poetry
+ and can be run anywhere where a tidy3d installation is already implemented. The output file has an extension.
+ """
+ timing_command_list = []
+ if output_file is None:
+ output_file = timing_command.split("_")[1:] + ".log"
+
+ output_file_path = pathlib.Path(output_file)
+
+ try:
+ output_file_write = open(output_file_path, "w+")
+ except FileNotFoundError:
+ raise FileNotFoundError(
+ "The output file path "
+ + str(output_file_path)
+ + " does not exist and cannot be created."
+ ) from None
+
+ if in_poetry_environment:
+ timing_command_list += ["poetry", "run"]
+
+ try:
+ timing_command_list = timing_commands[timing_command].copy()
+ except KeyError:
+ # This has to do with choosing a timing command not available in the dictionary
+ raise KeyError(
+ f"Make sure the selected timing command {timing_command} "
+ "corresponds to an existing command."
+ ) from None
+
+ echo_and_check_subprocess(
+ command=timing_command_list, stdout=output_file_write, stderr=subprocess.STDOUT
+ )
+
+
+@click.option(
+ "-c",
+ "--timing-command",
+ type=str,
+ help="Choose between any of the existing timing commands.",
+)
+@click.option(
+ "--in-poetry-environment",
+ default=True,
+ type=bool,
+ is_flag=True,
+ help="Runs in poetry environment if True.",
+)
+@click.option(
+ "-o",
+ "--output-file",
+ default="import.log",
+ type=str,
+ help="Output file name. Defaults to 'import.log'.'",
+)
+@develop.command(
+ name="benchmark-timing-operations", help="Benchmarks the timing of various operations."
+)
+def benchmark_timing_operations_command(
+ timing_command: str, in_poetry_environment: bool = True, output_file: str = "import.log"
+):
+ # If timing_command is inputted without a value, raise a warning and print out the existing key options from the
+ # timing command dictionary.
+ if timing_command is None:
+ raise ValueError(
+ "Please input a timing command with -c . The existing timing commands are: "
+ + str(list(timing_commands.keys()))
+ )
+
+ benchmark_timing_operations(
+ timing_command=timing_command,
+ in_poetry_environment=in_poetry_environment,
+ output_file=output_file,
+ )
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/tests.py b/tidy3d/plugins/smatrix/web/cli/develop/tests.py
new file mode 100644
index 0000000000..3a75a07389
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/tests.py
@@ -0,0 +1,66 @@
+"""
+This module contains the CLI commands for testing the tidy3d package. This includes testing the base package and the
+notebooks in order to achieve reproducibility between hardwares.
+"""
+
+from __future__ import annotations
+
+import click
+
+from .index import develop
+from .install import install_in_poetry
+from .utils import echo_and_run_subprocess
+
+__all__ = [
+ "test_in_environment_command",
+ "test_options",
+]
+
+
+def test_options(options: list):
+ """
+ Inclusive rather than exclusive tests in a given set of environments.
+
+ Parameters
+ ----------
+ options : list
+ A list of options for which tests to run. Options are 'base' and 'notebooks'.
+ """
+ if "base" in options:
+ echo_and_run_subprocess(["poetry", "run", "pytest", "-rA", "tests"])
+ if "notebooks" in options:
+ echo_and_run_subprocess(["poetry", "run", "pytest", "-rA", "tests/full_test_notebooks.py"])
+
+
+@click.option(
+ "--types",
+ default=["base"],
+ help="Types of tests to run. Defaults to 'base'. Other options",
+ type=list,
+)
+@click.option(
+ "--env",
+ default="dev",
+ help="Poetry environment to install. Defaults to 'dev'.",
+ type=str,
+)
+@develop.command(
+ name="test-in-envrionment", help="Installs the specified poetry environment and tests"
+)
+def test_in_environment_command(types: list, env: str = "dev"):
+ """
+ Installs a poetry environment specified by the extra definition in pyproject.toml and runs tests with pytest and
+ any additional arguments. Requires a poetry installation so make sure to verify the installation previously.
+
+ If the environment is already installed, it will be reinstalled to ensure the latest version of a reproducible
+ envrionment is used.
+
+ Parameters
+ ----------
+ types : list
+ A list of options for which tests to run.
+ env : str
+ The name of the poetry environment to install. Defaults to 'dev'. See pyproject.toml
+ """
+ install_in_poetry(env)
+ test_options(types)
diff --git a/tidy3d/plugins/smatrix/web/cli/develop/utils.py b/tidy3d/plugins/smatrix/web/cli/develop/utils.py
new file mode 100644
index 0000000000..8faaf5d6e1
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/develop/utils.py
@@ -0,0 +1,70 @@
+"""
+Utility functions for the tidy3d develop CLI.
+"""
+
+from __future__ import annotations
+
+import pathlib
+import subprocess
+
+import tidy3d
+
+__all__ = [
+ "echo_and_check_subprocess",
+ "echo_and_run_subprocess",
+ "get_install_directory",
+]
+
+
+def get_install_directory():
+ """
+ Retrieve the installation directory of the tidy3d module.
+
+ Returns
+ -------
+ pathlib.Path
+ The absolute path of the parent directory of the tidy3d module.
+ """
+ return pathlib.Path(tidy3d.__file__).parent.parent.absolute()
+
+
+def echo_and_run_subprocess(command: list, **kwargs):
+ """
+ Print and execute a subprocess command.
+
+ Parameters
+ ----------
+ command : list
+ A list of command line arguments to be executed.
+ **kwargs : dict
+ Additional keyword arguments to pass to subprocess.run.
+
+ Returns
+ -------
+ subprocess.CompletedProcess
+ The result of the subprocess execution.
+ """
+ concatenated_command = " ".join(command)
+ print("Running: " + concatenated_command)
+ return subprocess.run(command, cwd=get_install_directory(), **kwargs)
+
+
+def echo_and_check_subprocess(command: list, *args, **kwargs):
+ """
+ Print and execute a subprocess command, ensuring it completes successfully.
+
+ Parameters
+ ----------
+ command : list
+ A list of command line arguments to be executed.
+ **kwargs : dict
+ Additional keyword arguments to pass to subprocess.check_call.
+
+ Returns
+ -------
+ int
+ The return code of the subprocess execution.
+ """
+ concatenated_command = " ".join(command)
+ print("Running: " + concatenated_command)
+ return subprocess.check_call(command, *args, **kwargs, cwd=get_install_directory())
diff --git a/tidy3d/plugins/smatrix/web/cli/migrate.py b/tidy3d/plugins/smatrix/web/cli/migrate.py
new file mode 100644
index 0000000000..17fbd3c7ed
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/migrate.py
@@ -0,0 +1,72 @@
+"""Migrate authentication to API key."""
+
+from __future__ import annotations
+
+import json
+import os
+
+import click
+import requests
+import toml
+
+from tidy3d.web.core.constants import HEADER_APPLICATION, HEADER_APPLICATION_VALUE, KEY_APIKEY
+from tidy3d.web.core.environment import Env
+
+from .constants import CONFIG_FILE, CREDENTIAL_FILE, TIDY3D_DIR
+
+
+def migrate() -> bool:
+ """Click command to migrate the credential to api key."""
+ if os.path.exists(CREDENTIAL_FILE):
+ with open(CREDENTIAL_FILE, encoding="utf-8") as fp:
+ auth_json = json.load(fp)
+ email = auth_json["email"]
+ password = auth_json["password"]
+ if email and password:
+ is_migrate = click.prompt(
+ "This system was found to use the old authentication protocol based on auth.json, "
+ "which will not be supported in the upcoming 2.0 release. We strongly recommend "
+ "migrating to the API key authentication before the release. Would you like to "
+ "migrate to the API key authentication now? "
+ "This will create a '~/.tidy3d/config' file on your machine "
+ "to store the API key from your online account but all other "
+ "workings of Tidy3D will remain the same.",
+ type=bool,
+ default=True,
+ )
+ if is_migrate:
+ headers = {HEADER_APPLICATION: HEADER_APPLICATION_VALUE}
+ resp = requests.get(
+ f"{Env.current.web_api_endpoint}/auth",
+ headers=headers,
+ auth=(email, password),
+ )
+ if resp.status_code != 200:
+ click.echo(f"Migrate to api key failed: {resp.text}")
+ return False
+ # click.echo(json.dumps(resp.json(), indent=4))
+ access_token = resp.json()["data"]["auth"]["accessToken"]
+ headers["Authorization"] = f"Bearer {access_token}"
+ resp = requests.get(f"{Env.current.web_api_endpoint}/apikey", headers=headers)
+ if resp.status_code != 200:
+ click.echo(f"Migrate to api key failed: {resp.text}")
+ return False
+ click.echo(json.dumps(resp.json(), indent=4))
+ apikey = resp.json()["data"]
+ if not apikey:
+ resp = requests.post(f"{Env.current.web_api_endpoint}/apikey", headers=headers)
+ if resp.status_code != 200:
+ click.echo(f"Migrate to api key failed: {resp.text}")
+ return False
+ apikey = resp.json()["data"]
+ if not os.path.exists(TIDY3D_DIR):
+ os.mkdir(TIDY3D_DIR)
+ with open(CONFIG_FILE, "w+", encoding="utf-8") as config_file:
+ toml_config = toml.loads(config_file.read())
+ toml_config.update({KEY_APIKEY: apikey})
+ config_file.write(toml.dumps(toml_config))
+
+ # rename auth.json to auth.json.bak
+ os.rename(CREDENTIAL_FILE, CREDENTIAL_FILE + ".bak")
+ return True
+ click.echo("You can migrate to api key by running 'tidy3d migrate' command.")
diff --git a/tidy3d/plugins/smatrix/web/cli/readme.md b/tidy3d/plugins/smatrix/web/cli/readme.md
new file mode 100644
index 0000000000..e0e4bda671
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/cli/readme.md
@@ -0,0 +1,107 @@
+## API key
+
+### Generating an API key
+
+You can find your API key in the web http://tidy3d.simulation.cloud
+
+
+### Setup API key
+
+#### Environment Variable
+``export SIMCLOUD_API_KEY="your_api_key"``
+
+#### Command line
+``tidy3d configure``, then enter your API key when prompted
+
+#### Manually
+``echo 'apikey = "your_api_key"' > ~/.tidy3d/config``
+
+## Publishing Package
+
+First, configure poetry to work with test.PyPI. Give it a name of `test-pypi`.
+
+``poetry config repositories.test-pypi https://test.pypi.org/legacy/``
+
+``poetry config pypi-token.test-pypi <>``
+
+Then, build and upload, make sure to specify repository `-r` of `test-pypi`.
+
+``poetry publish --build -r test-pypi``
+
+The changes should be reflected on test PyPI https://test.pypi.org/project/tidy3d-beta/1.8.0/
+
+### Fixing pyproject.toml
+
+Note, this did not work originally because while the package directory name is `tidy3d/`, the repository name on PyPI is `tidy3d-beta`.
+
+So a couple changes were required in `pyproject.toml`.
+
+I needed to change the `name=tidy3d` to `name=tidy3d-beta`
+I needed to add what packages to include, namely
+```
+packages = [
+ { include = "tidy3d" },
+ { include = "tidy3d/web" },
+ { include = "tidy3d/plugins" },
+]
+```
+(note, not 100% sure I needed to include `web` and `plugins`, but I think they are needed because they aren't imported in the top level `tidy3d/__init__.py` file.)
+
+Once I did this, the steps from the previous section worked properly.
+
+### Testing installation from test.PyPI
+
+To test, in a clean environment
+
+``python3.9 -m pip install --index-url https://test.pypi.org/simple/ tidy3d-beta``
+
+note: I was getting errors doing this, because it was trying to install all previously uploaded versions of `tidy3d-beta`. So when I did
+
+``python3.9 -m pip install --index-url https://test.pypi.org/simple/ tidy3d-beta==1.8.0``
+
+It started working, however I got another error
+
+```
+Collecting tidy3d-beta==1.8.0
+ Using cached https://test-files.pythonhosted.org/packages/5d/67/0cd75f00bb851289c79b584600b17daa7e5d077d2afa7ab8bfccc0331b3b/tidy3d_beta-1.8.0-py3-none-any.whl (257 kB)
+ERROR: Could not find a version that satisfies the requirement pyroots<0.6.0,>=0.5.0 (from tidy3d-beta) (from versions: none)
+ERROR: No matching distribution found for pyroots<0.6.0,>=0.5.0
+```
+
+It turns out this is expected using test pyPI as explained [here](https://packaging.python.org/en/latest/tutorials/packaging-projects/#installing-your-newly-uploaded-package).
+
+When I try to install `tidy3d-beta` from test.pyPI in an environment with the dependencies already installed from
+``python3.9 -m pip install -e '.[dev]'``
+``python3.9 -m pip uninstall tidy3d-beta``
+
+It works as expected, besides needing `click`
+
+``python3.9 -c "import tidy3d as td; import tidy3d.web as web; from tidy3d.plugins.mode import ModeSolver"``
+
+```
+[11:04:02] INFO Using client version: 1.8.0 __init__.py:112
+Traceback (most recent call last):
+ File "", line 1, in
+ File "/usr/local/lib/python3.9/site-packages/tidy3d/web/__init__.py", line 8, in
+ from .cli import tidy3d_cli
+ File "/usr/local/lib/python3.9/site-packages/tidy3d/web/cli/__init__.py", line 4, in
+ from .app import tidy3d_cli
+ File "/usr/local/lib/python3.9/site-packages/tidy3d/web/cli/app.py", line 7, in
+ import click
+ModuleNotFoundError: No module named 'click'
+```
+
+### Fixing the error
+
+So I added `click` with
+
+``poetry add click``
+
+This changed the `poetry.lock` and `pyproject.toml`.
+
+I then bumped the version in `version.py` and `pyproject.toml` to `1.8.1` (otherwise, could not upload again to PyPI for same version) and repeated the publishing steps from above again.
+
+Testing the newly pip-installed version I was able to successfully import tidy3d and run a simulation!
+
+
+
diff --git a/tidy3d/plugins/smatrix/web/core/__init__.py b/tidy3d/plugins/smatrix/web/core/__init__.py
new file mode 100644
index 0000000000..f2f6150797
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/__init__.py
@@ -0,0 +1 @@
+"""Tidy3d core package imports"""
diff --git a/tidy3d/plugins/smatrix/web/core/account.py b/tidy3d/plugins/smatrix/web/core/account.py
new file mode 100644
index 0000000000..07cef4caaa
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/account.py
@@ -0,0 +1,66 @@
+"""Tidy3d user account."""
+
+from __future__ import annotations
+
+from datetime import datetime
+from typing import Optional
+
+from pydantic.v1 import Extra, Field
+
+from .http_util import http
+from .types import Tidy3DResource
+
+
+class Account(Tidy3DResource, extra=Extra.allow):
+ """Tidy3D User Account."""
+
+ allowance_cycle_type: Optional[str] = Field(
+ None,
+ title="AllowanceCycleType",
+ description="Daily or Monthly",
+ alias="allowanceCycleType",
+ )
+ credit: Optional[float] = Field(
+ 0, title="credit", description="Current FlexCredit balance", alias="credit"
+ )
+ credit_expiration: Optional[datetime] = Field(
+ None,
+ title="creditExpiration",
+ description="Expiration date",
+ alias="creditExpiration",
+ )
+ allowance_current_cycle_amount: Optional[float] = Field(
+ 0,
+ title="allowanceCurrentCycleAmount",
+ description="Daily/Monthly free simulation balance",
+ alias="allowanceCurrentCycleAmount",
+ )
+ allowance_current_cycle_end_date: Optional[datetime] = Field(
+ None,
+ title="allowanceCurrentCycleEndDate",
+ description="Daily/Monthly free simulation balance expiration date",
+ alias="allowanceCurrentCycleEndDate",
+ )
+ daily_free_simulation_counts: Optional[int] = Field(
+ 0,
+ title="dailyFreeSimulationCounts",
+ description="Daily free simulation counts",
+ alias="dailyFreeSimulationCounts",
+ )
+
+ @classmethod
+ def get(cls):
+ """Get user account information.
+
+ Parameters
+ ----------
+
+ Returns
+ -------
+ account : Account
+ """
+ resp = http.get("tidy3d/py/account")
+ if resp:
+ account = Account(**resp)
+ return account
+ return None
diff --git a/tidy3d/plugins/smatrix/web/core/cache.py b/tidy3d/plugins/smatrix/web/core/cache.py
new file mode 100644
index 0000000000..d83421ca21
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/cache.py
@@ -0,0 +1,6 @@
+"""Local caches."""
+
+from __future__ import annotations
+
+FOLDER_CACHE = {}
+S3_STS_TOKENS = {}
diff --git a/tidy3d/plugins/smatrix/web/core/constants.py b/tidy3d/plugins/smatrix/web/core/constants.py
new file mode 100644
index 0000000000..108d148800
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/constants.py
@@ -0,0 +1,33 @@
+"""Defines constants for core."""
+
+# HTTP Header key and value
+from __future__ import annotations
+
+HEADER_APIKEY = "simcloud-api-key"
+HEADER_VERSION = "tidy3d-python-version"
+HEADER_SOURCE = "source"
+HEADER_SOURCE_VALUE = "Python"
+HEADER_USER_AGENT = "User-Agent"
+HEADER_APPLICATION = "Application"
+HEADER_APPLICATION_VALUE = "TIDY3D"
+
+
+SIMCLOUD_APIKEY = "SIMCLOUD_APIKEY"
+KEY_APIKEY = "apikey"
+JSON_TAG = "JSON_STRING"
+# type of the task_id
+TaskId = str
+# type of task_name
+TaskName = str
+
+
+SIMULATION_JSON = "simulation.json"
+SIMULATION_DATA_HDF5 = "output/monitor_data.hdf5"
+SIMULATION_DATA_HDF5_GZ = "output/simulation_data.hdf5.gz"
+RUNNING_INFO = "output/solver_progress.csv"
+SIM_LOG_FILE = "output/tidy3d.log"
+SIM_FILE_HDF5 = "simulation.hdf5"
+SIM_FILE_HDF5_GZ = "simulation.hdf5.gz"
+MODE_FILE_HDF5_GZ = "mode_solver.hdf5.gz"
+MODE_DATA_HDF5_GZ = "output/mode_solver_data.hdf5.gz"
+SIM_ERROR_FILE = "output/tidy3d_error.json"
diff --git a/tidy3d/plugins/smatrix/web/core/core_config.py b/tidy3d/plugins/smatrix/web/core/core_config.py
new file mode 100644
index 0000000000..77f0fb5c33
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/core_config.py
@@ -0,0 +1,44 @@
+"""Tidy3d core log, need init config from Tidy3d api"""
+
+from __future__ import annotations
+
+import logging as log
+
+# default setting
+config_setting = {
+ "logger": log,
+ "logger_console": None,
+ "version": "",
+}
+
+
+def set_config(logger, logger_console, version: str):
+ """Init tidy3d core logger and logger console.
+
+ Parameters
+ ----------
+ logger : :class:`.Logger`
+ Tidy3d log Logger.
+ logger_console : :class:`.Console`
+ Get console from logging handlers.
+ version : str
+ tidy3d version
+ """
+ config_setting["logger"] = logger
+ config_setting["logger_console"] = logger_console
+ config_setting["version"] = version
+
+
+def get_logger():
+ """Get logging handlers."""
+ return config_setting["logger"]
+
+
+def get_logger_console():
+ """Get console from logging handlers."""
+ return config_setting["logger_console"]
+
+
+def get_version():
+ """Get version from cache."""
+ return config_setting["version"]
diff --git a/tidy3d/plugins/smatrix/web/core/environment.py b/tidy3d/plugins/smatrix/web/core/environment.py
new file mode 100644
index 0000000000..261f53ea93
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/environment.py
@@ -0,0 +1,241 @@
+"""Environment Setup."""
+
+from __future__ import annotations
+
+import os
+import ssl
+from typing import Optional
+
+from pydantic.v1 import BaseSettings, Field
+
+from .core_config import get_logger
+
+
+class EnvironmentConfig(BaseSettings):
+ """Basic Configuration for definition environment."""
+
+ def __hash__(self):
+ return hash((type(self), *tuple(self.__dict__.values())))
+
+ name: str
+ web_api_endpoint: str
+ website_endpoint: str
+ s3_region: str
+ ssl_verify: bool = Field(True, env="TIDY3D_SSL_VERIFY")
+ enable_caching: Optional[bool] = None
+ ssl_version: Optional[ssl.TLSVersion] = None
+ env_vars: Optional[dict[str, str]] = None
+
+ def active(self) -> None:
+ """Activate the environment instance."""
+ Env.set_current(self)
+
+ def get_real_url(self, path: str) -> str:
+ """Get the real url for the environment instance.
+
+ Parameters
+ ----------
+ path : str
+ Base path to append to web api endpoint.
+
+ Returns
+ -------
+ str
+ Full url for the webapi.
+ """
+ return "/".join([self.web_api_endpoint, path])
+
+
+dev = EnvironmentConfig(
+ name="dev",
+ s3_region="us-east-1",
+ web_api_endpoint="https://tidy3d-api.dev-simulation.cloud",
+ website_endpoint="https://tidy3d.dev-simulation.cloud",
+)
+
+uat = EnvironmentConfig(
+ name="uat",
+ s3_region="us-west-2",
+ web_api_endpoint="https://tidy3d-api.uat-simulation.cloud",
+ website_endpoint="https://tidy3d.uat-simulation.cloud",
+)
+
+pre = EnvironmentConfig(
+ name="pre",
+ s3_region="us-gov-west-1",
+ web_api_endpoint="https://preprod-tidy3d-api.simulation.cloud",
+ website_endpoint="https://preprod-tidy3d.simulation.cloud",
+)
+
+prod = EnvironmentConfig(
+ name="prod",
+ s3_region="us-gov-west-1",
+ web_api_endpoint="https://tidy3d-api.simulation.cloud",
+ website_endpoint="https://tidy3d.simulation.cloud",
+)
+
+
+nexus = EnvironmentConfig(
+ name="nexus",
+ web_api_endpoint="http://127.0.0.1:5000",
+ ssl_verify=False,
+ enable_caching=False,
+ s3_region="us-east-1",
+ website_endpoint="http://127.0.0.1/tidy3d",
+ env_vars={"AWS_ENDPOINT_URL_S3": "http://127.0.0.1:9000"},
+)
+
+
+class Environment:
+ """Environment decorator for user interactive.
+
+ Example
+ -------
+ >>> from tidy3d.web.core.environment import Env
+ >>> Env.dev.active()
+ >>> assert Env.current.name == "dev"
+ ...
+ """
+
+ env_map = {
+ "dev": dev,
+ "uat": uat,
+ "prod": prod,
+ "nexus": nexus,
+ }
+
+ def __init__(self):
+ log = get_logger()
+ """Initialize the environment."""
+ self._previous_env_vars = {}
+ env_key = os.environ.get("TIDY3D_ENV")
+ env_key = env_key.lower() if env_key else env_key
+ log.info(f"env_key is {env_key}")
+ if not env_key:
+ self._current = prod
+ elif env_key in self.env_map:
+ self._current = self.env_map[env_key]
+ else:
+ log.warning(
+ f"The value '{env_key}' for the environment variable TIDY3D_ENV is not supported. "
+ f"Using prod as default."
+ )
+ self._current = prod
+
+ if self._current.env_vars:
+ for key, value in self._current.env_vars.items():
+ self._previous_env_vars[key] = os.environ.get(key)
+ os.environ[key] = value
+
+ @property
+ def current(self) -> EnvironmentConfig:
+ """Get the current environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the current environment.
+ """
+ return self._current
+
+ @property
+ def dev(self) -> EnvironmentConfig:
+ """Get the dev environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the dev environment.
+ """
+ return dev
+
+ @property
+ def uat(self) -> EnvironmentConfig:
+ """Get the uat environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the uat environment.
+ """
+ return uat
+
+ @property
+ def pre(self) -> EnvironmentConfig:
+ """Get the preprod environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the preprod environment.
+ """
+ return pre
+
+ @property
+ def prod(self) -> EnvironmentConfig:
+ """Get the prod environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the prod environment.
+ """
+ return prod
+
+ @property
+ def nexus(self) -> EnvironmentConfig:
+ """Get the nexus environment.
+
+ Returns
+ -------
+ EnvironmentConfig
+ The config for the nexus environment.
+ """
+ return nexus
+
+ def set_current(self, config: EnvironmentConfig) -> None:
+ """Set the current environment.
+
+ Parameters
+ ----------
+ config : EnvironmentConfig
+ The environment to set to current.
+ """
+ for key, value in self._previous_env_vars.items():
+ if value is None:
+ if key in os.environ:
+ del os.environ[key]
+ else:
+ os.environ[key] = value
+ self._previous_env_vars = {}
+
+ if config.env_vars:
+ for key, value in config.env_vars.items():
+ self._previous_env_vars[key] = os.environ.get(key)
+ os.environ[key] = value
+
+ self._current = config
+
+ def enable_caching(self, enable_caching: bool = True) -> None:
+ """Set the environment configuration setting with regards to caching simulation results.
+
+ Parameters
+ ----------
+ enable_caching: bool = True
+ If ``True``, do duplicate checking. Return the previous simulation result if duplicate simulation is found.
+ If ``False``, do not duplicate checking. Just run the task directly.
+ """
+ self._current.enable_caching = enable_caching
+
+ def set_ssl_version(self, ssl_version: ssl.TLSVersion) -> None:
+ """Set the ssl version.
+
+ Parameters
+ ----------
+ ssl_version : ssl.TLSVersion
+ The ssl version to set.
+ """
+ self._current.ssl_version = ssl_version
+
+
+Env = Environment()
diff --git a/tidy3d/plugins/smatrix/web/core/exceptions.py b/tidy3d/plugins/smatrix/web/core/exceptions.py
new file mode 100644
index 0000000000..4061a8c6f8
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/exceptions.py
@@ -0,0 +1,21 @@
+"""Custom Tidy3D exceptions"""
+
+from __future__ import annotations
+
+from typing import Optional
+
+from .core_config import get_logger
+
+
+class WebError(Exception):
+ """Any error in tidy3d"""
+
+ def __init__(self, message: Optional[str] = None):
+ """Log just the error message and then raise the Exception."""
+ log = get_logger()
+ super().__init__(message)
+ log.error(message)
+
+
+class WebNotFoundError(WebError):
+ """A generic error indicating an HTTP 404 (resource not found)."""
diff --git a/tidy3d/plugins/smatrix/web/core/file_util.py b/tidy3d/plugins/smatrix/web/core/file_util.py
new file mode 100644
index 0000000000..833e1e1f71
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/file_util.py
@@ -0,0 +1,82 @@
+"""File compression utilities"""
+
+from __future__ import annotations
+
+import gzip
+import os
+import shutil
+import tempfile
+
+import h5py
+
+from tidy3d.web.core.constants import JSON_TAG
+
+
+def compress_file_to_gzip(input_file, output_gz_file):
+ """
+ Compresses a file using gzip.
+
+ Args:
+ input_file (str): The path of the input file.
+ output_gz_file (str): The path of the output gzip file.
+ """
+ with open(input_file, "rb") as file_in:
+ with gzip.open(output_gz_file, "wb") as file_out:
+ shutil.copyfileobj(file_in, file_out)
+
+
+def extract_gzip_file(input_gz_file, output_file):
+ """
+ Extract a gzip file.
+
+ Args:
+ input_gz_file (str): The path of the gzip input file.
+ output_file (str): The path of the output file.
+ """
+ with gzip.open(input_gz_file, "rb") as file_in:
+ with open(output_file, "wb") as file_out:
+ shutil.copyfileobj(file_in, file_out)
+
+
+def read_simulation_from_hdf5_gz(file_name: str) -> str:
+ """read simulation str from hdf5.gz"""
+
+ hdf5_file, hdf5_file_path = tempfile.mkstemp(".hdf5")
+ os.close(hdf5_file_path)
+ try:
+ extract_gzip_file(file_name, hdf5_file_path)
+ json_str = read_simulation_from_hdf5(file_name)
+ finally:
+ os.unlink(hdf5_file_path)
+ return json_str
+
+
+"""TODO: _json_string_key and read_simulation_from_hdf5 are duplicated functions that also exist
+as methods in Tidy3dBaseModel. For consistency it would be best if this duplication is avoided."""
+
+
+def _json_string_key(index):
+ """Get json string key for string chunk number ``index``."""
+ if index:
+ return f"{JSON_TAG}_{index}"
+ return JSON_TAG
+
+
+def read_simulation_from_hdf5(file_name: str) -> str:
+ """read simulation str from hdf5"""
+ with h5py.File(file_name, "r") as f_handle:
+ num_string_parts = len([key for key in f_handle.keys() if JSON_TAG in key])
+ json_string = b""
+ for ind in range(num_string_parts):
+ json_string += f_handle[_json_string_key(ind)][()]
+ return json_string
+
+
+"""End TODO"""
+
+
+def read_simulation_from_json(file_name: str) -> str:
+ """read simulation str from json"""
+ with open(file_name) as json_file:
+ json_data = json_file.read()
+ return json_data
diff --git a/tidy3d/plugins/smatrix/web/core/http_util.py b/tidy3d/plugins/smatrix/web/core/http_util.py
new file mode 100644
index 0000000000..ca8c00abc3
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/http_util.py
@@ -0,0 +1,214 @@
+"""Http connection pool and authentication management."""
+
+from __future__ import annotations
+
+import os
+from enum import Enum
+from functools import wraps
+from os.path import expanduser
+
+import requests
+import toml
+from requests.adapters import HTTPAdapter
+from urllib3.util.ssl_ import create_urllib3_context
+
+from . import core_config
+from .constants import (
+ HEADER_APIKEY,
+ HEADER_APPLICATION,
+ HEADER_APPLICATION_VALUE,
+ HEADER_SOURCE,
+ HEADER_SOURCE_VALUE,
+ HEADER_USER_AGENT,
+ HEADER_VERSION,
+ KEY_APIKEY,
+ SIMCLOUD_APIKEY,
+)
+from .core_config import get_logger
+from .environment import Env
+from .exceptions import WebError, WebNotFoundError
+
+REINITIALIZED = False
+
+TIDY3D_DIR = f"{expanduser('~')}"
+if os.access(TIDY3D_DIR, os.W_OK):
+ TIDY3D_DIR = f"{expanduser('~')}/.tidy3d"
+else:
+ TIDY3D_DIR = "/tmp/.tidy3d"
+CONFIG_FILE = TIDY3D_DIR + "/config"
+CREDENTIAL_FILE = TIDY3D_DIR + "/auth.json"
+
+
+class ResponseCodes(Enum):
+ """HTTP response codes to handle individually."""
+
+ UNAUTHORIZED = 401
+ OK = 200
+ NOT_FOUND = 404
+
+
+def get_version() -> None:
+ """Get the version for the current environment."""
+ return core_config.get_version()
+
+
+def get_user_agent():
+ """Get the user agent the current environment."""
+ return os.environ.get("TIDY3D_AGENT", f"Python-Client/{get_version()}")
+
+
+def api_key() -> None:
+ """Get the api key for the current environment."""
+
+ if os.environ.get(SIMCLOUD_APIKEY):
+ return os.environ.get(SIMCLOUD_APIKEY)
+ if os.path.exists(CONFIG_FILE):
+ with open(CONFIG_FILE, encoding="utf-8") as config_file:
+ config = toml.loads(config_file.read())
+ return config.get(KEY_APIKEY, "")
+
+ return None
+
+
+def api_key_auth(request: requests.request) -> requests.request:
+ """Save the authentication info in a request.
+
+ Parameters
+ ----------
+ request : requests.request
+ The original request to set authentication for.
+
+ Returns
+ -------
+ requests.request
+ The request with authentication set.
+ """
+ key = api_key()
+ version = get_version()
+ if not key:
+ raise ValueError(
+ "API key not found. To get your API key, sign into 'https://tidy3d.simulation.cloud' "
+ "and copy it from your 'Account' page. Then you can configure tidy3d through command "
+ "line 'tidy3d configure' and enter your API key when prompted. "
+ "Alternatively, especially if using windows, you can manually create the configuration "
+ "file by creating a file at their home directory '~/.tidy3d/config' (unix) or "
+ "'.tidy3d/config' (windows) containing the following line: "
+ "apikey = 'XXX'. Here XXX is your API key copied from your account page within quotes."
+ )
+ if not version:
+ raise ValueError("version not found.")
+
+ request.headers[HEADER_APIKEY] = key
+ request.headers[HEADER_VERSION] = version
+ request.headers[HEADER_SOURCE] = HEADER_SOURCE_VALUE
+ request.headers[HEADER_USER_AGENT] = get_user_agent()
+ return request
+
+
+def get_headers() -> dict[str, str]:
+ """get headers for http request.
+
+ Returns
+ -------
+ Dict[str, str]
+ dictionary with "Authorization" and "Application" keys.
+ """
+ return {
+ HEADER_APIKEY: api_key(),
+ HEADER_APPLICATION: HEADER_APPLICATION_VALUE,
+ HEADER_USER_AGENT: get_user_agent(),
+ }
+
+
+def http_interceptor(func):
+ """Intercept the response and raise an exception if the status code is not 200."""
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ """The wrapper function."""
+
+ # Extend some capabilities of func
+ resp = func(*args, **kwargs)
+
+ if resp.status_code != ResponseCodes.OK.value:
+ if resp.status_code == ResponseCodes.NOT_FOUND.value:
+ raise WebNotFoundError("Resource not found (HTTP 404).")
+ json_resp = resp.json()
+ if "error" in json_resp.keys():
+ raise WebError(json_resp["error"])
+ resp.raise_for_status()
+
+ if not resp.text:
+ return None
+ result = resp.json()
+
+ if isinstance(result, dict):
+ warning = result.get("warning")
+ if warning:
+ log = get_logger()
+ log.warning(warning)
+
+ return result.get("data") if "data" in result else result
+
+ return wrapper
+
+
+class TLSAdapter(HTTPAdapter):
+ def init_poolmanager(self, *args, **kwargs):
+ context = create_urllib3_context(ssl_version=Env.current.ssl_version)
+ kwargs["ssl_context"] = context
+ return super().init_poolmanager(*args, **kwargs)
+
+
+class HttpSessionManager:
+ """Http util class."""
+
+ def __init__(self, session: requests.Session):
+ """Initialize the session."""
+ ssl_version = Env.current.ssl_version
+ if ssl_version:
+ session.mount("https://", TLSAdapter())
+ session.verify = Env.current.ssl_verify
+ self.session = session
+
+ def reinit(self):
+ """Reinitialize the session."""
+ global REINITIALIZED
+ ssl_version = Env.current.ssl_version
+ if ssl_version and not REINITIALIZED:
+ self.session.mount("https://", TLSAdapter())
+ REINITIALIZED = True
+ self.session.verify = Env.current.ssl_verify
+
+ @http_interceptor
+ def get(self, path: str, json=None, params=None):
+ """Get the resource."""
+ self.reinit()
+ return self.session.get(
+ url=Env.current.get_real_url(path), auth=api_key_auth, json=json, params=params
+ )
+
+ @http_interceptor
+ def post(self, path: str, json=None):
+ """Create the resource."""
+ self.reinit()
+ return self.session.post(Env.current.get_real_url(path), json=json, auth=api_key_auth)
+
+ @http_interceptor
+ def put(self, path: str, json=None, files=None):
+ """Update the resource."""
+ self.reinit()
+ return self.session.put(
+ Env.current.get_real_url(path), json=json, auth=api_key_auth, files=files
+ )
+
+ @http_interceptor
+ def delete(self, path: str, json=None, params=None):
+ """Delete the resource."""
+ self.reinit()
+ return self.session.delete(
+ Env.current.get_real_url(path), auth=api_key_auth, json=json, params=params
+ )
+
+
+http = HttpSessionManager(requests.Session())
diff --git a/tidy3d/plugins/smatrix/web/core/s3utils.py b/tidy3d/plugins/smatrix/web/core/s3utils.py
new file mode 100644
index 0000000000..3df8b1bd70
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/s3utils.py
@@ -0,0 +1,430 @@
+"""handles filesystem, storage"""
+
+from __future__ import annotations
+
+import os
+import pathlib
+import tempfile
+import urllib
+from collections.abc import Mapping
+from datetime import datetime
+from enum import Enum
+from typing import Callable, Optional
+
+import boto3
+from boto3.s3.transfer import TransferConfig
+from pydantic.v1 import BaseModel, Field
+from rich.progress import (
+ BarColumn,
+ DownloadColumn,
+ Progress,
+ TextColumn,
+ TimeRemainingColumn,
+ TransferSpeedColumn,
+)
+
+from .core_config import get_logger_console
+from .environment import Env
+from .exceptions import WebError
+from .file_util import extract_gzip_file
+from .http_util import http
+
+IN_TRANSIT_SUFFIX = ".tmp"
+
+
+class _UserCredential(BaseModel):
+ """Stores information about user credentials."""
+
+ access_key_id: str = Field(alias="accessKeyId")
+ expiration: datetime
+ secret_access_key: str = Field(alias="secretAccessKey")
+ session_token: str = Field(alias="sessionToken")
+
+
+class _S3STSToken(BaseModel):
+ """Stores information about S3 token."""
+
+ cloud_path: str = Field(alias="cloudpath")
+ user_credential: _UserCredential = Field(alias="userCredentials")
+
+ def get_bucket(self) -> str:
+ """Get the bucket name for this token."""
+
+ r = urllib.parse.urlparse(self.cloud_path)
+ return r.netloc
+
+ def get_s3_key(self) -> str:
+ """Get the s3 key for this token."""
+
+ r = urllib.parse.urlparse(self.cloud_path)
+ return r.path[1:]
+
+ def get_client(self) -> boto3.client:
+ """Get the boto client for this token."""
+
+ return boto3.client(
+ "s3",
+ region_name=Env.current.s3_region,
+ aws_access_key_id=self.user_credential.access_key_id,
+ aws_secret_access_key=self.user_credential.secret_access_key,
+ aws_session_token=self.user_credential.session_token,
+ verify=Env.current.ssl_verify,
+ )
+
+ def is_expired(self) -> bool:
+ """True if token is expired."""
+
+ return (
+ self.user_credential.expiration
+ - datetime.now(tz=self.user_credential.expiration.tzinfo)
+ ).total_seconds() < 300
+
+
+class UploadProgress:
+ """Updates progressbar with the upload status.
+
+ Attributes
+ ----------
+ progress : rich.progress.Progress()
+ Progressbar instance from rich
+ ul_task : rich.progress.Task
+ Progressbar task instance.
+ """
+
+ def __init__(self, size_bytes, progress):
+ """initialize with the size of file and rich.progress.Progress() instance.
+
+ Parameters
+ ----------
+ size_bytes: float
+ Number of total bytes to upload.
+ progress : rich.progress.Progress()
+ Progressbar instance from rich
+ """
+ self.progress = progress
+ self.ul_task = self.progress.add_task("[red]Uploading...", total=size_bytes)
+
+ def report(self, bytes_in_chunk):
+ """Update the progressbar with the most recent chunk.
+
+ Parameters
+ ----------
+ bytes_in_chunk : float
+ Description
+ """
+ self.progress.update(self.ul_task, advance=bytes_in_chunk)
+
+
+class DownloadProgress:
+ """Updates progressbar using the download status.
+
+ Attributes
+ ----------
+ progress : rich.progress.Progress()
+ Progressbar instance from rich
+ ul_task : rich.progress.Task
+ Progressbar task instance.
+ """
+
+ def __init__(self, size_bytes, progress):
+ """initialize with the size of file and rich.progress.Progress() instance
+
+ Parameters
+ ----------
+ size_bytes: float
+ Number of total bytes to download.
+ progress : rich.progress.Progress()
+ Progressbar instance from rich
+ """
+ self.progress = progress
+ self.dl_task = self.progress.add_task("[red]Downloading...", total=size_bytes)
+
+ def report(self, bytes_in_chunk):
+ """Update the progressbar with the most recent chunk.
+
+ Parameters
+ ----------
+ bytes_in_chunk : float
+ Description
+ """
+ self.progress.update(self.dl_task, advance=bytes_in_chunk)
+
+
+class _S3Action(Enum):
+ UPLOADING = "↑"
+ DOWNLOADING = "↓"
+
+
+def _get_progress(action: _S3Action):
+ """Get the progress of an action."""
+
+ col = (
+ TextColumn(f"[bold green]{_S3Action.DOWNLOADING.value}")
+ if action == _S3Action.DOWNLOADING
+ else TextColumn(f"[bold red]{_S3Action.UPLOADING.value}")
+ )
+ return Progress(
+ col,
+ TextColumn("[bold blue]{task.fields[filename]}"),
+ BarColumn(),
+ "[progress.percentage]{task.percentage:>3.1f}%",
+ "•",
+ DownloadColumn(),
+ "•",
+ TransferSpeedColumn(),
+ "•",
+ TimeRemainingColumn(),
+ console=get_logger_console(),
+ )
+
+
+_s3_config = TransferConfig()
+
+_s3_sts_tokens: [str, _S3STSToken] = {}
+
+
+def get_s3_sts_token(
+ resource_id: str, file_name: str, extra_arguments: Optional[Mapping[str, str]] = None
+) -> _S3STSToken:
+ """Get s3 sts token for the given resource id and file name.
+
+ Parameters
+ ----------
+ resource_id : str
+ The resource id, e.g. task id.
+ file_name : str
+ The remote file name on S3.
+ extra_arguments : Mapping[str, str]
+ Additional arguments for the query url.
+
+ Returns
+ -------
+ _S3STSToken
+ The S3 STS token.
+ """
+ cache_key = f"{resource_id}:{file_name}"
+ if cache_key not in _s3_sts_tokens or _s3_sts_tokens[cache_key].is_expired():
+ method = f"tidy3d/py/tasks/{resource_id}/file?filename={file_name}"
+ if extra_arguments is not None:
+ method += "&" + "&".join(f"{k}={v}" for k, v in extra_arguments.items())
+ resp = http.get(method)
+ token = _S3STSToken.parse_obj(resp)
+ _s3_sts_tokens[cache_key] = token
+ return _s3_sts_tokens[cache_key]
+
+
+def upload_file(
+ resource_id: str,
+ path: str,
+ remote_filename: str,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ extra_arguments: Optional[Mapping[str, str]] = None,
+):
+ """Upload a file to S3.
+
+ Parameters
+ ----------
+ resource_id : str
+ The resource id, e.g. task id.
+ path : str
+ Path to the file to upload.
+ remote_filename : str
+ The remote file name on S3 relative to the resource context root path.
+ verbose : bool = True
+ Whether to display a progressbar for the upload.
+ progress_callback : Callable[[float], None] = None
+ User-supplied callback function with ``bytes_in_chunk`` as argument.
+ extra_arguments : Mapping[str, str]
+ Additional arguments used to specify the upload bucket.
+ """
+
+ token = get_s3_sts_token(resource_id, remote_filename, extra_arguments)
+
+ def _upload(_callback: Callable) -> None:
+ """Perform the upload with a callback function.
+
+ Parameters
+ ----------
+ _callback : Callable[[float], None]
+ Callback function for upload, accepts ``bytes_in_chunk``
+ """
+
+ with open(path, "rb") as data:
+ token.get_client().upload_fileobj(
+ data,
+ Bucket=token.get_bucket(),
+ Key=token.get_s3_key(),
+ Callback=_callback,
+ Config=_s3_config,
+ ExtraArgs={"ContentEncoding": "gzip"}
+ if token.get_s3_key().endswith(".gz")
+ else None,
+ )
+
+ if progress_callback is not None:
+ _upload(progress_callback)
+ else:
+ if verbose:
+ with _get_progress(_S3Action.UPLOADING) as progress:
+ total_size = pathlib.Path(path).stat().st_size
+ task_id = progress.add_task("upload", filename=remote_filename, total=total_size)
+
+ def _callback(bytes_in_chunk):
+ progress.update(task_id, advance=bytes_in_chunk)
+
+ _upload(_callback)
+
+ progress.update(task_id, completed=total_size, refresh=True)
+
+ else:
+ _upload(lambda bytes_in_chunk: None)
+
+
+def download_file(
+ resource_id: str,
+ remote_filename: str,
+ to_file: Optional[str] = None,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> pathlib.Path:
+ """Download file from S3.
+
+ Parameters
+ ----------
+ resource_id : str
+ The resource id, e.g. task id.
+ remote_filename : str
+ Path to the remote file.
+ to_file : str = None
+ Local filename to save to, if not specified, use the remote_filename.
+ verbose : bool = True
+ Whether to display a progressbar for the upload
+ progress_callback : Callable[[float], None] = None
+ User-supplied callback function with ``bytes_in_chunk`` as argument.
+ """
+
+ token = get_s3_sts_token(resource_id, remote_filename)
+ client = token.get_client()
+ meta_data = client.head_object(Bucket=token.get_bucket(), Key=token.get_s3_key())
+
+ # Get only last part of the remote file name
+ remote_basename = pathlib.Path(remote_filename).name
+
+ # set to_file if None
+ if not to_file:
+ path = pathlib.Path(resource_id)
+ to_path = path / remote_basename
+ else:
+ to_path = pathlib.Path(to_file)
+
+ # make the leading directories in the 'to_path', if any
+ to_path.parent.mkdir(parents=True, exist_ok=True)
+
+ def _download(_callback: Callable) -> None:
+ """Perform the download with a callback function.
+
+ Parameters
+ ----------
+ _callback : Callable[[float], None]
+ Callback function for download, accepts ``bytes_in_chunk``
+ """
+ # Caller can assume the existence of the file means download succeeded.
+ # So make sure this file does not exist until that assumption is true.
+ to_path.unlink(missing_ok=True)
+ # Download to a temporary file.
+ try:
+ fd, tmp_file_path_str = tempfile.mkstemp(suffix=IN_TRANSIT_SUFFIX, dir=to_path.parent)
+ os.close(fd) # `tempfile.mkstemp()` creates and opens a randomly named file. close it.
+ to_path_tmp = pathlib.Path(tmp_file_path_str)
+ client.download_file(
+ Bucket=token.get_bucket(),
+ Filename=tmp_file_path_str,
+ Key=token.get_s3_key(),
+ Callback=_callback,
+ Config=_s3_config,
+ )
+ to_path_tmp.rename(to_file)
+ except Exception as e:
+ to_path_tmp.unlink(missing_ok=True) # Delete incompletely downloaded file.
+ raise e
+
+ if progress_callback is not None:
+ _download(progress_callback)
+ else:
+ if verbose:
+ with _get_progress(_S3Action.DOWNLOADING) as progress:
+ total_size = meta_data.get("ContentLength", 0)
+ progress.start()
+ task_id = progress.add_task("download", filename=remote_basename, total=total_size)
+
+ def _callback(bytes_in_chunk):
+ progress.update(task_id, advance=bytes_in_chunk)
+
+ _download(_callback)
+
+ progress.update(task_id, completed=total_size, refresh=True)
+
+ else:
+ _download(lambda bytes_in_chunk: None)
+
+ return to_path
+
+
+def download_gz_file(
+ resource_id: str,
+ remote_filename: str,
+ to_file: Optional[str] = None,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+) -> pathlib.Path:
+ """Download a ``.gz`` file and unzip it into ``to_file``, unless ``to_file`` itself
+ ends in .gz
+
+ Parameters
+ ----------
+ resource_id : str
+ The resource id, e.g. task id.
+ remote_filename : str
+ Path to the remote file.
+ to_file : str = None
+ Local filename to save to, if not specified, use the remote_filename.
+ verbose : bool = True
+ Whether to display a progressbar for the upload
+ progress_callback : Callable[[float], None] = None
+ User-supplied callback function with ``bytes_in_chunk`` as argument.
+ """
+
+ # If to_file is a gzip extension, just download
+ if to_file.lower().endswith(".gz"):
+ return download_file(
+ resource_id,
+ remote_filename,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+
+ # Otherwise, download and unzip
+ # The tempfile is set as ``hdf5.gz`` so that the mock download in the webapi tests works
+ tmp_file, tmp_file_path_str = tempfile.mkstemp(".hdf5.gz")
+ os.close(tmp_file)
+
+ # make the leading directories in the 'to_file', if any
+ to_path = pathlib.Path(to_file)
+ to_path.parent.mkdir(parents=True, exist_ok=True)
+ try:
+ download_file(
+ resource_id,
+ remote_filename,
+ to_file=tmp_file_path_str,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ if os.path.exists(tmp_file_path_str):
+ extract_gzip_file(tmp_file_path_str, to_path)
+ else:
+ raise WebError(f"Failed to download and extract '{remote_filename}'.")
+ finally:
+ os.unlink(tmp_file_path_str)
+ return to_path
diff --git a/tidy3d/plugins/smatrix/web/core/stub.py b/tidy3d/plugins/smatrix/web/core/stub.py
new file mode 100644
index 0000000000..153fd69f22
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/stub.py
@@ -0,0 +1,80 @@
+"""Defines interface that can be subclassed to use with the tidy3d webapi"""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+
+
+class TaskStubData(ABC):
+ @abstractmethod
+ def from_file(self, file_path) -> TaskStubData:
+ """Loads a :class:`TaskStubData` from .yaml, .json, or .hdf5 file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the :class:`Stub` from.
+
+ Returns
+ -------
+ :class:`Stub`
+ An instance of the component class calling ``load``.
+
+ """
+
+ @abstractmethod
+ def to_file(self, file_path):
+ """Loads a :class:`Stub` from .yaml, .json, or .hdf5 file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the :class:`Stub` from.
+
+ Returns
+ -------
+ :class:`Stub`
+ An instance of the component class calling ``load``.
+ """
+
+
+class TaskStub(ABC):
+ @abstractmethod
+ def from_file(self, file_path) -> TaskStub:
+ """Loads a :class:`TaskStubData` from .yaml, .json, or .hdf5 file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the :class:`Stub` from.
+
+ Returns
+ -------
+ :class:`TaskStubData`
+ An instance of the component class calling ``load``.
+ """
+
+ @abstractmethod
+ def to_file(self, file_path):
+ """Loads a :class:`TaskStub` from .yaml, .json, .hdf5 or .hdf5.gz file.
+
+ Parameters
+ ----------
+ file_path : str
+ Full path to the .yaml or .json or .hdf5 file to load the :class:`TaskStub` from.
+
+ Returns
+ -------
+ :class:`Stub`
+ An instance of the component class calling ``load``.
+ """
+
+ @abstractmethod
+ def to_hdf5_gz(self, fname: str) -> None:
+ """Exports :class:`TaskStub` instance to .hdf5.gz file.
+
+ Parameters
+ ----------
+ fname : str
+ Full path to the .hdf5.gz file to save the :class:`TaskStub` to.
+ """
diff --git a/tidy3d/plugins/smatrix/web/core/task_core.py b/tidy3d/plugins/smatrix/web/core/task_core.py
new file mode 100644
index 0000000000..e38a9320fc
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/task_core.py
@@ -0,0 +1,715 @@
+"""Tidy3d webapi types."""
+
+from __future__ import annotations
+
+import os
+import pathlib
+import tempfile
+from datetime import datetime
+from typing import Callable, Optional, Union
+
+import pydantic.v1 as pd
+from botocore.exceptions import ClientError
+from pydantic.v1 import Extra, Field, parse_obj_as
+
+import tidy3d as td
+from tidy3d.exceptions import ValidationError
+
+from . import http_util
+from .cache import FOLDER_CACHE
+from .constants import SIM_ERROR_FILE, SIM_FILE_HDF5_GZ, SIM_LOG_FILE, SIMULATION_DATA_HDF5_GZ
+from .core_config import get_logger_console
+from .environment import Env
+from .exceptions import WebError, WebNotFoundError
+from .file_util import read_simulation_from_hdf5
+from .http_util import http
+from .s3utils import download_file, download_gz_file, upload_file
+from .stub import TaskStub
+from .types import PayType, Queryable, ResourceLifecycle, Submittable, Tidy3DResource
+
+
+class Folder(Tidy3DResource, Queryable, extra=Extra.allow):
+ """Tidy3D Folder."""
+
+ folder_id: str = Field(..., title="Folder id", description="folder id", alias="projectId")
+ folder_name: str = Field(
+ ..., title="Folder name", description="folder name", alias="projectName"
+ )
+
+ @classmethod
+ def list(cls, projects_endpoint: str = "tidy3d/projects") -> []:
+ """List all folders.
+
+ Returns
+ -------
+ folders : [Folder]
+ List of folders
+ """
+ resp = http.get(projects_endpoint)
+ return (
+ parse_obj_as(
+ list[Folder],
+ resp,
+ )
+ if resp
+ else None
+ )
+
+ @classmethod
+ def get(
+ cls,
+ folder_name: str,
+ create: bool = False,
+ projects_endpoint: str = "tidy3d/projects",
+ project_endpoint: str = "tidy3d/project",
+ ):
+ """Get folder by name.
+
+ Parameters
+ ----------
+ folder_name : str
+ Name of the folder.
+ create : str
+ If the folder doesn't exist, create it.
+
+ Returns
+ -------
+ folder : Folder
+ """
+ folder = FOLDER_CACHE.get(folder_name)
+ if not folder:
+ resp = http.get(project_endpoint, params={"projectName": folder_name})
+ if resp:
+ folder = Folder(**resp)
+ if create and not folder:
+ resp = http.post(projects_endpoint, {"projectName": folder_name})
+ if resp:
+ folder = Folder(**resp)
+ FOLDER_CACHE[folder_name] = folder
+ return folder
+
+ @classmethod
+ def create(cls, folder_name: str):
+ """Create a folder, return existing folder if there is one has the same name.
+
+ Parameters
+ ----------
+ folder_name : str
+ Name of the folder.
+
+ Returns
+ -------
+ folder : Folder
+ """
+ return Folder.get(folder_name, True)
+
+ def delete(self, projects_endpoint: str = "tidy3d/projects"):
+ """Remove this folder."""
+
+ http.delete(f"{projects_endpoint}/{self.folder_id}")
+
+ def delete_old(self, days_old: int) -> int:
+ """Remove folder contents older than ``days_old``."""
+
+ return http.delete(
+ f"tidy3d/tasks/{self.folder_id}/tasks",
+ params={"daysOld": days_old},
+ )
+
+ def list_tasks(self, projects_endpoint: str = "tidy3d/projects") -> list[Tidy3DResource]:
+ """List all tasks in this folder.
+
+ Returns
+ -------
+ tasks : List[:class:`.SimulationTask`]
+ List of tasks in this folder
+ """
+ resp = http.get(f"{projects_endpoint}/{self.folder_id}/tasks")
+ return (
+ parse_obj_as(
+ list[SimulationTask],
+ resp,
+ )
+ if resp
+ else None
+ )
+
+
+class SimulationTask(ResourceLifecycle, Submittable, extra=Extra.allow):
+ """Interface for managing the running of a :class:`.Simulation` task on server."""
+
+ task_id: Optional[str] = Field(
+ ...,
+ title="task_id",
+ description="Task ID number, set when the task is uploaded, leave as None.",
+ alias="taskId",
+ )
+ folder_id: Optional[str] = Field(
+ None,
+ title="folder_id",
+ description="Folder ID number, set when the task is uploaded, leave as None.",
+ alias="folderId",
+ )
+ status: Optional[str] = Field(title="status", description="Simulation task status.")
+
+ real_flex_unit: float = Field(
+ None, title="real FlexCredits", description="Billed FlexCredits.", alias="realCost"
+ )
+
+ created_at: Optional[datetime] = Field(
+ title="created_at", description="Time at which this task was created.", alias="createdAt"
+ )
+
+ task_type: Optional[str] = Field(
+ title="task_type", description="The type of task.", alias="taskType"
+ )
+
+ folder_name: Optional[str] = Field(
+ "default",
+ title="Folder Name",
+ description="Name of the folder associated with this task.",
+ alias="folderName",
+ )
+
+ callback_url: str = Field(
+ None,
+ title="Callback URL",
+ description="Http PUT url to receive simulation finish event. "
+ "The body content is a json file with fields "
+ "``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
+ )
+
+ # simulation_type: str = pd.Field(
+ # None,
+ # title="Simulation Type",
+ # description="Type of simulation, used internally only.",
+ # )
+
+ # parent_tasks: Tuple[TaskId, ...] = pd.Field(
+ # None,
+ # title="Parent Tasks",
+ # description="List of parent task ids for the simulation, used internally only."
+ # )
+
+ @pd.root_validator(pre=True)
+ def _error_if_jax_sim(cls, values):
+ """Raise error if user tries to submit simulation that's a JaxSimulation."""
+ sim = values.get("simulation")
+ if sim is None:
+ return values
+ if "JaxSimulation" in str(type(sim)):
+ raise ValueError(
+ "'JaxSimulation' not compatible with regular webapi functions. "
+ "Either convert it to Simulation with 'jax_sim.to_simulation()[0]' or use "
+ "the 'adjoint.run' function to run JaxSimulations."
+ )
+ return values
+
+ @classmethod
+ def create(
+ cls,
+ task_type: str,
+ task_name: str,
+ folder_name: str = "default",
+ callback_url: Optional[str] = None,
+ simulation_type: str = "tidy3d",
+ parent_tasks: Optional[list[str]] = None,
+ file_type: str = "Gz",
+ projects_endpoint: str = "tidy3d/projects",
+ ) -> SimulationTask:
+ """Create a new task on the server.
+
+ Parameters
+ ----------
+ task_type: :class".TaskType"
+ The type of task.
+ task_name: str
+ The name of the task.
+ folder_name: str,
+ The name of the folder to store the task. Default is "default".
+ callback_url: str
+ Http PUT url to receive simulation finish event. The body content is a json file with
+ fields ``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.
+ simulation_type : str
+ Type of simulation being uploaded.
+ parent_tasks : List[str]
+ List of related task ids.
+ file_type: str
+ the simulation file type Json, Hdf5, Gz
+
+ Returns
+ -------
+ :class:`SimulationTask`
+ :class:`SimulationTask` object containing info about status, size,
+ credits of task and others.
+ """
+
+ # handle backwards compatibility, "tidy3d" is the default simulation_type
+ if simulation_type is None:
+ simulation_type = "tidy3d"
+
+ folder = Folder.get(folder_name, create=True)
+ resp = http.post(
+ f"{projects_endpoint}/{folder.folder_id}/tasks",
+ {
+ "taskName": task_name,
+ "taskType": task_type,
+ "callbackUrl": callback_url,
+ "simulationType": simulation_type,
+ "parentTasks": parent_tasks,
+ "fileType": file_type,
+ },
+ )
+ return SimulationTask(**resp, taskType=task_type, folder_name=folder_name)
+
+ @classmethod
+ def get(cls, task_id: str, verbose: bool = True) -> SimulationTask:
+ """Get task from the server by id.
+
+ Parameters
+ ----------
+ task_id: str
+ Unique identifier of task on server.
+ verbose:
+ If `True`, will print progressbars and status, otherwise, will run silently.
+
+ Returns
+ -------
+ :class:`.SimulationTask`
+ :class:`.SimulationTask` object containing info about status,
+ size, credits of task and others.
+ """
+ try:
+ resp = http.get(f"tidy3d/tasks/{task_id}/detail")
+ except WebNotFoundError as e:
+ td.log.error(f"The requested task ID '{task_id}' does not exist.")
+ raise e
+
+ task = SimulationTask(**resp) if resp else None
+ return task
+
+ @classmethod
+ def get_running_tasks(cls) -> list[SimulationTask]:
+ """Get a list of running tasks from the server"
+
+ Returns
+ -------
+ List[:class:`.SimulationTask`]
+ :class:`.SimulationTask` object containing info about status,
+ size, credits of task and others.
+ """
+ resp = http.get("tidy3d/py/tasks")
+ if not resp:
+ return []
+ return parse_obj_as(list[SimulationTask], resp)
+
+ def delete(self, versions: bool = False):
+ """Delete current task from server.
+
+ Parameters
+ ----------
+ versions : bool = False
+ If ``True``, delete all versions of the task in the task group. Otherwise, delete only the version associated with the current task ID.
+ """
+ if not self.task_id:
+ raise ValueError("Task id not found.")
+
+ task_details = http.get(f"tidy3d/tasks/{self.task_id}")
+
+ if task_details and "groupId" in task_details and "version" in task_details:
+ group_id = task_details["groupId"]
+ version = task_details["version"]
+ if versions:
+ http.delete("tidy3d/group", json={"groupIds": [group_id]})
+ else:
+ http.delete(f"tidy3d/group/{group_id}/versions", json={"versions": [version]})
+ else: # Fallback to old method if we can't get the groupId and version
+ http.delete(f"tidy3d/tasks/{self.task_id}")
+
+ def get_simulation_json(self, to_file: str, verbose: bool = True) -> pathlib.Path:
+ """Get json file for a :class:`.Simulation` from server.
+
+ Parameters
+ ----------
+ to_file: str
+ Save file to path.
+ verbose: bool = True
+ Whether to display progress bars.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ hdf5_file, hdf5_file_path = tempfile.mkstemp(".hdf5")
+ os.close(hdf5_file)
+ try:
+ self.get_simulation_hdf5(hdf5_file_path)
+ if os.path.exists(hdf5_file_path):
+ json_string = read_simulation_from_hdf5(hdf5_file_path)
+ with open(to_file, "w") as file:
+ # Write the string to the file
+ file.write(json_string.decode("utf-8"))
+ if verbose:
+ console = get_logger_console()
+ console.log(f"Generate {to_file} successfully.")
+ else:
+ raise WebError("Failed to download simulation.json.")
+ finally:
+ os.unlink(hdf5_file_path)
+
+ def upload_simulation(
+ self,
+ stub: TaskStub,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ remote_sim_file: str = SIM_FILE_HDF5_GZ,
+ ) -> None:
+ """Upload :class:`.Simulation` object to Server.
+
+ Parameters
+ ----------
+ stub: :class:`TaskStub`
+ An instance of TaskStub.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while uploading the data.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+ if not stub:
+ raise WebError("Expected field 'simulation' is unset.")
+ # Also upload hdf5.gz containing all data.
+ file, file_name = tempfile.mkstemp()
+ os.close(file)
+ try:
+ # upload simulation
+ # compress .hdf5 to .hdf5.gz
+ stub.to_hdf5_gz(file_name)
+ upload_file(
+ self.task_id,
+ file_name,
+ remote_sim_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ finally:
+ os.unlink(file_name)
+
+ def upload_file(
+ self,
+ local_file: str,
+ remote_filename: str,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> None:
+ """
+ Upload file to platform. Using this method when the json file is too large to parse
+ as :class".simulation".
+ Parameters
+ ----------
+ local_file: str
+ local file path.
+ remote_filename: str
+ file name on the server
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while uploading the data.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ upload_file(
+ self.task_id,
+ local_file,
+ remote_filename,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+
+ def submit(
+ self,
+ solver_version: Optional[str] = None,
+ worker_group: Optional[str] = None,
+ pay_type: Union[PayType, str] = PayType.AUTO,
+ priority: Optional[int] = None,
+ ):
+ """Kick off this task.
+
+ It will be uploaded to server before
+ starting the task. Otherwise, this method assumes that the Simulation has been uploaded by
+ the upload_file function, so the task will be kicked off directly.
+
+ Parameters
+ ----------
+ solver_version: str = None
+ target solver version.
+ worker_group: str = None
+ worker group
+ pay_type: Union[PayType, str] = PayType.AUTO
+ Which method to pay the simulation.
+ priority: int = None
+ Task priority for vGPU queue (1=lowest, 10=highest).
+ """
+ pay_type = PayType(pay_type) if not isinstance(pay_type, PayType) else pay_type
+
+ if solver_version:
+ protocol_version = None
+ else:
+ protocol_version = http_util.get_version()
+
+ http.post(
+ f"tidy3d/tasks/{self.task_id}/submit",
+ {
+ "solverVersion": solver_version,
+ "workerGroup": worker_group,
+ "protocolVersion": protocol_version,
+ "enableCaching": Env.current.enable_caching,
+ "payType": pay_type.value,
+ "priority": priority,
+ },
+ )
+
+ def estimate_cost(self, solver_version=None) -> float:
+ """Compute the maximum flex unit charge for a given task, assuming the simulation runs for
+ the full ``run_time``. If early shut-off is triggered, the cost is adjusted proportionately.
+
+ Parameters
+ ----------
+ solver_version: str
+ target solver version.
+
+ Returns
+ -------
+ flex_unit_cost: float
+ estimated cost in FlexCredits
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ if solver_version:
+ protocol_version = None
+ else:
+ protocol_version = http_util.get_version()
+
+ resp = http.post(
+ f"tidy3d/tasks/{self.task_id}/metadata",
+ {
+ "solverVersion": solver_version,
+ "protocolVersion": protocol_version,
+ },
+ )
+ return resp
+
+ def get_sim_data_hdf5(
+ self,
+ to_file: str,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ remote_data_file: str = SIMULATION_DATA_HDF5_GZ,
+ ) -> pathlib.Path:
+ """Get simulation data file from Server.
+
+ Parameters
+ ----------
+ to_file: str
+ Save file to path.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ file = None
+ try:
+ file = download_gz_file(
+ resource_id=self.task_id,
+ remote_filename=remote_data_file,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ except ClientError:
+ if verbose:
+ console = get_logger_console()
+ console.log(f"Unable to download '{remote_data_file}'.")
+
+ if not file:
+ try:
+ file = download_file(
+ resource_id=self.task_id,
+ remote_filename=remote_data_file[:-3],
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+ except Exception as e:
+ raise WebError(
+ "Failed to download the simulation data file from the server. "
+ "Please confirm that the task was successfully run."
+ ) from e
+
+ return file
+
+ def get_simulation_hdf5(
+ self,
+ to_file: str,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ remote_sim_file: str = SIM_FILE_HDF5_GZ,
+ ) -> pathlib.Path:
+ """Get simulation.hdf5 file from Server.
+
+ Parameters
+ ----------
+ to_file: str
+ Save file to path.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ return download_gz_file(
+ resource_id=self.task_id,
+ remote_filename=remote_sim_file,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+
+ def get_running_info(self) -> tuple[float, float]:
+ """Gets the % done and field_decay for a running task.
+
+ Returns
+ -------
+ perc_done : float
+ Percentage of run done (in terms of max number of time steps).
+ Is ``None`` if run info not available.
+ field_decay : float
+ Average field intensity normalized to max value (1.0).
+ Is ``None`` if run info not available.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ resp = http.get(f"tidy3d/tasks/{self.task_id}/progress")
+ perc_done = resp.get("perc_done")
+ field_decay = resp.get("field_decay")
+ return perc_done, field_decay
+
+ def get_log(
+ self,
+ to_file: str,
+ verbose: bool = True,
+ progress_callback: Optional[Callable[[float], None]] = None,
+ ) -> pathlib.Path:
+ """Get log file from Server.
+
+ Parameters
+ ----------
+ to_file: str
+ Save file to path.
+ verbose: bool = True
+ Whether to display progress bars.
+ progress_callback : Callable[[float], None] = None
+ Optional callback function called while downloading the data.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ return download_file(
+ self.task_id,
+ SIM_LOG_FILE,
+ to_file=to_file,
+ verbose=verbose,
+ progress_callback=progress_callback,
+ )
+
+ def get_error_json(self, to_file: str, verbose: bool = True) -> pathlib.Path:
+ """Get error json file for a :class:`.Simulation` from server.
+
+ Parameters
+ ----------
+ to_file: str
+ Save file to path.
+ verbose: bool = True
+ Whether to display progress bars.
+
+ Returns
+ -------
+ path: pathlib.Path
+ Path to saved file.
+ """
+ if not self.task_id:
+ raise WebError("Expected field 'task_id' is unset.")
+
+ return download_file(
+ self.task_id,
+ SIM_ERROR_FILE,
+ to_file=to_file,
+ verbose=verbose,
+ )
+
+ def abort(self):
+ """Abort current task from server."""
+ if not self.task_id:
+ raise ValueError("Task id not found.")
+ return http.put(
+ "tidy3d/tasks/abort", json={"taskType": self.task_type, "taskId": self.task_id}
+ )
+
+ def validate_post_upload(self, parent_tasks: Optional[list[str]] = None):
+ """Perform checks after task is uploaded and metadata is processed."""
+ if self.task_type == "HEAT_CHARGE" and parent_tasks:
+ try:
+ if len(parent_tasks) > 1:
+ raise ValueError(
+ "A single parent 'task_id' corresponding to the task in which the meshing "
+ "was run must be provided."
+ )
+ try:
+ # get mesh task info
+ mesh_task = SimulationTask.get(parent_tasks[0], verbose=False)
+ assert mesh_task.task_type == "VOLUME_MESH"
+ assert mesh_task.status == "success"
+ # get up-to-date task info
+ task = SimulationTask.get(self.task_id, verbose=False)
+ if task.fileMd5 != mesh_task.childFileMd5:
+ raise ValidationError(
+ "Simulation stored in parent task 'VolumeMesher' does not match the "
+ "current simulation."
+ )
+ except Exception as e:
+ raise ValidationError(
+ "The parent task must be a 'VolumeMesher' task which has been successfully "
+ "run and is associated to the same 'HeatChargeSimulation' as provided here."
+ ) from e
+
+ except Exception as e:
+ raise WebError(f"Provided 'parent_tasks' failed validation: {e!s}") from e
diff --git a/tidy3d/plugins/smatrix/web/core/task_info.py b/tidy3d/plugins/smatrix/web/core/task_info.py
new file mode 100644
index 0000000000..3406c62b3a
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/task_info.py
@@ -0,0 +1,169 @@
+"""Defines information about a task"""
+
+from __future__ import annotations
+
+from abc import ABC
+from datetime import datetime
+from enum import Enum
+from typing import Optional
+
+import pydantic.v1 as pydantic
+
+
+class TaskStatus(Enum):
+ """The statuses that the task can be in."""
+
+ INIT = "initialized"
+ """The task has been initialized."""
+
+ QUEUE = "queued"
+ """The task is in the queue."""
+
+ PRE = "preprocessing"
+ """The task is in the preprocessing stage."""
+
+ RUN = "running"
+ """The task is running."""
+
+ POST = "postprocessing"
+ """The task is in the postprocessing stage."""
+
+ SUCCESS = "success"
+ """The task has completed successfully."""
+
+ ERROR = "error"
+ """The task has completed with an error."""
+
+
+class TaskBase(pydantic.BaseModel, ABC):
+ """Base configuration for all task objects."""
+
+ class Config:
+ """Configuration for TaskBase"""
+
+ arbitrary_types_allowed = True
+ """Allow arbitrary types to be used within the model."""
+
+
+class ChargeType(str, Enum):
+ """The payment method of the task."""
+
+ FREE = "free"
+ """No payment required."""
+
+ PAID = "paid"
+ """Payment required."""
+
+
+class TaskBlockInfo(TaskBase):
+ """Information about the task's block status.
+
+ This includes details about how the task can be blocked by various features
+ such as user limits and insufficient balance.
+ """
+
+ chargeType: ChargeType = None
+ """The type of charge applicable to the task (free or paid)."""
+
+ maxFreeCount: int = None
+ """The maximum number of free tasks allowed."""
+
+ maxGridPoints: int = None
+ """The maximum number of grid points permitted."""
+
+ maxTimeSteps: int = None
+ """The maximum number of time steps allowed."""
+
+
+class TaskInfo(TaskBase):
+ """General information about a task."""
+
+ taskId: str
+ """Unique identifier for the task."""
+
+ taskName: str = None
+ """Name of the task."""
+
+ nodeSize: int = None
+ """Size of the node allocated for the task."""
+
+ completedAt: Optional[datetime] = None
+ """Timestamp when the task was completed."""
+
+ status: str = None
+ """Current status of the task."""
+
+ realCost: float = None
+ """Actual cost incurred by the task."""
+
+ timeSteps: int = None
+ """Number of time steps involved in the task."""
+
+ solverVersion: str = None
+ """Version of the solver used for the task."""
+
+ createAt: Optional[datetime] = None
+ """Timestamp when the task was created."""
+
+ estCostMin: float = None
+ """Estimated minimum cost for the task."""
+
+ estCostMax: float = None
+ """Estimated maximum cost for the task."""
+
+ realFlexUnit: float = None
+ """Actual flexible units used by the task."""
+
+ oriRealFlexUnit: float = None
+ """Original real flexible units."""
+
+ estFlexUnit: float = None
+ """Estimated flexible units for the task."""
+
+ estFlexCreditTimeStepping: float = None
+ """Estimated flexible credits for time stepping."""
+
+ estFlexCreditPostProcess: float = None
+ """Estimated flexible credits for post-processing."""
+
+ estFlexCreditMode: float = None
+ """Estimated flexible credits based on the mode."""
+
+ s3Storage: float = None
+ """Amount of S3 storage used by the task."""
+
+ startSolverTime: Optional[datetime] = None
+ """Timestamp when the solver started."""
+
+ finishSolverTime: Optional[datetime] = None
+ """Timestamp when the solver finished."""
+
+ totalSolverTime: int = None
+ """Total time taken by the solver."""
+
+ callbackUrl: str = None
+ """Callback URL for task notifications."""
+
+ taskType: str = None
+ """Type of the task."""
+
+ metadataStatus: str = None
+ """Status of the metadata for the task."""
+
+ taskBlockInfo: TaskBlockInfo = None
+ """Blocking information for the task."""
+
+
+class RunInfo(TaskBase):
+ """Information about the run of a task."""
+
+ perc_done: pydantic.confloat(ge=0.0, le=100.0)
+ """Percentage of the task that is completed (0 to 100)."""
+
+ field_decay: pydantic.confloat(ge=0.0, le=1.0)
+ """Field decay from the maximum value (0 to 1)."""
+
+ def display(self):
+ """Print some info about the task's progress."""
+ print(f" - {self.perc_done:.2f} (%) done")
+ print(f" - {self.field_decay:.2e} field decay from max")
diff --git a/tidy3d/plugins/smatrix/web/core/types.py b/tidy3d/plugins/smatrix/web/core/types.py
new file mode 100644
index 0000000000..a3f7bb9519
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/core/types.py
@@ -0,0 +1,72 @@
+"""Tidy3d abstraction types for the core."""
+
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from enum import Enum
+
+from pydantic.v1 import BaseModel
+
+
+class Tidy3DResource(BaseModel, ABC):
+ """Abstract base class / template for a webservice that implements resource query."""
+
+ @classmethod
+ @abstractmethod
+ def get(cls, *args, **kwargs) -> Tidy3DResource:
+ """Get a resource from the server."""
+
+
+class ResourceLifecycle(Tidy3DResource, ABC):
+ """Abstract base class for a webservice that implements resource life cycle management."""
+
+ @classmethod
+ @abstractmethod
+ def create(cls, *args, **kwargs) -> Tidy3DResource:
+ """Create a new resource and return it."""
+
+ @abstractmethod
+ def delete(self, *args, **kwargs) -> None:
+ """Delete the resource."""
+
+
+class Submittable(BaseModel, ABC):
+ """Abstract base class / template for a webservice that implements a submit method."""
+
+ @abstractmethod
+ def submit(self, *args, **kwargs) -> None:
+ """Submit the task to the webservice."""
+
+
+class Queryable(BaseModel, ABC):
+ """Abstract base class / template for a webservice that implements a query method."""
+
+ @classmethod
+ @abstractmethod
+ def list(cls, *args, **kwargs) -> [Queryable]:
+ """List all resources of this type."""
+
+
+class TaskType(str, Enum):
+ FDTD = "FDTD"
+ MODE_SOLVER = "MODE_SOLVER"
+ HEAT = "HEAT"
+ HEAT_CHARGE = "HEAT_CHARGE"
+ EME = "EME"
+ MODE = "MODE"
+ VOLUME_MESH = "VOLUME_MESH"
+ COMPONENT_MODELER = "COMPONENT_MODELER"
+ TERMINAL_COMPONENT_MODELER = "TERMINAL_COMPONENT_MODELER"
+
+
+class PayType(str, Enum):
+ CREDITS = "FLEX_CREDIT"
+ AUTO = "AUTO"
+
+ @classmethod
+ def _missing_(cls, value: object) -> PayType:
+ if isinstance(value, str):
+ key = value.strip().replace(" ", "_").upper()
+ if key in cls.__members__:
+ return cls.__members__[key]
+ return super()._missing_(value)
diff --git a/tidy3d/plugins/smatrix/web/environment.py b/tidy3d/plugins/smatrix/web/environment.py
new file mode 100644
index 0000000000..af4b089a96
--- /dev/null
+++ b/tidy3d/plugins/smatrix/web/environment.py
@@ -0,0 +1,7 @@
+"""preserve from tidy3d.web.environment import Env backward compatibility"""
+
+from __future__ import annotations
+
+from .core.environment import Env
+
+__all__ = ["Env"]
diff --git a/tidy3d/web/api/container.py b/tidy3d/web/api/container.py
index fb5b1915bb..6326a30304 100644
--- a/tidy3d/web/api/container.py
+++ b/tidy3d/web/api/container.py
@@ -32,6 +32,8 @@
DEFAULT_DATA_DIR = "."
BATCH_MONITOR_PROGRESS_REFRESH_TIME = 0.02
+BatchCategoryType = Literal["tidy3d", "microwave", "tidy3d_design"]
+
class WebContainer(Tidy3dBaseModel, ABC):
"""Base class for :class:`Job` and :class:`Batch`, technically not used"""
@@ -44,9 +46,18 @@ def _check_path_dir(path: str) -> None:
"""Make sure local output directory exists and create it if not."""
@staticmethod
- def _check_folder(folder_name: str) -> None:
+ def _check_folder(
+ folder_name: str,
+ projects_endpoint: str = "tidy3d/projects",
+ project_endpoint: str = "tidy3d/project",
+ ) -> None:
"""Make sure ``folder_name`` exists on the web UI and create it if not."""
- Folder.get(folder_name, create=True)
+ Folder.get(
+ folder_name,
+ create=True,
+ projects_endpoint=projects_endpoint,
+ project_endpoint=project_endpoint,
+ )
class Job(WebContainer):
@@ -158,7 +169,7 @@ class Job(WebContainer):
True, title="Verbose", description="Whether to print info messages and progressbars."
)
- simulation_type: str = pd.Field(
+ simulation_type: BatchCategoryType = pd.Field(
"tidy3d",
title="Simulation Type",
description="Type of simulation, used internally only.",
@@ -527,7 +538,7 @@ class Batch(WebContainer):
"``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",
)
- simulation_type: str = pd.Field(
+ simulation_type: BatchCategoryType = pd.Field(
"tidy3d",
title="Simulation Type",
description="Type of each simulation in the batch, used internally only.",
diff --git a/tidy3d/web/api/tidy3d_stub.py b/tidy3d/web/api/tidy3d_stub.py
index c5a6c94228..f66f9da53d 100644
--- a/tidy3d/web/api/tidy3d_stub.py
+++ b/tidy3d/web/api/tidy3d_stub.py
@@ -26,6 +26,12 @@
from tidy3d.components.tcad.simulation.heat import HeatSimulation
from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation
from tidy3d.plugins.mode.mode_solver import ModeSolver
+from tidy3d.plugins.smatrix import (
+ ComponentModeler,
+ ComponentModelerData,
+ TerminalComponentModeler,
+ TerminalComponentModelerData,
+)
from tidy3d.web.core.file_util import (
read_simulation_from_hdf5,
read_simulation_from_hdf5_gz,
@@ -42,6 +48,8 @@
ModeSolver,
ModeSimulation,
VolumeMesher,
+ ComponentModeler,
+ TerminalComponentModeler,
]
SimulationDataType = Union[
SimulationData,
@@ -50,6 +58,8 @@
EMESimulationData,
ModeSolverData,
ModeSimulationData,
+ ComponentModelerData,
+ TerminalComponentModelerData,
]
@@ -100,6 +110,10 @@ def from_file(cls, file_path: str) -> SimulationType:
sim = ModeSimulation.from_file(file_path)
elif type_ == "VolumeMesher":
sim = VolumeMesher.from_file(file_path)
+ elif type_ == "ComponentModeler":
+ sim = ComponentModeler.from_file(file_path)
+ elif type_ == "TerminalComponentModeler":
+ sim = TerminalComponentModeler.from_file(file_path)
return sim
@@ -162,6 +176,10 @@ def get_type(self) -> str:
return TaskType.MODE.name
elif isinstance(self.simulation, VolumeMesher):
return TaskType.VOLUME_MESH.name
+ elif isinstance(self.simulation, ComponentModeler):
+ return TaskType.COMPONENT_MODELER.name
+ elif isinstance(self.simulation, TerminalComponentModeler):
+ return TaskType.TERMINAL_COMPONENT_MODELER.name
def validate_pre_upload(self, source_required) -> None:
"""Perform some pre-checks on instances of component"""
@@ -216,6 +234,10 @@ def from_file(cls, file_path: str) -> SimulationDataType:
sim_data = ModeSimulationData.from_file(file_path)
elif type_ == "VolumeMesherData":
sim_data = VolumeMesherData.from_file(file_path)
+ elif type_ == "ComponentModelerData":
+ sim_data = ComponentModelerData.from_file(file_path)
+ elif type_ == "TerminalComponentModelerData":
+ sim_data = TerminalComponentModelerData.from_file(file_path)
return sim_data
diff --git a/tidy3d/web/core/task_core.py b/tidy3d/web/core/task_core.py
index 376aef301b..e38a9320fc 100644
--- a/tidy3d/web/core/task_core.py
+++ b/tidy3d/web/core/task_core.py
@@ -37,7 +37,7 @@ class Folder(Tidy3DResource, Queryable, extra=Extra.allow):
)
@classmethod
- def list(cls) -> []:
+ def list(cls, projects_endpoint: str = "tidy3d/projects") -> []:
"""List all folders.
Returns
@@ -45,7 +45,7 @@ def list(cls) -> []:
folders : [Folder]
List of folders
"""
- resp = http.get("tidy3d/projects")
+ resp = http.get(projects_endpoint)
return (
parse_obj_as(
list[Folder],
@@ -56,7 +56,13 @@ def list(cls) -> []:
)
@classmethod
- def get(cls, folder_name: str, create: bool = False):
+ def get(
+ cls,
+ folder_name: str,
+ create: bool = False,
+ projects_endpoint: str = "tidy3d/projects",
+ project_endpoint: str = "tidy3d/project",
+ ):
"""Get folder by name.
Parameters
@@ -72,11 +78,11 @@ def get(cls, folder_name: str, create: bool = False):
"""
folder = FOLDER_CACHE.get(folder_name)
if not folder:
- resp = http.get("tidy3d/project", params={"projectName": folder_name})
+ resp = http.get(project_endpoint, params={"projectName": folder_name})
if resp:
folder = Folder(**resp)
if create and not folder:
- resp = http.post("tidy3d/projects", {"projectName": folder_name})
+ resp = http.post(projects_endpoint, {"projectName": folder_name})
if resp:
folder = Folder(**resp)
FOLDER_CACHE[folder_name] = folder
@@ -97,10 +103,10 @@ def create(cls, folder_name: str):
"""
return Folder.get(folder_name, True)
- def delete(self):
+ def delete(self, projects_endpoint: str = "tidy3d/projects"):
"""Remove this folder."""
- http.delete(f"tidy3d/projects/{self.folder_id}")
+ http.delete(f"{projects_endpoint}/{self.folder_id}")
def delete_old(self, days_old: int) -> int:
"""Remove folder contents older than ``days_old``."""
@@ -110,7 +116,7 @@ def delete_old(self, days_old: int) -> int:
params={"daysOld": days_old},
)
- def list_tasks(self) -> list[Tidy3DResource]:
+ def list_tasks(self, projects_endpoint: str = "tidy3d/projects") -> list[Tidy3DResource]:
"""List all tasks in this folder.
Returns
@@ -118,7 +124,7 @@ def list_tasks(self) -> list[Tidy3DResource]:
tasks : List[:class:`.SimulationTask`]
List of tasks in this folder
"""
- resp = http.get(f"tidy3d/projects/{self.folder_id}/tasks")
+ resp = http.get(f"{projects_endpoint}/{self.folder_id}/tasks")
return (
parse_obj_as(
list[SimulationTask],
@@ -209,6 +215,7 @@ def create(
simulation_type: str = "tidy3d",
parent_tasks: Optional[list[str]] = None,
file_type: str = "Gz",
+ projects_endpoint: str = "tidy3d/projects",
) -> SimulationTask:
"""Create a new task on the server.
@@ -243,7 +250,7 @@ def create(
folder = Folder.get(folder_name, create=True)
resp = http.post(
- f"tidy3d/projects/{folder.folder_id}/tasks",
+ f"{projects_endpoint}/{folder.folder_id}/tasks",
{
"taskName": task_name,
"taskType": task_type,
diff --git a/tidy3d/web/core/types.py b/tidy3d/web/core/types.py
index 2ecf066402..a3f7bb9519 100644
--- a/tidy3d/web/core/types.py
+++ b/tidy3d/web/core/types.py
@@ -55,6 +55,8 @@ class TaskType(str, Enum):
EME = "EME"
MODE = "MODE"
VOLUME_MESH = "VOLUME_MESH"
+ COMPONENT_MODELER = "COMPONENT_MODELER"
+ TERMINAL_COMPONENT_MODELER = "TERMINAL_COMPONENT_MODELER"
class PayType(str, Enum):