From 023acd568cf0da182792ce2886c0ff30716a4c2e Mon Sep 17 00:00:00 2001 From: duarte-jfs Date: Thu, 23 May 2024 16:39:35 +0200 Subject: [PATCH 01/12] fixed mesh_from_OrderedDict - issue #150 --- femwell/mesh/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index 5f880c05..9011cef8 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -73,7 +73,7 @@ def mesh_from_Dict( rings = [ LineString(list(object.exterior.coords)) for object in listpoly - if not (object.geom_type in ["Point", "LineString"] or object.is_empty) + if not (object.geom_type in ["Point", "LineString", "MultiLineString"] or object.is_empty) ] union = unary_union(rings) From bd2a6bd8b8da94637bf2504f50f35fb9dbb90086 Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Thu, 23 May 2024 18:08:40 +0200 Subject: [PATCH 02/12] fixed mesh generation on linemerge by point --- femwell/mesh/mesh.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index 9011cef8..46ffc6af 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -14,7 +14,7 @@ Point, Polygon, ) -from shapely.ops import linemerge, polygonize, split, unary_union +from shapely.ops import linemerge, polygonize, split, unary_union, snap from femwell.mesh.meshtracker import MeshTracker @@ -31,7 +31,7 @@ def break_line_(line, other_line): ): # if type == "", intersection.type != 'Point': if intersection.geom_type == "Point": - line = linemerge(split(line, intersection)) + line = snap(line, intersection, 0.1) else: new_coords_start, new_coords_end = intersection.boundary.geoms line = linemerge(split(line, new_coords_start)) From 6d211ad0e834e617a8608620a5442194693be8b6 Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 21:05:41 +0200 Subject: [PATCH 03/12] added tutorial on CPW for RF design --- .../RF CPW transmission line tutorial.py | 1634 +++++++++++++++++ .../support/ImZ0_delft.txt | 154 ++ .../support/ReK_delft.txt | 154 ++ .../support/ReZ0_delft.txt | 154 ++ .../support/freq_delft.txt | 154 ++ .../support/loss_delft.txt | 154 ++ 6 files changed, 2404 insertions(+) create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt create mode 100644 docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py b/docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py new file mode 100644 index 00000000..30c0f99c --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py @@ -0,0 +1,1634 @@ +# --- +# jupyter: +# jupytext: +# formats: ipynb,py:percent +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.16.2 +# kernelspec: +# display_name: Python 3 (ipykernel) +# language: python +# name: python3 +# --- + +# %% +from collections import OrderedDict + +from matplotlib import pyplot as plt +from matplotlib.gridspec import GridSpec +import numpy as np + +from shapely.ops import unary_union, clip_by_rect, linemerge +from shapely.geometry import box, LineString, MultiLineString +from skfem import Basis, ElementTriP0 +from skfem.io.meshio import from_meshio + +from femwell.maxwell.waveguide import compute_modes, calculate_overlap, calculate_scalar_product +from femwell.mesh import mesh_from_OrderedDict +from femwell.visualization import plot_domains + +from skfem import Functional +from skfem.helpers import inner + +from pint import UnitRegistry + +import enlighten + +import time +# %matplotlib inline +# To make things go smoother I advise to use %matplotlib widget so that you can inspect the mesh and other figures more clearly + +# %% [markdown] +# In this notebook we aim to study a simple CPW structure by repicating the measurements from [1] as in the image below. We wish to retrieve data from the microwave index and attenuation. This case is a good example of when the losses inside the metal will contribute greatly to the losses and, therefore, new conformal techniques were required to properly model the CPW. In our case, we will use FEMWELL to achieve the same results and confirm the theory and benchmark the software with the measurements. +# +#
+# +#
+# +# Furthermore, this notebook will also provide insight on how to use it for the design of RF waveguides, by exploring the fine line between waveguide and circuit theories [4, 5]. +# +# ## References +# +# [1] E. Tuncer, Beom-Taek Lee, M. S. Islam, and D. P. Neikirk, “Quasi-static conductor loss calculations in transmission lines using a new conformal mapping technique,” IEEE Trans. Microwave Theory Techn., vol. 42, no. 9, pp. 1807–1815, Sep. 1994, doi: 10.1109/22.310592. +# +# [2] G. W. Slade and K. J. Webb, “Computation of characteristic impedance for multiple microstrip transmission lines using a vector finite element method,” IEEE Trans. Microwave Theory Techn., vol. 40, no. 1, pp. 34–40, Jan. 1992, doi: 10.1109/22.108320. +# +# [3] - S. van Berkel, A. Garufo, A. Endo, N. LLombart, and A. Neto, �Characterization of printed transmission lines at high frequencies,� in The 9th European Conference on Antennas and Propagation (EuCAP 2015), (Lisbon, Portugal), Apr. 2015. (Software available at https://terahertz.tudelft.nl/Research/project.php?id=74&ti=27 ) +# +# [4] R. B. Marks and D. F. Williams, “A general waveguide circuit theory,” J. RES. NATL. INST. STAN., vol. 97, no. 5, p. 533, Sep. 1992, doi: 10.6028/jres.097.024. +# +# [5] D. F. Williams, L. A. Hayden, and R. B. Marks, “A complete multimode equivalent-circuit theory for electrical design,” J. Res. Natl. Inst. Stand. Technol., vol. 102, no. 4, p. 405, Jul. 1997, doi: 10.6028/jres.102.029. +# + +# %% [markdown] +# # Defining geometry and mesh + +# %% [markdown] +# The first step is to define our material parameters and geometry. To avoid any unit mistakes (AND THEY DO HAPPEN), we'll use `pint` to track every unit and conversions. Let us start by defining the frequency range we want to work with (in practice, in the following we will only use one frequency for now, but let's prepare everything to integrate seamlessly in a loop): + +# %% +reg = UnitRegistry() + +#Define frequency range +freq = np.linspace(0.2, 45, 10) * reg.GHz +omega = 2*np.pi*freq + +# %% [markdown] +# Now some universal constants: + +# %% +## Define universal constants +mu0 = 4*np.pi * 1e-7 * reg.henry/reg.meter #vacuum magnetic permeability +e0 = 8.854e-12 * reg.farad*reg.meter**-1 +c = 3e8 * reg.meter*reg.second**-1 #m s^-1 +e=1.602176634e-19 * reg.coulomb #Coulombs +kb=1.380649e-23 *reg.meter**2*reg.kg*reg.second**-2*reg.kelvin**-1 +T=300 * reg.kelvin + +# %% [markdown] +# For the geometry we will follow the parametrization: +# +#
+# +#
+# +# Note that the port width can be smaller than the `w_sig+2*sep+2*w_gnd`. This, however, has to be chosen with a priori knowledge of the field profile. In this case, we know that the supported fields will be tightly confined in the gaps between the ground and signal pads. Therefore, we need to place the simulation boundaries far enough away from that region so as to guarantee that there is no coupling between the CPW mode and the boundary. + +# %% +w_sig = 7 * reg.micrometer +sep = 10 * reg.micrometer +w_gnd = 100 * reg.micrometer +t_metal = 0.8 * reg.micrometer + +port_width = (w_sig+2*sep+2*w_gnd)*0.5+10 * reg.micrometer #um +bottom_height = 100 * reg.micrometer +top_height = 100 * reg.micrometer + + +eps_r_sub = 13 * reg.dimensionless #substrate relative permitivity +sig_metal = 6e5 * reg.siemens/reg.centimeter #Metal conductivity + +# %% [markdown] +# Next we will define the geometry that `skfem` needs to mesh. We also want to leave the option to use a symmetry plane or not. This will be useful to speed up computations and filter even and odd modes out. Therefore, we define 'use_symmetry_plane' and then tell which plane it is. The way we'll do it is to define the full geometry and then cut all the polygons and lines by that plane. +# +# Apart from the geometrical polygons that are included in the image above, we also want to include two additional things: +# +# The first one, is an integration path *just* outside the metal tracks. These will be used to integrate the magnetic field so as to retrieve the current flowing longitudinally in the transmission line. The second one will be two lines that will be used to calculate a voltage integral. Details will follow on the sections below. +# +#
+# +#
+ +# %% +######## Define FEM simulation ########### +use_symmetry_plane = True*0 +symmetry_plane = box(0,-np.inf, np.inf, np.inf) + +near_field_width = 50 * reg.micrometer +near_field_height = 10 * reg.micrometer +########################################## + +metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, + 0, + w_sig.to(reg.micrometer).magnitude/2, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, -(port_width/2).to(reg.micrometer).magnitude), + 0, + (-w_sig/2-sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, + 0, + min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, (port_width/2).to(reg.micrometer).magnitude), + t_metal.to(reg.micrometer).magnitude) + + +air_box = box((-port_width/2).to(reg.micrometer).magnitude, + 0, + (port_width/2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude) + + +substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width/2).to(reg.micrometer).magnitude, + 0) + +surface = unary_union([air_box, substrate_box]) + +##Make a line for a path integral +### Right conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmax_1, (ymax_1+ymin_1)/2), + (xmin_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_right = LineString(points) + +### left conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmin_1, (ymax_1+ymin_1)/2), + (xmax_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_left = LineString(points) + +polygons = OrderedDict( + surface = LineString(surface.exterior), + metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_sig.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + path_integral_right = path_integral_right, + path_integral_left = path_integral_left, + + metal_sig = metal_sig_box, + metal_gnd_left = metal_gnd_left_box, + metal_gnd_right = metal_gnd_right_box, + air = air_box, + substrate = substrate_box + ) + + + +if use_symmetry_plane: + keys_to_pop = [] + for key,poly in polygons.items(): + if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): + poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) + + if type(poly_tmp) == MultiLineString: + polygons[key] = linemerge(poly_tmp) + + elif poly_tmp.is_empty: + keys_to_pop.append(key) + else: + polygons[key] = poly_tmp + elif not poly.intersects(symmetry_plane): + keys_to_pop.append(key) + + for key in keys_to_pop: + polygons.pop(key) + +#Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons['surface'].bounds + +left = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[0], surf_bounds[3])]) + +bottom = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[2], surf_bounds[1])]) + +right = LineString([(surf_bounds[2], surf_bounds[1]), + (surf_bounds[2], surf_bounds[3])]) + +top = LineString([(surf_bounds[0], surf_bounds[3]), + (surf_bounds[2], surf_bounds[3])]) + +polygons['left'] = left +polygons['bottom'] = bottom +polygons['right'] = right +polygons['top'] = top + +polygons.move_to_end('top', last = False) +polygons.move_to_end('right', last = False) +polygons.move_to_end('bottom', last = False) +polygons.move_to_end('left', last = False) + +polygons.pop('surface') + +resolutions = dict( + surface = {'resolution': 100, 'distance': 1}, + metal_sig_interface = {'resolution': 0.1, 'distance': 10}, + metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, + path_integral_right = {'resolution': 0.2, 'distance': 10}, + path_integral_left = {'resolution': 0.2, 'distance': 10}, + metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, + metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + air = {'resolution': 10, 'distance': 0.1}, + substrate = {'resolution': 10, 'distance': 1}, + ) + + + + +# %% [markdown] +# We can now check our polygons: + +# %% +fig = plt.figure() +ax = fig.add_subplot(111) + +for key, polygon in polygons.items(): + if type(polygon) is not LineString: + x,y = polygon.exterior.xy + ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + else: + ax.plot(*polygon.xy) + + +# %% [markdown] +# Notice how in the above we have used the argument `SizeMax` on the metal tracks. We do this so that at a distance of `distance` of the boundary of the polygons a resolution of `SizeMax` is assured. This is important as we expect the field to vary rapidly near the metal. +# +#
+# +#
+# +# Now we mesh it + +# %% +#Mesh it +mesh = from_meshio(mesh_from_OrderedDict(polygons, + resolutions, + default_resolution_max = 100, + verbose = True)) + +print(mesh.nelements) + +# %% +fig = mesh.draw() +fig.axes.set_axis_on() + +# %% [markdown] +# Note that we are using a **very** fine mesh. Normally, if we were using thick metals then surface impedance would suffice and we wouldn't have to mesh the entire surface of the metal. However, in this case, the metal is far too thin and the skin depth of the frequencies we are using is far too big to neglect current flowing inside the metal. Sadly, the profile of $I(x,y)$ inside the metal is rapidly changing, so we need the very fine mesh. +# +# How do we know that it is fine enough? We won't do that here, but for a thorough study we advise to do a sweep on the resolution inside the metal and keep checking the absorption of the modes. Once it converges, you're good. +# +# For now, we'll move on to the definition of the materials. FEMWELL allows the definition of the $\epsilon(x,y)$ such that at each polygon you attribute a material constant as: +# +# $$ +# \epsilon = \epsilon^` +j\epsilon^{``} +# $$ +# +# where, for conductive materials: +# +# $$ +# \epsilon = -\frac{\sigma}{\omega \epsilon_0} +# $$ +# + +# %% +idx_freq = -1 +print(f'Frequency:{freq[idx_freq].magnitude:.2f} GHz') + +basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM +epsilon = basis0.ones(dtype = complex) + +epsilon[basis0.get_dofs(elements = 'air')] = 1 +epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub +epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + +# %% +fig = basis0.plot(epsilon.real, colorbar = True) +fig.axes.set_axis_on() + +fig = basis0.plot(epsilon.imag, colorbar = True) +fig.axes.set_axis_on() + +# %% [markdown] +# Let's compute the modes. Since we have 3 conductors, we expect only 2 modes to be present: an even and an odd mode. So let's just calculate 2 + +# %% +start = time.time() +modes = compute_modes( + basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=2, + metallic_boundaries=False, + ) +stop = time.time() + +modes = modes.sorted(key = lambda mode: mode.n_eff.real) + +print(stop-start) + +# %% +print(modes.n_effs.real) +print(20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude, 'dB/cm') + +# %% [markdown] +# We should now inspect the modes. You can use `modes[i].plot(modes.E)` or even plot a single component with `modes[i].plot_component('E', 'x')`, but we're looking for something more custom, I will export the data onto a rectangular grid and plot a streamplot to visualize the field lines: + +# %% +from skfem import ( + ElementVector, + ElementDG, + ElementTriP1 +) + +# Trying interpolator +Nx = 50 +Ny = 50 +grid_data_E = np.zeros((2, Ny, Nx, 3), dtype = complex) +grid_data_H = np.zeros((2, Ny, Nx, 3), dtype = complex) + +xmin = -40 +xmax = 40 +ymin = -10 +ymax = 10 + +grid_x, grid_y = np.meshgrid(np.linspace(xmin,xmax,Nx), np.linspace(ymin, ymax, Ny)) + +for i, mode in enumerate(modes): + basis = mode.basis + basis_fix = basis.with_element(ElementVector(ElementDG(ElementTriP1()))) + + (et, et_basis), (ez, ez_basis) = basis.split(mode.E) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + + + coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) + + start = time.time() + grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates))).T + + grid_data_E[i] = grid_data.reshape((*grid_x.shape, -1)) + end = time.time() + print(end-start) + + (et, et_basis), (ez, ez_basis) = basis.split(mode.H) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + + coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) + + + grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates))).T + + grid_data_H[i] = grid_data.reshape((*grid_x.shape, -1)) + +# %% +fig = plt.figure(figsize = (7,5)) +gs = GridSpec(2,2) + +ax_even_E = fig.add_subplot(gs[0,0]) +ax_odd_E = fig.add_subplot(gs[0,1]) + +ax_even_H = fig.add_subplot(gs[1,0]) +ax_odd_H = fig.add_subplot(gs[1,1]) + +for ax, data, label in zip([ax_even_E, ax_odd_E, ax_even_H, ax_odd_H], + [grid_data_E[0], grid_data_E[1], grid_data_H[0], grid_data_H[1]], + [r'Mode 0 | $|E(x,y)|$', r'Mode 1 | $|E(x,y)|$', r'Mode 0 | $|H(x,y)|$', r'Mode 1 | $|H(x,y)|$']): + + ax.imshow(np.sum(np.abs(data), axis = 2), origin = 'lower', + extent = [xmin,xmax,ymin,ymax], cmap = 'jet', interpolation = 'bicubic', aspect = 'auto') + ax.streamplot(grid_x, grid_y, data.real[:,:,0], data.real[:,:,1], color = 'black', linewidth = 0.5) + + for key, polygon in polygons.items(): + if type(polygon) is not LineString: + x,y = polygon.exterior.xy + ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + + ax.set_xlim(xmin, xmax) + ax.set_ylim(ymin, ymax) + + ax.set_xlabel('x (um)') + ax.set_ylabel('y (um)') + + ax.set_title(label) + +fig.tight_layout() +# fig.savefig('modes.png', dpi = 400) + +# %% [markdown] +# # Transmission line characterization +# +# So far we have been foccused on studying the eigenmodes of maxwell's equations for a particular structure. This allows us to calculate $\mathbf{E(x,y)}_i$ and $\mathbf{H(x,y)}_i$ for each of the available eigenmodes. However, we are interested in studying the structure as a transmission line and for that we must start merging towards circuit theory. However, that is not as straightforward as it may appear. First, our modes are not TEM fields, and, therefore, cannot exist in a transmission line. Second, this is far from a lossless line, and it is also supports multiple modes, which not only leads to the requirement of the expressing the line as a multiconductor transmission line, but can also lead to a non-reciprocal line, depending on the formalism you decide to follow [4,5]. It is this ambiguity that can lead to a big confusion when attempting to translate a waveguiding system to a circuit. Here we will follow the formalism of [4] and eventually fully characterize this system as a transmission line. +# + +# %% [markdown] +# The first thing to check is if our modes are transverse enough. Fundamentally they are not, but if they're close enough, it's a good start. We can actually check this with FEMWELL by computing: +# +# $$ +# T = \int_S \frac{|E_x|^2 + |E_y|^2}{|E_z|^2} dS +# $$ + +# %% +print(modes[0].transversality) +print(modes[1].transversality) + + +# %% [markdown] +# We're good. Now we need to take care of the fact that we have a multimode system. More often than not, we will be interested in single mode operation. In the case of a CPW, we are interested in the ground-signal-ground excitation, which represents the fundamental mode. If we excite only this mode we can treat the system as a transmisison line, greatly simplifying our work. On that assumption let us now define the power integral as: +# +# $$ +# p(z) = \int_S \mathbf{E_t} \times \mathbf{H_t}^* dS +# $$ +# +# where $\mathbf{E_t}$, $\mathbf{H_t}$ denote the transverse components of the electric and magnetic field. These, in turn, can be defined as: +# +# $$ +# \mathbf{E_t} = (c_+ e^{-\gamma z} + c_- e^{\gamma z}) \mathbf{e}_t = \frac{v(z)}{v_0}\mathbf{e}_t +# $$ +# +# $$ +# \mathbf{H_t} = (c_+ e^{-\gamma z} - c_- e^{\gamma z}) \mathbf{h}_t = \frac{i(z)}{i_0}\mathbf{h}_t +# $$ +# +# where $i_0$, $v_0$, $i(z)$ and $v(z)$ all have units of current and voltage and the vectors have field units. With definition the power now becomes: +# +# $$ +# p(z) = \frac{v(z) i(z)^*}{v_0 i_0^*}p_0 +# $$ +# +# with: +# +# $$ +# p_0 = \int_S \mathbf{e_t} \times \mathbf{h_t}^* dS +# $$ +# +# Now we have quite some ambiguity left. What are the values of $v_0$, $i_0$ and $p_0$? There is no fundamental constraint telling us what the values should be and we can define any value (any 2 of the 3 variables) and still be correct in the sense that we will still end up with eigenmodes of the system. However, we have started this discussion wanting to transition to a circuit, therefore, we must start converging towards it. Since we can define some currents and voltages, let us identify these with the ones that make sense to be present in a circuit picture. Therefore, we can define: +# +# $$ +# p_0 = v_0 i_0^* +# $$ +# +# It is important to impose the constraint that $Re(p_0)>0$[4]. By fixing the power, we now have only one degree of freedom left to define. We can either define a current or a voltage. Since we want to have a clear and illustrative analogy with a circuit we must choose either $v_0$ or $i_0$ as circuit quantities. In the case of the CPW operating the GSG mode, we can make the analogy as in the picture below. +# +#
+# +#
+# +# With this model, it is now clear that we can define $i_0$ as: +# +# $$ +# i_0 = \int_C \mathbf{h_t} \cdot dl +# $$ +# +# where the integration is on the contour of the signal track. Having this quantity the voltage $v_0$ follows from the power definition. It is important that we define only two of the three unknown variables as they are not independent. This previous method is what is called in the literature as the PI model. Alternatively, we can define: +# +# $$ +# v_0 = -\int_{path} \mathbf{e_t} \cdot dl +# $$ +# +# where the integration path is defined so as to capture the voltage difference between the ground and signal tracks. This is called the PV model. Finally, it is also possible to define simultaneously the $i_0$ and the $v_0$ as above, and THEN define the power $p_0$. This is called the IV (or VI) model. +# +# +# With this in mind, it is clear that the characteristic impedance $Z_0$ follows from: +# +# $$ +# Z_0 = \frac{v_0}{i_0} = \frac{|v_0|^2}{p_0^*} = {p_0}{|i_0|^2} +# $$ +# +# Let us test this: + +# %% +@Functional(dtype=np.complex64) +def current_form(w): + ''' + What this does is it takes the normal vector to the boundary and rotates it 90deg + Then takes the inner product with the magnetic field in it's complex form + ''' + return inner(np.array([w.n[1], -w.n[0]]), w.H) + +@Functional(dtype=np.complex64) +def voltage_form(w): + ''' + What this does is it takes the normal vector to the boundary and rotates it 90deg + Then takes the inner product with the electric field in it's complex form + ''' + + return -inner(np.array([w.n[1], -w.n[0]]), w.E) + +conductor = 'metal_sig_interface' +line = 'path_integral_right' +mode = modes[0] + +p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), + mode.basis,np.conjugate(mode.H)) #The conjugate is due to femwell internal definition as conj(E) x H + + +(ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) +facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) +i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) + +(et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) +facet_basis = et_basis.boundary(facets=mesh.boundaries[line]) +v0 = voltage_form.assemble(facet_basis,E=facet_basis.interpolate(et)) + + + +print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') +print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') +print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') + + +# %% [markdown] +# As you can see all the model give a very close result for the characteristic impedance. +# +# Another interesting aspect of waveguide circuit theory is the fact that it can be shown that, under the quasi-TEM approximation, it is possible to **analytically** extract the RLGC parameters of a circuit via the field solutions. This fits perfectly with the FEM solutions as the evaluation of the integrals is straightforward. The parameters are extracted as: +# +# $$ +# C = \frac{1}{|v_0|^2}\left[ \int_S \epsilon^\prime |\mathbf{e}_t|^2 dS - \int_S \mu^\prime |\mathbf{h}_z|^2 dS \right] +# $$ +# +# $$ +# L = \frac{1}{|i_0|^2}\left[ \int_S \mu^\prime |\mathbf{h}_t|^2 dS - \int_S \epsilon^\prime |\mathbf{e}_z|^2 dS \right] +# $$ +# +# $$ +# G = \frac{\omega}{|v_0|^2}\left[ \int_S \epsilon^{\prime\prime} |\mathbf{e}_t|^2 dS + \int_S \mu^{\prime\prime} |\mathbf{h}_z|^2 dS \right] +# $$ +# +# $$ +# R = \frac{\omega}{|i_0|^2}\left[ \int_S \mu^{\prime\prime} |\mathbf{h}_t|^2 dS + \int_S \epsilon^{\prime\prime} |\mathbf{e}_z|^2 dS \right] +# $$ + +# %% +@Functional(dtype=np.complex64) +def C_form(w): + + return 1/np.abs(w.v0)**2*(np.real(w.epsilon)*inner(w.et, np.conj(w.et)) - + np.real(w.mu) * inner(w.hz, np.conj(w.hz))) + +@Functional(dtype=np.complex64) +def L_form(w): + + return 1/np.abs(w.i0)**2*(np.real(w.mu)*inner(w.ht, np.conj(w.ht)) - + np.real(w.epsilon)*inner(w.ez, np.conj(w.ez))) + +@Functional(dtype=np.complex64) +def G_form(w): + #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + #whereas with python we have eps = eps_r+1j*eps_i. + return -w.omega/np.abs(w.v0)**2*(np.imag(w.epsilon)*inner(w.et, np.conj(w.et))+ + np.imag(w.mu) * inner(w.hz, np.conj(w.hz))) + +@Functional(dtype=np.complex64) +def R_form(w): + #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + #whereas with python we have eps = eps_r+1j*eps_i. + return -w.omega/np.abs(w.i0)**2*(np.imag(w.epsilon)*inner(w.ez, np.conj(w.ez)) + + np.imag(w.mu) * inner(w.ht, np.conj(w.ht))) + +basis_t = et_basis +basis_z = ez_basis +basis_eps = basis0 + +#Be careful with the units!! +#In this case just make sure you adjust the length unit to micrometers +#everything else can stay as is. + +C=C_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +L=L_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +G=G_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +R=R_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') +print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') +print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') +print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') + +print('=================== RLGC ==================') +print(f'R = {R.real/1e-3:.2f} mOhm/um') +print(f'L = {L.real/1e-12:.2f} picoHenry/um') +print(f'G = {G.real/1e-12:.2f} picoSiemens/um') +print(f'C = {C.real/1e-15:.2f} femtoFarad/um') + +print('================== propagation constant ===============') +print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') +print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') + +# %% [markdown] +# While there are some slight differences, the results match quite well!! Now, let us try to repeat the process but including a symmetry plane. + +# %% [markdown] +# Let's try to the same, but now with the symmetry plane: + +# %% +######## Define FEM simulation ########### +use_symmetry_plane = True +symmetry_plane = box(0,-np.inf, np.inf, np.inf) + +near_field_width = 50 * reg.micrometer +near_field_height = 10 * reg.micrometer +########################################## + + +metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, + 0, + w_sig.to(reg.micrometer).magnitude/2, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, -(port_width/2).to(reg.micrometer).magnitude), + 0, + (-w_sig/2-sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, + 0, + min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, (port_width/2).to(reg.micrometer).magnitude), + t_metal.to(reg.micrometer).magnitude) + + +air_box = box((-port_width/2).to(reg.micrometer).magnitude, + 0, + (port_width/2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude) + + +substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width/2).to(reg.micrometer).magnitude, + 0) + +surface = unary_union([air_box, substrate_box]) + +##Make a line for a path integral +### Right conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmax_1, (ymax_1+ymin_1)/2), + (xmin_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_right = LineString(points) + +### left conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmin_1, (ymax_1+ymin_1)/2), + (xmax_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_left = LineString(points) + +polygons = OrderedDict( + surface = LineString(surface.exterior), + metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_sig.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + path_integral_right = path_integral_right, + path_integral_left = path_integral_left, + + metal_sig = metal_sig_box, + metal_gnd_left = metal_gnd_left_box, + metal_gnd_right = metal_gnd_right_box, + air = air_box, + substrate = substrate_box + ) + + + +if use_symmetry_plane: + keys_to_pop = [] + for key,poly in polygons.items(): + if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): + poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) + + if type(poly_tmp) == MultiLineString: + polygons[key] = linemerge(poly_tmp) + + elif poly_tmp.is_empty: + keys_to_pop.append(key) + else: + polygons[key] = poly_tmp + elif not poly.intersects(symmetry_plane): + keys_to_pop.append(key) + + for key in keys_to_pop: + polygons.pop(key) + + +#Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons['surface'].bounds + +left = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[0], surf_bounds[3])]) + +bottom = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[2], surf_bounds[1])]) + +right = LineString([(surf_bounds[2], surf_bounds[1]), + (surf_bounds[2], surf_bounds[3])]) + +top = LineString([(surf_bounds[0], surf_bounds[3]), + (surf_bounds[2], surf_bounds[3])]) + +polygons['left'] = left +polygons['bottom'] = bottom +polygons['right'] = right +polygons['top'] = top + +polygons.move_to_end('top', last = False) +polygons.move_to_end('right', last = False) +polygons.move_to_end('bottom', last = False) +polygons.move_to_end('left', last = False) + +polygons.pop('surface') + +resolutions = dict( + metal_sig_interface = {'resolution': 0.1, 'distance': 10}, + metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, + path_integral_right = {'resolution': 0.2, 'distance': 10}, + path_integral_left = {'resolution': 0.2, 'distance': 10}, + metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, + metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + left = {'resolution': 10, 'distance': 0.1}, + right = {'resolution': 10, 'distance': 0.1}, + top = {'resolution': 10, 'distance': 0.1}, + bottom = {'resolution': 10, 'distance': 0.1}, + air = {'resolution': 10, 'distance': 0.1}, + substrate = {'resolution': 10, 'distance': 1}, + ) + +# %% +fig = plt.figure() +ax = fig.add_subplot(111) + +for key, polygon in polygons.items(): + # print(polygon) + if type(polygon) is not LineString: + x,y = polygon.exterior.xy + ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + else: + ax.plot(*polygon.xy) + + +# %% +#Mesh it +mesh = from_meshio(mesh_from_OrderedDict(polygons, + resolutions, + default_resolution_max = 100, + verbose = True)) + +print(mesh.nelements) + +# %% +mesh.draw() + +# %% +idx_freq = -1 +print(f'Frequency:{freq[idx_freq].magnitude:.2f} GHz') + +basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM +epsilon = basis0.ones(dtype = complex) + +epsilon[basis0.get_dofs(elements = 'air')] = 1 +epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub +epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +# epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + +# %% +start = time.time() +modes = compute_modes(basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=1, + metallic_boundaries=[] + ) + +stop = time.time() + +print(stop-start) + +# %% +print(modes.n_effs.real) +print(20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude, 'dB/cm') + +# %% [markdown] +# Much faster this time round + +# %% +from skfem import ( + ElementVector, + ElementDG, + ElementTriP1 +) + +# Trying interpolator +Nx = 25 +Ny = 50 +grid_data_E = np.zeros((len(modes), Ny, Nx, 3), dtype = complex) +grid_data_H = np.zeros((len(modes), Ny, Nx, 3), dtype = complex) + +xmin = 0 +xmax = 40 +ymin = -10 +ymax = 10 + +grid_x, grid_y = np.meshgrid(np.linspace(xmin,xmax,Nx), np.linspace(ymin, ymax, Ny)) + +for i, mode in enumerate(modes): + basis = mode.basis + basis_fix = basis.with_element(ElementVector(ElementDG(ElementTriP1()))) + + (et, et_basis), (ez, ez_basis) = basis.split(mode.E) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + + + coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) + + start = time.time() + grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates))).T + + grid_data_E[i] = grid_data.reshape((*grid_x.shape, -1)) + end = time.time() + print(end-start) + + (et, et_basis), (ez, ez_basis) = basis.split(mode.H) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + + coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) + + + grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates))).T + + grid_data_H[i] = grid_data.reshape((*grid_x.shape, -1)) + +# %% +fig = plt.figure(figsize = (4,5)) +gs = GridSpec(2,1) + +ax_even_E = fig.add_subplot(gs[0,0]) +ax_even_H = fig.add_subplot(gs[1,0]) + +mode_idx = 0 +for ax, data, label in zip([ax_even_E, ax_even_H], + [grid_data_E[mode_idx], grid_data_H[mode_idx]], + [r'Even mode | $|E(x,y)|$', r'Even mode | $|H(x,y)|$']): + + ax.imshow(np.sum(np.abs(data), axis = 2), origin = 'lower', + extent = [xmin,xmax,ymin,ymax], cmap = 'jet', interpolation = 'bicubic', aspect = 'auto') + ax.streamplot(grid_x, grid_y, data.real[:,:,0], data.real[:,:,1], color = 'black', linewidth = 0.5) + + for key, polygon in polygons.items(): + if type(polygon) is not LineString: + x,y = polygon.exterior.xy + ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + + ax.set_xlim(xmin, xmax) + ax.set_ylim(ymin, ymax) + + ax.set_xlabel('x (um)') + ax.set_ylabel('y (um)') + + ax.set_title(label) + +fig.tight_layout() +# fig.savefig('modes_sym.png', dpi = 400) + +# %% [markdown] +# Now you have to be careful and account for a small artifact. Not only you are dealing with half the field, but the current integral that you do is half of the one calculated previously. Plus, the calculated fields also have different normalizations. Interestingly, if we apply the exact same algorithms without accounting for this fact, what we end up with the values corresponding to a circuit in parallel as below: +# +#
+# +#
+# +# + +# %% +@Functional(dtype=np.complex64) +def current_form(w): + ''' + What this does is it takes the normal vector to the boundary and rotates it 90deg + Then takes the inner product with the magnetic field in it's complex form + ''' + return inner(np.array([w.n[1], -w.n[0]]), w.H) + +@Functional(dtype=np.complex64) +def voltage_form(w): + ''' + What this does is it takes the normal vector to the boundary and rotates it 90deg + Then takes the inner product with the electric field in it's complex form + ''' + + return -inner(np.array([w.n[1], -w.n[0]]), w.E) + +conductor = 'metal_sig_interface' +line = 'path_integral_right' +mode = modes[0] + +p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), + mode.basis,np.conjugate(mode.H)) +print(p0) +#The conjugate is due to femwell internal definition as conj(E) x H +#The factor of 2 is to account for the fact that we are working with half the field only + +(ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) +facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) +i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) + +(et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) +facet_basis = et_basis.boundary(facets=mesh.boundaries[line]) +v0 = voltage_form.assemble(facet_basis,E=facet_basis.interpolate(et)) + + +v0 = p0/i0.conj() +print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') +print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') +print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') + + +# %% [markdown] +# The fact that the Z0 is now double as before follows from the conclusion above. + +# %% +@Functional(dtype=np.complex64) +def C_form(w): + + return 1/np.abs(w.v0)**2*(np.real(w.epsilon)*inner(w.et, np.conj(w.et)) - + np.real(w.mu) * inner(w.hz, np.conj(w.hz))) + +@Functional(dtype=np.complex64) +def L_form(w): + + return 1/np.abs(w.i0)**2*(np.real(w.mu)*inner(w.ht, np.conj(w.ht)) - + np.real(w.epsilon)*inner(w.ez, np.conj(w.ez))) + +@Functional(dtype=np.complex64) +def G_form(w): + #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + #whereas with python we have eps = eps_r+1j*eps_i. + return -w.omega/np.abs(w.v0)**2*(np.imag(w.epsilon)*inner(w.et, np.conj(w.et))+ + np.imag(w.mu) * inner(w.hz, np.conj(w.hz))) + +@Functional(dtype=np.complex64) +def R_form(w): + #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + #whereas with python we have eps = eps_r+1j*eps_i. + return -w.omega/np.abs(w.i0)**2*(np.imag(w.epsilon)*inner(w.ez, np.conj(w.ez)) + + np.imag(w.mu) * inner(w.ht, np.conj(w.ht))) + +basis_t = et_basis +basis_z = ez_basis +basis_eps = basis0 + +#Be careful with the units!! +#In this case just make sure you adjust the length unit to micrometers +#everything else can stay as is. + +C=C_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +L=L_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +G=G_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +R=R_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + +Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') +print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') +print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') +print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') + +print('=================== RLGC ==================') +print(f'R = {R.real/1e-3:.2f} mOhm/um') +print(f'L = {L.real/1e-12:.2f} picoHenry/um') +print(f'G = {G.real/1e-12:.2f} picoSiemens/um') +print(f'C = {C.real/1e-15:.2f} femtoFarad/um') + +print('================== propagation constant ===============') +print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') +print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') + +# %% [markdown] +# To recover the RLGC values of the entire structure you simple half the Z and double the Y. + +# %% +R = R/2 +L = L/2 +C = 2*C +G = 2*G + +Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* + (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) + +print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') + +print('=================== RLGC ==================') +print(f'R = {R.real/1e-3:.2f} mOhm/um') +print(f'L = {L.real/1e-12:.2f} picoHenry/um') +print(f'G = {G.real/1e-12:.2f} picoSiemens/um') +print(f'C = {C.real/1e-15:.2f} femtoFarad/um') + +print('================== propagation constant ===============') +print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') +print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') + +# %% [markdown] +# Well, having all this set up nicely, all we are left to do is to make a frequency sweep and compare the frequency dependent values with other works and check the validity of it. Let's just define some helper functions + +# %% [markdown] +# # Frequency sweep + +# %% +reg = UnitRegistry() + +use_symmetry_plane = True +mesh_res = 'fine' + +#Define frequency range +freq = np.logspace(np.log10(0.05), np.log10(45), 100) * reg.GHz +n_modes = 1 +omega = 2*np.pi*freq +print('meshing') + + +################## MESHING ################################ +## Define universal constants +mu0 = 4*np.pi * 1e-7 * reg.henry/reg.meter #vacuum magnetic permeability +e0 = 8.854e-12 * reg.farad*reg.meter**-1 +c = 3e8 * reg.meter*reg.second**-1 #m s^-1 +e=1.602176634e-19 * reg.coulomb #Coulombs +kb=1.380649e-23 *reg.meter**2*reg.kg*reg.second**-2*reg.kelvin**-1 +T=300 * reg.kelvin + +w_sig = 7 * reg.micrometer +sep = 10 * reg.micrometer +w_gnd = 100 * reg.micrometer +t_metal = 0.8 * reg.micrometer + +h_sub = 500 * reg.micrometer +h_air = 500 * reg.micrometer + +eps_r_sub = 13 * reg.dimensionless + +sig_metal = 6e5 * reg.siemens/reg.centimeter + +######## Define FEM simulation ########### + +symmetry_plane = box(0,-np.inf, np.inf, np.inf) +port_width = (w_sig+2*sep)*3 #um +bottom_height = (w_sig+2*sep)*1 +top_height = (w_sig+2*sep)*1 + +########################################## + +metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, + 0, + w_sig.to(reg.micrometer).magnitude/2, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, + -(port_width/2).to(reg.micrometer).magnitude), + 0, + (-w_sig/2-sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude) + +metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, + 0, + min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, + (port_width/2).to(reg.micrometer).magnitude), + t_metal.to(reg.micrometer).magnitude) + +air_box = box((-port_width/2).to(reg.micrometer).magnitude, + 0, + (port_width/2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude) + + +substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width/2).to(reg.micrometer).magnitude, + 0) + +surface = unary_union([air_box, substrate_box]) + +##Make a line for a path integral +### Right conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmax_1, (ymax_1+ymin_1)/2), + (xmin_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_right = LineString(points) + +### left conductor +xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds +xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds + +points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), + (xmin_1, (ymax_1+ymin_1)/2), + (xmax_2, (ymax_2+ymin_2)/2), + ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] + +path_integral_left = LineString(points) + +polygons = OrderedDict( + surface = LineString(surface.exterior), + metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_sig.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, + w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), + join_style = 'bevel'), + *surface.bounds).exterior), + + path_integral_right = path_integral_right, + path_integral_left = path_integral_left, + + metal_sig = metal_sig_box, + metal_gnd_left = metal_gnd_left_box, + metal_gnd_right = metal_gnd_right_box, + air = air_box, + substrate = substrate_box + ) + + + +if use_symmetry_plane: + keys_to_pop = [] + for key,poly in polygons.items(): + if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): + poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) + + if type(poly_tmp) == MultiLineString: + polygons[key] = linemerge(poly_tmp) + + elif poly_tmp.is_empty: + keys_to_pop.append(key) + else: + polygons[key] = poly_tmp + elif not poly.intersects(symmetry_plane): + keys_to_pop.append(key) + + for key in keys_to_pop: + polygons.pop(key) + +#Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons['surface'].bounds + +left = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[0], surf_bounds[3])]) + +bottom = LineString([(surf_bounds[0], surf_bounds[1]), + (surf_bounds[2], surf_bounds[1])]) + +right = LineString([(surf_bounds[2], surf_bounds[1]), + (surf_bounds[2], surf_bounds[3])]) + +top = LineString([(surf_bounds[0], surf_bounds[3]), + (surf_bounds[2], surf_bounds[3])]) + +polygons['left'] = left +polygons['bottom'] = bottom +polygons['right'] = right +polygons['top'] = top + +polygons.move_to_end('top', last = False) +polygons.move_to_end('right', last = False) +polygons.move_to_end('bottom', last = False) +polygons.move_to_end('left', last = False) + +polygons.pop('surface') +# print(polygons.keys()) +if mesh_res == 'fine': + resolutions = dict( + surface = {'resolution': 100, 'distance': 1}, + metal_sig_interface = {'resolution': 0.1, 'distance': 10}, + metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, + path_integral_right = {'resolution': 0.2, 'distance': 10}, + path_integral_left = {'resolution': 0.2, 'distance': 10}, + metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, + metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + air = {'resolution': 10, 'distance': 0.1}, + substrate = {'resolution': 10, 'distance': 1}, + ) +else: + resolutions = dict( + surface = {'resolution': 100, 'distance': 1}, + metal_sig_interface = {'resolution': 0.5, 'distance': 10}, + metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, + path_integral_right = {'resolution': 0.5, 'distance': 10}, + path_integral_left = {'resolution': 0.5, 'distance': 10}, + metal_sig = {'resolution': 0.5, 'distance': 0.1, 'SizeMax': 0.1}, + metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, + air = {'resolution': 20, 'distance': 0.1}, + substrate = {'resolution': 20, 'distance': 1}, + ) + +# fig = plt.figure() +# ax = fig.add_subplot(111) + +# for key, polygon in polygons.items(): +# if type(polygon) is not LineString: +# x,y = polygon.exterior.xy +# ax.plot(np.asarray(x),np.asarray(y), color = 'pink') +# else: +# ax.plot(*polygon.xy) +#Mesh it +mesh = from_meshio(mesh_from_OrderedDict(polygons, + resolutions, + default_resolution_max = 100, + verbose = True)) + + +print(mesh.nelements) +manager = enlighten.get_manager() +bar = manager.counter(total=len(freq), desc='Ticks', unit='ticks') + +neff_all = np.zeros(len(freq), dtype = complex) +atten = np.zeros(len(freq), dtype = float) +z0 = np.zeros(len(freq), dtype = complex) +R = np.zeros(len(freq), dtype = complex) +L = np.zeros(len(freq), dtype = complex) +G = np.zeros(len(freq), dtype = complex) +C = np.zeros(len(freq), dtype = complex) +z0_RLGC = np.zeros(len(freq), dtype = complex) + +for idx_freq in range(len(freq)): + # break + basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM + epsilon = basis0.ones(dtype = complex) + + epsilon[basis0.get_dofs(elements = 'air')] = 1 + epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub + epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + # epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + # break + modes = compute_modes( + basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=n_modes, + metallic_boundaries=False, + ) + + neff_all[idx_freq] = modes.n_effs + atten[idx_freq] = 20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude #dB/cm + + ###### CALCULATE Z0 ############ + conductor = 'metal_sig_interface' + line = 'path_integral_right' + mode = modes[0] + + p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), + mode.basis,np.conjugate(mode.H)) #The conjugate is due to femwell internal definition as conj(E) x H + + + (ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) + (et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) + + facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) + i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) + + v0 = p0/i0.conj() + + z0_tmp = v0/i0 + + ########### CALCULATE RLGC PARAMETERS ############# + basis_t = et_basis + basis_z = ez_basis + basis_eps = basis0 + + C_tmp=C_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + + L_tmp=L_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + + G_tmp=G_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + + R_tmp=R_form.assemble(mode.basis, + epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), + mu = mu0.to(reg.henry/reg.micrometer).magnitude, + i0=i0, + omega = omega[idx_freq].to(reg.second**-1).magnitude, + v0 = v0, + et = basis_t.interpolate(et), + ez = basis_z.interpolate(ez), + ht = basis_t.interpolate(ht), + hz = basis_z.interpolate(hz)) + + z0_RLGC_tmp = np.sqrt((R_tmp+1j*omega[idx_freq].to(reg.second**-1).magnitude * L_tmp)/ + (G_tmp+1j*omega[idx_freq].to(reg.second**-1).magnitude * C_tmp)) + + ## Save the parameters for the full device + + z0[idx_freq] = z0_tmp/2 + z0_RLGC[idx_freq] = z0_RLGC_tmp/2 + R[idx_freq] = R_tmp/2 + L[idx_freq] = L_tmp/2 + G[idx_freq] = G_tmp*2 + C[idx_freq] = C_tmp*2 + + + bar.update() + +mesh.draw() + +# fig = basis0.plot(epsilon.real, colorbar = True) +# fig.axes.set_axis_on() + +# fig = basis0.plot(epsilon.imag, colorbar = True) +# fig.axes.set_axis_on() + +# %% +##DATA FROM PAPER +data_nu = np.asarray([[-1.317, 7.780], + [-1.218, 7.327], + [-1.106, 6.474], + [-0.983, 5.995], + [-0.800, 5.160], + [-0.647, 4.574], + [-0.499, 4.094], + [-0.270, 3.632], + [0.000, 3.188], + [0.311, 2.993], + [0.622, 2.940], + [0.958, 2.833], + [1.254, 2.833], + [1.559, 2.833]]) + + +data_alpha = np.asarray([[-1.287, 0.728], + [-1.060, 0.950], + [-0.780, 1.181], + [-0.520, 1.465], + [-0.270, 1.785], + [-0.025, 2.087], + [0.204, 2.353], + [0.392, 2.567], + [0.632, 2.886], + [0.846, 3.117], + [1.070, 3.739], + [1.310, 4.680], + [1.478, 5.426], + [1.590, 5.977]]) + +## DATA FROM TU DELFT SIM +freq_matlab = np.loadtxt('support/freq_delft.txt') #Frequency sampling from their simulator +ReZ0_delft = np.loadtxt('support/ReZ0_delft.txt') #Real part of the impedance +ImZ0_delft = np.loadtxt('support/ImZ0_delft.txt') #Imaginary part of the impedance +loss_delft = np.loadtxt('support/loss_delft.txt') #The loss of the mode +ReK_delft = np.loadtxt('support/ReK_delft.txt') #The real part of the wavevector + +# %% +fig = plt.figure() +ax = fig.add_subplot(111) +ax1 = ax.twinx() + +ax.plot(freq, neff_all, color = 'blue', label = 'FEMWELL') +ax.plot(freq_matlab, ReK_delft, color = 'blue', linestyle = 'dotted', label = 'TU Delft simulator') + +ax.plot(10**data_nu[:,0], data_nu[:,1], + marker= '^', color = 'blue', linestyle = 'dashed', + label = 'Tuncer et al. 1992') +ax.set_xscale('log') + +ax.legend(loc = 'upper center') +ax1.plot(freq, -atten, color = 'red') +ax1.plot(10**data_alpha[:,0], data_alpha[:,1], marker= '^', color = 'red', linestyle = 'dashed') +ax1.plot(freq_matlab, loss_delft, color = 'red', linestyle = 'dotted') + +ax.set_xlabel('Frequency (GHz)') +ax.set_ylabel('Microwave index') +ax1.set_ylabel('Attenuation (dB/cm)') + +ax.arrow(0.1,5,-0.05,0, head_width = 0.1, head_length = 0.01, color = 'blue') +ax1.arrow(10,3,10,0, head_width = 0.1, head_length = 5, color = 'red') + +# fig.savefig('Tuncer_et_al_1992_results.png', dpi = 400) + +# %% +fig = plt.figure() +ax = fig.add_subplot(111) + +ax.plot(freq, z0.real, label = 'FEMWELL - real', color = 'blue') +ax.plot(freq, z0.imag, label = 'FEMWELL - imag', color = 'red') +ax.plot(freq_matlab, ReZ0_delft, label = 'TU Delft sim - real', color = 'blue', linestyle = 'dashed') +ax.plot(freq_matlab, ImZ0_delft, label = 'TU Delft sim - imag', color = 'red', linestyle = 'dashed') + +ax.set_xlabel('Frequency (GHz)') +ax.set_ylabel('Z0 (Ohm)') + +ax.legend() + +ax.set_xscale('log') + +# fig.savefig('z0_delft.png', dpi = 400) + +# %% [markdown] +# The mismatch between the above curves can be explained due to TU Delft not being able to correctly estimate the propagation constant of the field, hence it will not correctly predict the characteristic impedance of the device. + +# %% +fig = plt.figure() +gs = GridSpec(2,2) + +ax_C = fig.add_subplot(gs[0,0]) +ax_G = fig.add_subplot(gs[0,1]) +ax_L = fig.add_subplot(gs[1,0]) +ax_R = fig.add_subplot(gs[1,1]) + +for data, ax, label in zip([R/1e-3,L/1e-12,G/1e-12,C/1e-15], + [ax_R, ax_L, ax_G, ax_C], + ['kOhm/m', 'uHenry/m','uS/m', 'nF/m']): + + ax.plot(freq, data, color = 'blue') + ax.set_xlabel('Frequency (GHz)') + ax.set_ylabel(label) + + ax.set_xscale('log') +fig.tight_layout() diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt b/docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt new file mode 100644 index 00000000..35c095d9 --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt @@ -0,0 +1,154 @@ +-1.157582306070029876e+00 +-1.182509787468440710e+00 +-1.208503087405807364e+00 +-1.235588034615594744e+00 +-1.263768931049162880e+00 +-1.293081539981793204e+00 +-1.323557946029030763e+00 +-1.355237558367177408e+00 +-1.388156740638433417e+00 +-1.422352034109460650e+00 +-1.457869823339989601e+00 +-1.494749245914654079e+00 +-1.533033300300297386e+00 +-1.572766357985726504e+00 +-1.613993723586580664e+00 +-1.656763166485924810e+00 +-1.701122794174423580e+00 +-1.747122599141999855e+00 +-1.794789653763162907e+00 +-1.844248824821063559e+00 +-1.895486117128052816e+00 +-1.948580531250056591e+00 +-2.003590981533915460e+00 +-2.060578434045229113e+00 +-2.119605996687136340e+00 +-2.180739009610220158e+00 +-2.244045135972282612e+00 +-2.309594452303071588e+00 +-2.377459540113373571e+00 +-2.447715577172612456e+00 +-2.520440428911534525e+00 +-2.595714741236326173e+00 +-2.673622032428730755e+00 +-2.754248786457532905e+00 +-2.837692988237921377e+00 +-2.924030895904208283e+00 +-3.013367263682980024e+00 +-3.105800566244479644e+00 +-3.201433503654893542e+00 +-3.300372292394565310e+00 +-3.402726754313660340e+00 +-3.508610402074802792e+00 +-3.618140522625397093e+00 +-3.731438255995030318e+00 +-3.848628670830473997e+00 +-3.969840833390715584e+00 +-4.095207873418067912e+00 +-4.224867035474326293e+00 +-4.358959731850598374e+00 +-4.497631578450308254e+00 +-4.641032423297423826e+00 +-4.789316363120608600e+00 +-4.942641745335079051e+00 +-5.101171159102533714e+00 +-5.265071405826591722e+00 +-5.434513460729482048e+00 +-5.609672409365172641e+00 +-5.790727371568686443e+00 +-5.977861403959793485e+00 +-6.171261384512746417e+00 +-6.371117874860692254e+00 +-6.577624967227535535e+00 +-6.790980107930280951e+00 +-7.011383902694460879e+00 +-7.239039907174851685e+00 +-7.474154394664134671e+00 +-7.716936112842202711e+00 +-7.967596024459555615e+00 +-8.226347038316509597e+00 +-8.493368069995694825e+00 +-8.769116357913990356e+00 +-9.053438798896502604e+00 +-9.346717875333606429e+00 +-9.649171930456445168e+00 +-9.961019532631356199e+00 +-1.028247920218426970e+01 +-1.061376914683452988e+01 +-1.095510702912872070e+01 +-1.130670973467646157e+01 +-1.166879318020991718e+01 +-1.204157213952655248e+01 +-1.242526010783475243e+01 +-1.282006918049309618e+01 +-1.322621001267457430e+01 +-1.364389172951264229e+01 +-1.407332198224052178e+01 +-1.451470696280678574e+01 +-1.496825148615143775e+01 +-1.543415912025571579e+01 +-1.591263233910674124e+01 +-1.640387271644796385e+01 +-1.690808117257032350e+01 +-1.742545822669816147e+01 +-1.795620431149833607e+01 +-1.850052007621407668e+01 +-1.905860677360249511e+01 +-1.963066660621158022e+01 +-2.021690314087108931e+01 +-2.081752169844617129e+01 +-2.143272980464999478e+01 +-2.206273759372864518e+01 +-2.271147997335348379e+01 +-2.337191642684674520e+01 +-2.404780852821738435e+01 +-2.473938119978217287e+01 +-2.544686414835368282e+01 +-2.617049227889619090e+01 +-2.691050610924286701e+01 +-2.766715216563436996e+01 +-2.844068336383317330e+01 +-2.923135937872348933e+01 +-3.003944700212987939e+01 +-3.086522046999905200e+01 +-3.170896180387996921e+01 +-3.257096109076084645e+01 +-3.345298297477481242e+01 +-3.435244289293947162e+01 +-3.527108288336980735e+01 +-3.620922845887920261e+01 +-3.716721422092346216e+01 +-3.814538437971605589e+01 +-3.914409291622719422e+01 +-4.016370393316071841e+01 +-4.120459168435123587e+01 +-4.226714085453937031e+01 +-4.335174669023051308e+01 +-4.445881518217638018e+01 +-4.558876328031279002e+01 +-4.674201895488760528e+01 +-4.791902143439809691e+01 +-4.912022133323333151e+01 +-5.034608082443121901e+01 +-5.159707373969254007e+01 +-5.287368580837868848e+01 +-5.417641474669940749e+01 +-5.550577042013694751e+01 +-5.686227506700453915e+01 +-5.824646334609037268e+01 +-5.965888261773633872e+01 +-6.110009302410490051e+01 +-6.257066775745295217e+01 +-6.407119312415588297e+01 +-6.560226883635576201e+01 +-6.716450814885490672e+01 +-6.875853803288995891e+01 +-7.038499945678564984e+01 +-7.204454750450928202e+01 +-7.373785166009164982e+01 +-7.546559597686869836e+01 +-7.722847942938810206e+01 +-7.902721592877759349e+01 +-8.086253483971837852e+01 +-8.273518104013440677e+01 +-8.464591516344316346e+01 diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt b/docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt new file mode 100644 index 00000000..8e328607 --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt @@ -0,0 +1,154 @@ +2.691485913925359075e+00 +2.692151926868994405e+00 +2.692826441486253053e+00 +2.693519551526634803e+00 +2.694226310540131841e+00 +2.694946955246150910e+00 +2.695675282891197622e+00 +2.696424752955969062e+00 +2.697189305790852032e+00 +2.697959524713574364e+00 +2.698755824544570636e+00 +2.699568884977982641e+00 +2.700399341492370819e+00 +2.701247861759427327e+00 +2.702114915749732837e+00 +2.703001684617745148e+00 +2.703908695441340271e+00 +2.704836729769424686e+00 +2.705772287716126545e+00 +2.706744804619915179e+00 +2.707740856974918842e+00 +2.708761340324274070e+00 +2.709807181408131260e+00 +2.710879339718949677e+00 +2.711978809320642725e+00 +2.713106620885914033e+00 +2.714263843944311194e+00 +2.715451589337029681e+00 +2.716671011877543673e+00 +2.717923313219855252e+00 +2.719209744938591022e+00 +2.720531611827278873e+00 +2.721890275423061389e+00 +2.723287157767730449e+00 +2.724698058981393167e+00 +2.726175871080242175e+00 +2.727696572665775587e+00 +2.729261870536163759e+00 +2.730873554773528333e+00 +2.732533504191862139e+00 +2.734243692115226665e+00 +2.736006192499397027e+00 +2.737823186408880449e+00 +2.739696968859475668e+00 +2.741629956034230542e+00 +2.743624692877720594e+00 +2.745683861070061571e+00 +2.747810287377837835e+00 +2.750006952374342006e+00 +2.752276999515936406e+00 +2.754623744555268594e+00 +2.757050685265315160e+00 +2.759561511440975057e+00 +2.762160115137277216e+00 +2.764850601095249161e+00 +2.767637297298348731e+00 +2.770524765594182259e+00 +2.773517812308225139e+00 +2.776621498768665486e+00 +2.779841151654383946e+00 +2.783182373071895643e+00 +2.786651050261772955e+00 +2.790253364831053684e+00 +2.793995801405521728e+00 +2.797885155594628248e+00 +2.801928541162580721e+00 +2.806133396301537353e+00 +2.810507488907405094e+00 +2.815058920765070916e+00 +2.819748064870253312e+00 +2.824679731147743844e+00 +2.829815066340673368e+00 +2.835163524812530866e+00 +2.840734896916673247e+00 +2.846539305188366065e+00 +2.852587199232630599e+00 +2.858889349231084065e+00 +2.865456838110005844e+00 +2.872301052431808888e+00 +2.879433672091868068e+00 +2.886866658921865447e+00 +2.894612244318917949e+00 +2.902682916045249417e+00 +2.911091404287765361e+00 +2.919850667325169624e+00 +2.928973876668762077e+00 +2.938474402164450883e+00 +2.948365797056039295e+00 +2.958661783256278266e+00 +2.969376236999468510e+00 +2.980523175052351537e+00 +2.992116741651275724e+00 +3.004171196321832049e+00 +3.016700902723012501e+00 +3.029720318641507593e+00 +3.043243987243854143e+00 +3.057286529674496656e+00 +3.071862639068193346e+00 +3.086987076024456211e+00 +3.102674665571999224e+00 +3.118940295631263293e+00 +3.135798921196399469e+00 +3.153265549201734075e+00 +3.171355265617371799e+00 +3.190083223819020652e+00 +3.209464653944571033e+00 +3.229514869597755578e+00 +3.250249275684259587e+00 +3.271683377290186101e+00 +3.293832789509170134e+00 +3.316713248122880930e+00 +3.340340621039453950e+00 +3.364730920395968550e+00 +3.389900315233658201e+00 +3.415865144658416952e+00 +3.442491762046922865e+00 +3.470096006880941886e+00 +3.498545824337362387e+00 +3.527858371546021665e+00 +3.558051046071218693e+00 +3.589141500381201055e+00 +3.621147656735406617e+00 +3.654087721093635999e+00 +3.687980198273299148e+00 +3.722843906066733766e+00 +3.758697989651845095e+00 +3.795561935826521349e+00 +3.833455587118438146e+00 +3.872399155761411471e+00 +3.912413237533613586e+00 +3.953518825456088681e+00 +3.995737323353187431e+00 +4.039090559279421733e+00 +4.083600798819269073e+00 +4.129290758268699513e+00 +4.176183617708841389e+00 +4.224303033983763278e+00 +4.273673153595286323e+00 +4.324318625529041782e+00 +4.376264614026507793e+00 +4.429536811318531164e+00 +4.484161450336041632e+00 +4.540165317414378343e+00 +4.597575765007191784e+00 +4.656420724426808100e+00 +4.716728718627032357e+00 +4.778528875045179092e+00 +4.841850938519123737e+00 +4.906725284295848866e+00 +4.973182931147079877e+00 +5.041255554607754874e+00 +5.110975500352574308e+00 +5.182375797725897293e+00 +5.255490173439739898e+00 diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt b/docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt new file mode 100644 index 00000000..c8d467b5 --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt @@ -0,0 +1,154 @@ +6.102385574926012879e+01 +6.104551690520960250e+01 +6.106660759490944201e+01 +6.108739654224218185e+01 +6.110778956018397423e+01 +6.112799942262114428e+01 +6.114826459881543030e+01 +6.116833493516440967e+01 +6.118842705717329267e+01 +6.120883484463112723e+01 +6.122911859359372500e+01 +6.124956324215520453e+01 +6.127020755411031416e+01 +6.129108784380473907e+01 +6.131224480027973556e+01 +6.133369835457791908e+01 +6.135548631509072237e+01 +6.137763908111366362e+01 +6.140021069839198731e+01 +6.142350545854514365e+01 +6.144692799167865616e+01 +6.147083094078191579e+01 +6.149524254550814106e+01 +6.152019106516704028e+01 +6.154570490011303718e+01 +6.157181271102260922e+01 +6.159854353110115710e+01 +6.162592687634925426e+01 +6.165399285405608509e+01 +6.168277227071557434e+01 +6.171229673690123718e+01 +6.174259877754125370e+01 +6.177371193951613293e+01 +6.180567090335308933e+01 +6.183912856860459328e+01 +6.187288786537260421e+01 +6.190760526989012646e+01 +6.194332090354865983e+01 +6.198007690987424922e+01 +6.201791731655830375e+01 +6.205688817727072859e+01 +6.209703772158894708e+01 +6.213841651481875061e+01 +6.218107761885220697e+01 +6.222507676685935252e+01 +6.227047254194565795e+01 +6.231732656393388226e+01 +6.236570368718663815e+01 +6.241567220340925815e+01 +6.246730405089373761e+01 +6.252067503472971310e+01 +6.257586505101465235e+01 +6.263295831715276307e+01 +6.269204360832888057e+01 +6.275321449515720929e+01 +6.281656959017098529e+01 +6.288221278669858094e+01 +6.295025349988044638e+01 +6.302080691851418237e+01 +6.309399422984090933e+01 +6.316994285179792001e+01 +6.324878666050017273e+01 +6.333066619439188116e+01 +6.341572885473902943e+01 +6.350412908553607849e+01 +6.359602853695201929e+01 +6.369159620807236166e+01 +6.379100856715874102e+01 +6.389444964897840862e+01 +6.400214211666279596e+01 +6.411531591714583556e+01 +6.423202558526564587e+01 +6.435357677811569488e+01 +6.448019178989409284e+01 +6.461210044097907712e+01 +6.474953996029825021e+01 +6.489275483799201538e+01 +6.504199664771238076e+01 +6.519752384104459964e+01 +6.535960150978695538e+01 +6.552850113473118654e+01 +6.570450029603257747e+01 +6.588788238115849083e+01 +6.607893626697959633e+01 +6.627795599223888701e+01 +6.648524042411435175e+01 +6.670109292007090573e+01 +6.692582099174367727e+01 +6.715973596512434085e+01 +6.740315266591022692e+01 +6.765638909882801499e+01 +6.791976615457770095e+01 +6.819360734285707792e+01 +6.847823852945849410e+01 +6.877398771243461795e+01 +6.908118483458150649e+01 +6.940016160583618898e+01 +6.973125137354213621e+01 +7.007478901776045177e+01 +7.043111089055379637e+01 +7.080055477563900013e+01 +7.118271584792721285e+01 +7.157945595311556986e+01 +7.199034528708668290e+01 +7.241572788516086234e+01 +7.285594952744975217e+01 +7.331135788694763278e+01 +7.378230272139265367e+01 +7.426913605136654439e+01 +7.477221240680763970e+01 +7.529188906327368613e+01 +7.582852628226599734e+01 +7.638248762111402357e+01 +7.695414018646303589e+01 +7.754385496229198793e+01 +7.815548911234613172e+01 +7.878248590484847114e+01 +7.942868489861962189e+01 +8.009447578825587755e+01 +8.078025360638723384e+01 +8.148641923749076454e+01 +8.221337959125223449e+01 +8.296154814674756039e+01 +8.373134509454395413e+01 +8.452319774390967666e+01 +8.533754085711237281e+01 +8.617481694247821622e+01 +8.703547657732279674e+01 +8.791997878040336900e+01 +8.882879123025921331e+01 +8.976239064182811944e+01 +9.072126308019812768e+01 +9.170590424863537748e+01 +9.271681974733117215e+01 +9.375452543905950620e+01 +9.481954772076542781e+01 +9.591242381350990343e+01 +9.703370205806540127e+01 +9.818394221262735755e+01 +9.936371571524450985e+01 +1.005736060313317495e+02 +1.018142088790417148e+02 +1.030861325579987806e+02 +1.043899981619856447e+02 +1.057264400949929239e+02 +1.070961059601505525e+02 +1.084996572906935484e+02 +1.099377695632773566e+02 +1.114111325680043336e+02 +1.129204506863547408e+02 +1.144664432939833745e+02 +1.160498448614285962e+02 +1.176714055121576905e+02 +1.193318911123788268e+02 diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt b/docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt new file mode 100644 index 00000000..1deac253 --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt @@ -0,0 +1,154 @@ +5.000000000000000000e+01 +4.800000000000000000e+01 +4.607999999999999829e+01 +4.423679999999999524e+01 +4.246732799999999486e+01 +4.076863487999999336e+01 +3.913788948479999874e+01 +3.757237390540799282e+01 +3.606947894919166231e+01 +3.462669979122399866e+01 +3.324163179957503900e+01 +3.191196652759204255e+01 +3.063548786648835431e+01 +2.941006835182882284e+01 +2.823366561775566552e+01 +2.710431899304543890e+01 +2.602014623332361865e+01 +2.497934038399067447e+01 +2.398016676863104735e+01 +2.302096009788580488e+01 +2.210012169397037241e+01 +2.121611682621155381e+01 +2.036747215316308868e+01 +1.955277326703656726e+01 +1.877066233635510173e+01 +1.801983584290089624e+01 +1.729904240918486025e+01 +1.660708071281746712e+01 +1.594279748430476928e+01 +1.530508558493257709e+01 +1.469288216153527316e+01 +1.410516687507386280e+01 +1.354096020007090573e+01 +1.299932179206806993e+01 +1.247934892038534649e+01 +1.198017496356993306e+01 +1.150096796502713481e+01 +1.104092924642604778e+01 +1.059929207656900552e+01 +1.017532039350624729e+01 +9.768307577765995120e+00 +9.377575274655354676e+00 +9.002472263669142549e+00 +8.642373373122374858e+00 +8.296678438197480077e+00 +7.964811300669580874e+00 +7.646218848642796218e+00 +7.340370094697085790e+00 +7.046755290909201719e+00 +6.764885079272832336e+00 +6.494289676101919540e+00 +6.234518089057842438e+00 +5.985137365495527995e+00 +5.745731870875706804e+00 +5.515902596040677786e+00 +5.295266492199051100e+00 +5.083455832511088346e+00 +4.880117599210645807e+00 +4.684912895242218767e+00 +4.497516379432529021e+00 +4.317615724255229104e+00 +4.144911095285019442e+00 +3.979114651473618647e+00 +3.819950065414673190e+00 +3.667152062798086476e+00 +3.520465980286162644e+00 +3.379647341074716760e+00 +3.244461447431727930e+00 +3.114682989534458546e+00 +2.990095669953080293e+00 +2.870491843154956868e+00 +2.755672169428758256e+00 +2.645445282651607943e+00 +2.539627471345543341e+00 +2.438042372491721732e+00 +2.340520677592052845e+00 +2.246899850488370376e+00 +2.157023856468835987e+00 +2.070742902210081837e+00 +1.987913186121678555e+00 +1.908396658676811519e+00 +1.832060792329738952e+00 +1.758778360636549465e+00 +1.688427226211087318e+00 +1.620890137162643851e+00 +1.556054531676137831e+00 +1.493812350409092105e+00 +1.434059856392728483e+00 +1.376697462137019379e+00 +1.321629563651538719e+00 +1.268764381105477179e+00 +1.218013805861257737e+00 +1.169293253626807472e+00 +1.122521523481735173e+00 +1.077620662542465579e+00 +1.034515836040767134e+00 +9.931352025991362353e-01 +9.534097944951709369e-01 +9.152734027153639484e-01 +8.786624666067494038e-01 +8.435159679424794055e-01 +8.097753292247802381e-01 +7.773843160557889487e-01 +7.462889434135573374e-01 +7.164373856770150928e-01 +6.877798902499344669e-01 +6.602686946399370926e-01 +6.338579468543396045e-01 +6.085036289801658560e-01 +5.841634838209591729e-01 +5.607969444681207838e-01 +5.383650666893959880e-01 +5.168304640218202017e-01 +4.961572454609473604e-01 +4.763109556425094149e-01 +4.572585174168090072e-01 +4.389681767201366047e-01 +4.214094496513311472e-01 +4.045530716652778747e-01 +3.883709487986667930e-01 +3.728361108467200813e-01 +3.579226664128512536e-01 +3.436057597563372257e-01 +3.298615293660837056e-01 +3.166670681914403596e-01 +3.040003854637826630e-01 +2.918403700452313898e-01 +2.801667552434221609e-01 +2.689600850336852922e-01 +2.582016816323378094e-01 +2.478736143670443315e-01 +2.379586697923625205e-01 +2.284403230006679941e-01 +2.193027100806413110e-01 +2.105306016774156075e-01 +2.021093776103189965e-01 +1.940250025059062355e-01 +1.862640024056699539e-01 +1.788134423094431824e-01 +1.716609046170654296e-01 +1.647944684323827957e-01 +1.582026896950874950e-01 +1.518745821072839675e-01 +1.457995988229926210e-01 +1.399676148700728928e-01 +1.343689102752699815e-01 +1.289941538642591923e-01 +1.238343877096887979e-01 +1.188810122013012477e-01 +1.141257717132491967e-01 +1.095607408447192288e-01 +1.051783112109304641e-01 +1.009711787624932161e-01 +9.693233161199349412e-02 diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt b/docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt new file mode 100644 index 00000000..8b0efa22 --- /dev/null +++ b/docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt @@ -0,0 +1,154 @@ +4.739199576599296471e+00 +4.647012687544633813e+00 +4.558218242030294576e+00 +4.472737258033426144e+00 +4.390419373715454654e+00 +4.311136387493473165e+00 +4.234734544214971308e+00 +4.161141192738623928e+00 +4.090205657526658101e+00 +4.021769103523255673e+00 +3.955792925471715460e+00 +3.892124139392544091e+00 +3.830653812404662073e+00 +3.771277277256577243e+00 +3.713892704675008538e+00 +3.658407302229667479e+00 +3.604727969944267141e+00 +3.552767548089233340e+00 +3.502400346173617507e+00 +3.453634805545053421e+00 +3.406351834173134030e+00 +3.360480679837588802e+00 +3.315954354723815722e+00 +3.272709479418081990e+00 +3.230686122419777373e+00 +3.189827637236431812e+00 +3.150080499055894379e+00 +3.111394142623802761e+00 +3.073720802634242855e+00 +3.037015357664051507e+00 +3.001235178442220874e+00 +2.966339981041535179e+00 +2.932291685406332782e+00 +2.899054279484842844e+00 +2.866546038454464984e+00 +2.834831497199962413e+00 +2.803830874363740566e+00 +2.773515189757886912e+00 +2.743856937864472822e+00 +2.714829985828722592e+00 +2.686409476587388045e+00 +2.658571736908973637e+00 +2.631294190115978537e+00 +2.604555273259819170e+00 +2.578334358526289449e+00 +2.552611678680725582e+00 +2.527368256232317645e+00 +2.502585836535490049e+00 +2.478246824038599971e+00 +2.454334222209606065e+00 +2.430831576665106741e+00 +2.407722921576634700e+00 +2.384992729311537385e+00 +2.362625863314483876e+00 +2.340607534264264711e+00 +2.318923259566478379e+00 +2.297558826265203802e+00 +2.276500257474750821e+00 +2.255733782445382030e+00 +2.235245810384023368e+00 +2.215022908151307313e+00 +2.195051781950370984e+00 +2.175319263109452006e+00 +2.155812298040462149e+00 +2.136517942428950878e+00 +2.117423359678053174e+00 +2.098515823590638707e+00 +2.079782725230922669e+00 +2.061211583859940077e+00 +2.042750476276841365e+00 +2.024467011801589944e+00 +2.006308978241504892e+00 +1.988264591073880316e+00 +1.970322304903539212e+00 +1.952470841442672578e+00 +1.934699221859771168e+00 +1.916996800625285324e+00 +1.899353300357262020e+00 +1.881758847150196923e+00 +1.864204005858222546e+00 +1.846679814799558450e+00 +1.829177819353787093e+00 +1.811690103957840137e+00 +1.794209321857834105e+00 +1.776728722600446453e+00 +1.759242176092288279e+00 +1.741744193592286383e+00 +1.724229944860523389e+00 +1.706695271340720188e+00 +1.689136695144437095e+00 +1.671551423691038929e+00 +1.653937349923479516e+00 +1.636293048086679081e+00 +1.618617765120738738e+00 +1.600911407783964124e+00 +1.583174525679961864e+00 +1.565408290417325876e+00 +1.547614471178377693e+00 +1.529795407015015707e+00 +1.511953976223662233e+00 +1.494093563177530504e+00 +1.476218023552321013e+00 +1.458331644867988164e+00 +1.440439112061591365e+00 +1.422545465328050485e+00 +1.404656061048842997e+00 +1.386776531806721113e+00 +1.368912746607069852e+00 +1.351070771637138179e+00 +1.333256831865709202e+00 +1.315477273754578347e+00 +1.297738529320441536e+00 +1.280047081752527127e+00 +1.262409432757672700e+00 +1.244832071771888815e+00 +1.227267033177987354e+00 +1.209830330469727988e+00 +1.192473026334426311e+00 +1.175201293632006649e+00 +1.158021169741950240e+00 +1.140938538244850031e+00 +1.123959112684016048e+00 +1.107088422285148477e+00 +1.090331799674984836e+00 +1.073694370408444732e+00 +1.057181044281076510e+00 +1.040796508308895030e+00 +1.024545221277803453e+00 +1.008431409758662189e+00 +9.924590654823298719e-01 +9.766319439688035953e-01 +9.609535643054692988e-01 +9.454272099713439959e-01 +9.300559306069960952e-01 +9.148425446331904798e-01 +8.997896426252972590e-01 +8.848995913548900472e-01 +8.701745384146549744e-01 +8.556164173476724644e-01 +8.412269532070437705e-01 +8.270076684771089370e-01 +8.129598892923184383e-01 +7.990847518949766304e-01 +7.853832092779218232e-01 +7.718560379627628087e-01 +7.585038448691042712e-01 +7.453270742342021915e-01 +7.323260145468937221e-01 +7.195008054632886507e-01 +7.068514446755712610e-01 +6.943777947084666113e-01 +6.820795896213168730e-01 +6.699564415964458508e-01 +6.580078473973850484e-01 From d1f0919d5f3fcc88dc39cec1f8d950c0fa0ace05 Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 21:10:07 +0200 Subject: [PATCH 04/12] added entry to _toc.yml --- docs/_toc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_toc.yml b/docs/_toc.yml index c3f5fc2d..f7bfbbe0 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -46,6 +46,7 @@ parts: - file: photonics/examples/refinement.py - file: electronics/examples/capacitor.py - file: electronics/examples/coax_cable.py + - file: electronics\examples\RF CPW transmission line tutorial\RF CPW transmission line tutorial.py - file: electronics/examples/coplanar_waveguide_vary_width.py - file: electronics/examples/coplanar_waveguide_vary_gap.py From dbba3c2ffbcc7bf2c06d29425f4c553c95f8fd3c Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 21:22:45 +0200 Subject: [PATCH 05/12] test meshtracker.atol --- femwell/mesh/mesh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index 46ffc6af..cd7618dc 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -31,7 +31,7 @@ def break_line_(line, other_line): ): # if type == "", intersection.type != 'Point': if intersection.geom_type == "Point": - line = snap(line, intersection, 0.1) + line = snap(line, intersection, MeshTracker.atol) else: new_coords_start, new_coords_end = intersection.boundary.geoms line = linemerge(split(line, new_coords_start)) From 68b33cad913d44a465a0289f8760d1dbfe6100ea Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 21:34:52 +0200 Subject: [PATCH 06/12] fixed --- femwell/mesh/mesh.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index cd7618dc..cefee45c 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -21,7 +21,7 @@ initial_settings = np.seterr() # remove when shapely updated to more recent geos -def break_line_(line, other_line): +def break_line_(line, other_line, snap_tol = 0.1): np.seterr(invalid="ignore") intersections = line.intersection(other_line) np.seterr(**initial_settings) @@ -31,7 +31,7 @@ def break_line_(line, other_line): ): # if type == "", intersection.type != 'Point': if intersection.geom_type == "Point": - line = snap(line, intersection, MeshTracker.atol) + line = snap(line, intersection, snap_tol) else: new_coords_start, new_coords_end = intersection.boundary.geoms line = linemerge(split(line, new_coords_start)) @@ -204,7 +204,8 @@ def mesh_from_OrderedDict( gmsh.option.setNumber("Mesh.Algorithm", gmsh_algorithm) model = geometry - + meshtracker = MeshTracker(model=model) + # Break up shapes in order so that plane is tiled with non-overlapping layers, overriding shapes according to an order shapes_tiled_dict = OrderedDict() for lower_index, (lower_name, lower_shape) in reversed( @@ -250,8 +251,8 @@ def mesh_from_OrderedDict( else second_shape ) first_exterior_line = break_line_( - first_exterior_line, second_exterior_line - ) + first_exterior_line, second_exterior_line, + snap_tol = meshtracker.atol) # Second line interiors for second_interior_line in ( second_shape.interiors @@ -260,8 +261,8 @@ def mesh_from_OrderedDict( ): second_interior_line = LineString(second_interior_line) first_exterior_line = break_line_( - first_exterior_line, second_interior_line - ) + first_exterior_line, second_interior_line, + snap_tol = meshtracker.atol) # First line interiors if first_shape.geom_type in ["Polygon", "MultiPolygon"]: first_shape_interiors = [] @@ -286,8 +287,8 @@ def mesh_from_OrderedDict( else second_shape ) first_interior_line = break_line_( - first_interior_line, second_exterior_line - ) + first_interior_line, second_exterior_line, + snap_tol = meshtracker.atol) # Interiors for second_interior_line in ( second_shape.interiors @@ -301,8 +302,8 @@ def mesh_from_OrderedDict( ) np.seterr(**initial_settings) first_interior_line = break_line_( - first_interior_line, second_interior_line - ) + first_interior_line, second_interior_line, + snap_tol = meshtracker.atol) first_shape_interiors.append(first_interior_line) if first_shape.geom_type in ["Polygon", "MultiPolygon"]: broken_shapes.append(Polygon(first_exterior_line, holes=first_shape_interiors)) @@ -318,7 +319,7 @@ def mesh_from_OrderedDict( ) # Add lines, reusing line segments - meshtracker = MeshTracker(model=model) + for line_name, line in lines_broken_dict.items(): meshtracker.add_get_xy_line(line, line_name) From 020a8e5e2eca760456e434cd75d7844534905e5f Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 21:45:20 +0200 Subject: [PATCH 07/12] removed default value for snap_tol --- femwell/mesh/mesh.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index cefee45c..118cd48b 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -21,7 +21,7 @@ initial_settings = np.seterr() # remove when shapely updated to more recent geos -def break_line_(line, other_line, snap_tol = 0.1): +def break_line_(line, other_line, snap_tol): np.seterr(invalid="ignore") intersections = line.intersection(other_line) np.seterr(**initial_settings) @@ -252,7 +252,7 @@ def mesh_from_OrderedDict( ) first_exterior_line = break_line_( first_exterior_line, second_exterior_line, - snap_tol = meshtracker.atol) + meshtracker.atol) # Second line interiors for second_interior_line in ( second_shape.interiors @@ -262,7 +262,7 @@ def mesh_from_OrderedDict( second_interior_line = LineString(second_interior_line) first_exterior_line = break_line_( first_exterior_line, second_interior_line, - snap_tol = meshtracker.atol) + meshtracker.atol) # First line interiors if first_shape.geom_type in ["Polygon", "MultiPolygon"]: first_shape_interiors = [] @@ -288,7 +288,7 @@ def mesh_from_OrderedDict( ) first_interior_line = break_line_( first_interior_line, second_exterior_line, - snap_tol = meshtracker.atol) + meshtracker.atol) # Interiors for second_interior_line in ( second_shape.interiors @@ -303,7 +303,7 @@ def mesh_from_OrderedDict( np.seterr(**initial_settings) first_interior_line = break_line_( first_interior_line, second_interior_line, - snap_tol = meshtracker.atol) + meshtracker.atol) first_shape_interiors.append(first_interior_line) if first_shape.geom_type in ["Polygon", "MultiPolygon"]: broken_shapes.append(Polygon(first_exterior_line, holes=first_shape_interiors)) From abc4350a1d6e58e83d8b4ebc1ff6d657707d06e1 Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 22:49:00 +0200 Subject: [PATCH 08/12] removed whitespaces --- .../RF_CPW_transmission_line_tutorial.py} | 0 .../support/ImZ0_delft.txt | 0 .../support/ReK_delft.txt | 0 .../support/ReZ0_delft.txt | 0 .../support/freq_delft.txt | 0 .../support/loss_delft.txt | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename docs/electronics/examples/{RF CPW transmission line tutorial/RF CPW transmission line tutorial.py => RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py} (100%) rename docs/electronics/examples/{RF CPW transmission line tutorial => RF_CPW_transmission_line_tutorial}/support/ImZ0_delft.txt (100%) rename docs/electronics/examples/{RF CPW transmission line tutorial => RF_CPW_transmission_line_tutorial}/support/ReK_delft.txt (100%) rename docs/electronics/examples/{RF CPW transmission line tutorial => RF_CPW_transmission_line_tutorial}/support/ReZ0_delft.txt (100%) rename docs/electronics/examples/{RF CPW transmission line tutorial => RF_CPW_transmission_line_tutorial}/support/freq_delft.txt (100%) rename docs/electronics/examples/{RF CPW transmission line tutorial => RF_CPW_transmission_line_tutorial}/support/loss_delft.txt (100%) diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ImZ0_delft.txt similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/support/ImZ0_delft.txt rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ImZ0_delft.txt diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ReK_delft.txt similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/support/ReK_delft.txt rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ReK_delft.txt diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ReZ0_delft.txt similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/support/ReZ0_delft.txt rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/ReZ0_delft.txt diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/freq_delft.txt similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/support/freq_delft.txt rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/freq_delft.txt diff --git a/docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/loss_delft.txt similarity index 100% rename from docs/electronics/examples/RF CPW transmission line tutorial/support/loss_delft.txt rename to docs/electronics/examples/RF_CPW_transmission_line_tutorial/support/loss_delft.txt From 0a0c63c7fdd14ac9aca25a0c8717fc08b0338a9e Mon Sep 17 00:00:00 2001 From: "Fernandes da Silva, Duarte" Date: Fri, 24 May 2024 22:52:39 +0200 Subject: [PATCH 09/12] replaces backslash in toc.yml --- docs/_toc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_toc.yml b/docs/_toc.yml index f7bfbbe0..00679db5 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -46,7 +46,7 @@ parts: - file: photonics/examples/refinement.py - file: electronics/examples/capacitor.py - file: electronics/examples/coax_cable.py - - file: electronics\examples\RF CPW transmission line tutorial\RF CPW transmission line tutorial.py + - file: electronics/examples/RF CPW transmission line tutorial/RF CPW transmission line tutorial.py - file: electronics/examples/coplanar_waveguide_vary_width.py - file: electronics/examples/coplanar_waveguide_vary_gap.py From 269cbbf724215407e71b0ae22e388814350cb7e9 Mon Sep 17 00:00:00 2001 From: duarte-jfs Date: Sun, 26 May 2024 20:30:48 +0200 Subject: [PATCH 10/12] ran pre-commit --- .../RF_CPW_transmission_line_tutorial.py | 1956 ++++++++++------- femwell/mesh/mesh.py | 28 +- 2 files changed, 1135 insertions(+), 849 deletions(-) diff --git a/docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py index 30c0f99c..e9ddab7c 100644 --- a/docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py +++ b/docs/electronics/examples/RF_CPW_transmission_line_tutorial/RF_CPW_transmission_line_tutorial.py @@ -13,30 +13,30 @@ # name: python3 # --- +import time + # %% from collections import OrderedDict +import enlighten +import numpy as np from matplotlib import pyplot as plt from matplotlib.gridspec import GridSpec -import numpy as np - -from shapely.ops import unary_union, clip_by_rect, linemerge -from shapely.geometry import box, LineString, MultiLineString -from skfem import Basis, ElementTriP0 +from pint import UnitRegistry +from shapely.geometry import LineString, MultiLineString, box +from shapely.ops import clip_by_rect, linemerge, unary_union +from skfem import Basis, ElementTriP0, Functional +from skfem.helpers import inner from skfem.io.meshio import from_meshio -from femwell.maxwell.waveguide import compute_modes, calculate_overlap, calculate_scalar_product +from femwell.maxwell.waveguide import ( + calculate_overlap, + calculate_scalar_product, + compute_modes, +) from femwell.mesh import mesh_from_OrderedDict from femwell.visualization import plot_domains -from skfem import Functional -from skfem.helpers import inner - -from pint import UnitRegistry - -import enlighten - -import time # %matplotlib inline # To make things go smoother I advise to use %matplotlib widget so that you can inspect the mesh and other figures more clearly @@ -71,21 +71,21 @@ # %% reg = UnitRegistry() -#Define frequency range +# Define frequency range freq = np.linspace(0.2, 45, 10) * reg.GHz -omega = 2*np.pi*freq +omega = 2 * np.pi * freq # %% [markdown] # Now some universal constants: # %% ## Define universal constants -mu0 = 4*np.pi * 1e-7 * reg.henry/reg.meter #vacuum magnetic permeability -e0 = 8.854e-12 * reg.farad*reg.meter**-1 -c = 3e8 * reg.meter*reg.second**-1 #m s^-1 -e=1.602176634e-19 * reg.coulomb #Coulombs -kb=1.380649e-23 *reg.meter**2*reg.kg*reg.second**-2*reg.kelvin**-1 -T=300 * reg.kelvin +mu0 = 4 * np.pi * 1e-7 * reg.henry / reg.meter # vacuum magnetic permeability +e0 = 8.854e-12 * reg.farad * reg.meter**-1 +c = 3e8 * reg.meter * reg.second**-1 # m s^-1 +e = 1.602176634e-19 * reg.coulomb # Coulombs +kb = 1.380649e-23 * reg.meter**2 * reg.kg * reg.second**-2 * reg.kelvin**-1 +T = 300 * reg.kelvin # %% [markdown] # For the geometry we will follow the parametrization: @@ -102,16 +102,16 @@ w_gnd = 100 * reg.micrometer t_metal = 0.8 * reg.micrometer -port_width = (w_sig+2*sep+2*w_gnd)*0.5+10 * reg.micrometer #um +port_width = (w_sig + 2 * sep + 2 * w_gnd) * 0.5 + 10 * reg.micrometer # um bottom_height = 100 * reg.micrometer top_height = 100 * reg.micrometer -eps_r_sub = 13 * reg.dimensionless #substrate relative permitivity -sig_metal = 6e5 * reg.siemens/reg.centimeter #Metal conductivity +eps_r_sub = 13 * reg.dimensionless # substrate relative permitivity +sig_metal = 6e5 * reg.siemens / reg.centimeter # Metal conductivity # %% [markdown] -# Next we will define the geometry that `skfem` needs to mesh. We also want to leave the option to use a symmetry plane or not. This will be useful to speed up computations and filter even and odd modes out. Therefore, we define 'use_symmetry_plane' and then tell which plane it is. The way we'll do it is to define the full geometry and then cut all the polygons and lines by that plane. +# Next we will define the geometry that `skfem` needs to mesh. We also want to leave the option to use a symmetry plane or not. This will be useful to speed up computations and filter even and odd modes out. Therefore, we define 'use_symmetry_plane' and then tell which plane it is. The way we'll do it is to define the full geometry and then cut all the polygons and lines by that plane. # # Apart from the geometrical polygons that are included in the image above, we also want to include two additional things: # @@ -123,39 +123,55 @@ # %% ######## Define FEM simulation ########### -use_symmetry_plane = True*0 -symmetry_plane = box(0,-np.inf, np.inf, np.inf) +use_symmetry_plane = True * 0 +symmetry_plane = box(0, -np.inf, np.inf, np.inf) near_field_width = 50 * reg.micrometer near_field_height = 10 * reg.micrometer ########################################## -metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, - 0, - w_sig.to(reg.micrometer).magnitude/2, - t_metal.to(reg.micrometer).magnitude) +metal_sig_box = box( + -w_sig.to(reg.micrometer).magnitude / 2, + 0, + w_sig.to(reg.micrometer).magnitude / 2, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, -(port_width/2).to(reg.micrometer).magnitude), - 0, - (-w_sig/2-sep).to(reg.micrometer).magnitude, - t_metal.to(reg.micrometer).magnitude) +metal_gnd_left_box = box( + max( + (-w_sig / 2 - sep - w_gnd).to(reg.micrometer).magnitude, + -(port_width / 2).to(reg.micrometer).magnitude, + ), + 0, + (-w_sig / 2 - sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, - 0, - min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, (port_width/2).to(reg.micrometer).magnitude), - t_metal.to(reg.micrometer).magnitude) +metal_gnd_right_box = box( + (w_sig / 2 + sep).to(reg.micrometer).magnitude, + 0, + min( + (w_sig / 2 + sep + w_gnd).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + ), + t_metal.to(reg.micrometer).magnitude, +) -air_box = box((-port_width/2).to(reg.micrometer).magnitude, - 0, - (port_width/2).to(reg.micrometer).magnitude, - top_height.to(reg.micrometer).magnitude) +air_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + 0, + (port_width / 2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude, +) -substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, - -(bottom_height).to(reg.micrometer).magnitude, - (port_width/2).to(reg.micrometer).magnitude, - 0) +substrate_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + 0, +) surface = unary_union([air_box, substrate_box]) @@ -164,10 +180,12 @@ xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmax_1, (ymax_1+ymin_1)/2), - (xmin_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmax_1, (ymax_1 + ymin_1) / 2), + (xmin_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_right = LineString(points) @@ -175,100 +193,108 @@ xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmin_1, (ymax_1+ymin_1)/2), - (xmax_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmin_1, (ymax_1 + ymin_1) / 2), + (xmax_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_left = LineString(points) polygons = OrderedDict( - surface = LineString(surface.exterior), - metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_sig.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - path_integral_right = path_integral_right, - path_integral_left = path_integral_left, - - metal_sig = metal_sig_box, - metal_gnd_left = metal_gnd_left_box, - metal_gnd_right = metal_gnd_right_box, - air = air_box, - substrate = substrate_box - ) + surface=LineString(surface.exterior), + metal_sig_interface=LineString( + clip_by_rect( + metal_sig_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_sig.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_left_interface=LineString( + clip_by_rect( + metal_gnd_left_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_right_interface=LineString( + clip_by_rect( + metal_gnd_right_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + path_integral_right=path_integral_right, + path_integral_left=path_integral_left, + metal_sig=metal_sig_box, + metal_gnd_left=metal_gnd_left_box, + metal_gnd_right=metal_gnd_right_box, + air=air_box, + substrate=substrate_box, +) - if use_symmetry_plane: keys_to_pop = [] - for key,poly in polygons.items(): + for key, poly in polygons.items(): if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): - poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) - + poly_tmp = clip_by_rect(poly, *symmetry_plane.bounds) + if type(poly_tmp) == MultiLineString: polygons[key] = linemerge(poly_tmp) - + elif poly_tmp.is_empty: keys_to_pop.append(key) else: polygons[key] = poly_tmp elif not poly.intersects(symmetry_plane): keys_to_pop.append(key) - + for key in keys_to_pop: polygons.pop(key) - -#Add the boundary polygons so that you can set custom boundary conditions -surf_bounds = polygons['surface'].bounds -left = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[0], surf_bounds[3])]) +# Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons["surface"].bounds -bottom = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[2], surf_bounds[1])]) +left = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[0], surf_bounds[3])]) -right = LineString([(surf_bounds[2], surf_bounds[1]), - (surf_bounds[2], surf_bounds[3])]) +bottom = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[2], surf_bounds[1])]) -top = LineString([(surf_bounds[0], surf_bounds[3]), - (surf_bounds[2], surf_bounds[3])]) +right = LineString([(surf_bounds[2], surf_bounds[1]), (surf_bounds[2], surf_bounds[3])]) -polygons['left'] = left -polygons['bottom'] = bottom -polygons['right'] = right -polygons['top'] = top +top = LineString([(surf_bounds[0], surf_bounds[3]), (surf_bounds[2], surf_bounds[3])]) -polygons.move_to_end('top', last = False) -polygons.move_to_end('right', last = False) -polygons.move_to_end('bottom', last = False) -polygons.move_to_end('left', last = False) +polygons["left"] = left +polygons["bottom"] = bottom +polygons["right"] = right +polygons["top"] = top -polygons.pop('surface') +polygons.move_to_end("top", last=False) +polygons.move_to_end("right", last=False) +polygons.move_to_end("bottom", last=False) +polygons.move_to_end("left", last=False) -resolutions = dict( - surface = {'resolution': 100, 'distance': 1}, - metal_sig_interface = {'resolution': 0.1, 'distance': 10}, - metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, - path_integral_right = {'resolution': 0.2, 'distance': 10}, - path_integral_left = {'resolution': 0.2, 'distance': 10}, - metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, - metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - air = {'resolution': 10, 'distance': 0.1}, - substrate = {'resolution': 10, 'distance': 1}, - ) +polygons.pop("surface") - +resolutions = dict( + surface={"resolution": 100, "distance": 1}, + metal_sig_interface={"resolution": 0.1, "distance": 10}, + metal_gnd_interface={"resolution": 0.5, "distance": 10}, + path_integral_right={"resolution": 0.2, "distance": 10}, + path_integral_left={"resolution": 0.2, "distance": 10}, + metal_sig={"resolution": 0.1, "distance": 0.1, "SizeMax": 0.1}, + metal_gnd_left={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + metal_gnd_right={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + air={"resolution": 10, "distance": 0.1}, + substrate={"resolution": 10, "distance": 1}, +) # %% [markdown] @@ -280,8 +306,8 @@ for key, polygon in polygons.items(): if type(polygon) is not LineString: - x,y = polygon.exterior.xy - ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + x, y = polygon.exterior.xy + ax.plot(np.asarray(x), np.asarray(y), color="pink") else: ax.plot(*polygon.xy) @@ -296,11 +322,10 @@ # Now we mesh it # %% -#Mesh it -mesh = from_meshio(mesh_from_OrderedDict(polygons, - resolutions, - default_resolution_max = 100, - verbose = True)) +# Mesh it +mesh = from_meshio( + mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=100, verbose=True) +) print(mesh.nelements) @@ -309,7 +334,7 @@ fig.axes.set_axis_on() # %% [markdown] -# Note that we are using a **very** fine mesh. Normally, if we were using thick metals then surface impedance would suffice and we wouldn't have to mesh the entire surface of the metal. However, in this case, the metal is far too thin and the skin depth of the frequencies we are using is far too big to neglect current flowing inside the metal. Sadly, the profile of $I(x,y)$ inside the metal is rapidly changing, so we need the very fine mesh. +# Note that we are using a **very** fine mesh. Normally, if we were using thick metals then surface impedance would suffice and we wouldn't have to mesh the entire surface of the metal. However, in this case, the metal is far too thin and the skin depth of the frequencies we are using is far too big to neglect current flowing inside the metal. Sadly, the profile of $I(x,y)$ inside the metal is rapidly changing, so we need the very fine mesh. # # How do we know that it is fine enough? We won't do that here, but for a thorough study we advise to do a sweep on the resolution inside the metal and keep checking the absorption of the modes. Once it converges, you're good. # @@ -328,22 +353,28 @@ # %% idx_freq = -1 -print(f'Frequency:{freq[idx_freq].magnitude:.2f} GHz') +print(f"Frequency:{freq[idx_freq].magnitude:.2f} GHz") -basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM -epsilon = basis0.ones(dtype = complex) +basis0 = Basis(mesh, ElementTriP0(), intorder=4) # Define the basis for the FEM +epsilon = basis0.ones(dtype=complex) -epsilon[basis0.get_dofs(elements = 'air')] = 1 -epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub -epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) -epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) -epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements="air")] = 1 +epsilon[basis0.get_dofs(elements="substrate")] = eps_r_sub +epsilon[basis0.get_dofs(elements="metal_sig")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude +) +epsilon[basis0.get_dofs(elements="metal_gnd_left")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude +) +epsilon[basis0.get_dofs(elements="metal_gnd_right")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude +) # %% -fig = basis0.plot(epsilon.real, colorbar = True) +fig = basis0.plot(epsilon.real, colorbar=True) fig.axes.set_axis_on() -fig = basis0.plot(epsilon.imag, colorbar = True) +fig = basis0.plot(epsilon.imag, colorbar=True) fig.axes.set_axis_on() # %% [markdown] @@ -352,107 +383,133 @@ # %% start = time.time() modes = compute_modes( - basis0, - epsilon, - wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, - num_modes=2, - metallic_boundaries=False, - ) + basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=2, + metallic_boundaries=False, +) stop = time.time() -modes = modes.sorted(key = lambda mode: mode.n_eff.real) +modes = modes.sorted(key=lambda mode: mode.n_eff.real) -print(stop-start) +print(stop - start) # %% print(modes.n_effs.real) -print(20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude, 'dB/cm') +print( + 20 + / np.log(10) + * (modes.n_effs.imag * 2 * np.pi * freq[idx_freq] / c).to(reg.centimeter**-1).magnitude, + "dB/cm", +) # %% [markdown] # We should now inspect the modes. You can use `modes[i].plot(modes.E)` or even plot a single component with `modes[i].plot_component('E', 'x')`, but we're looking for something more custom, I will export the data onto a rectangular grid and plot a streamplot to visualize the field lines: # %% -from skfem import ( - ElementVector, - ElementDG, - ElementTriP1 -) +from skfem import ElementDG, ElementTriP1, ElementVector # Trying interpolator Nx = 50 Ny = 50 -grid_data_E = np.zeros((2, Ny, Nx, 3), dtype = complex) -grid_data_H = np.zeros((2, Ny, Nx, 3), dtype = complex) +grid_data_E = np.zeros((2, Ny, Nx, 3), dtype=complex) +grid_data_H = np.zeros((2, Ny, Nx, 3), dtype=complex) xmin = -40 xmax = 40 ymin = -10 ymax = 10 -grid_x, grid_y = np.meshgrid(np.linspace(xmin,xmax,Nx), np.linspace(ymin, ymax, Ny)) +grid_x, grid_y = np.meshgrid(np.linspace(xmin, xmax, Nx), np.linspace(ymin, ymax, Ny)) for i, mode in enumerate(modes): basis = mode.basis basis_fix = basis.with_element(ElementVector(ElementDG(ElementTriP1()))) (et, et_basis), (ez, ez_basis) = basis.split(mode.E) - (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) - + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split( + basis_fix.project(et_basis.interpolate(et)) + ) coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) start = time.time() - grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), - et_y_basis.interpolator(et_y)(coordinates), - ez_basis.interpolator(ez)(coordinates))).T + grid_data = np.array( + ( + et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates), + ) + ).T grid_data_E[i] = grid_data.reshape((*grid_x.shape, -1)) end = time.time() - print(end-start) + print(end - start) (et, et_basis), (ez, ez_basis) = basis.split(mode.H) - (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split( + basis_fix.project(et_basis.interpolate(et)) + ) coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) - - grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), - et_y_basis.interpolator(et_y)(coordinates), - ez_basis.interpolator(ez)(coordinates))).T + grid_data = np.array( + ( + et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates), + ) + ).T grid_data_H[i] = grid_data.reshape((*grid_x.shape, -1)) # %% -fig = plt.figure(figsize = (7,5)) -gs = GridSpec(2,2) - -ax_even_E = fig.add_subplot(gs[0,0]) -ax_odd_E = fig.add_subplot(gs[0,1]) - -ax_even_H = fig.add_subplot(gs[1,0]) -ax_odd_H = fig.add_subplot(gs[1,1]) - -for ax, data, label in zip([ax_even_E, ax_odd_E, ax_even_H, ax_odd_H], - [grid_data_E[0], grid_data_E[1], grid_data_H[0], grid_data_H[1]], - [r'Mode 0 | $|E(x,y)|$', r'Mode 1 | $|E(x,y)|$', r'Mode 0 | $|H(x,y)|$', r'Mode 1 | $|H(x,y)|$']): - - ax.imshow(np.sum(np.abs(data), axis = 2), origin = 'lower', - extent = [xmin,xmax,ymin,ymax], cmap = 'jet', interpolation = 'bicubic', aspect = 'auto') - ax.streamplot(grid_x, grid_y, data.real[:,:,0], data.real[:,:,1], color = 'black', linewidth = 0.5) +fig = plt.figure(figsize=(7, 5)) +gs = GridSpec(2, 2) + +ax_even_E = fig.add_subplot(gs[0, 0]) +ax_odd_E = fig.add_subplot(gs[0, 1]) + +ax_even_H = fig.add_subplot(gs[1, 0]) +ax_odd_H = fig.add_subplot(gs[1, 1]) + +for ax, data, label in zip( + [ax_even_E, ax_odd_E, ax_even_H, ax_odd_H], + [grid_data_E[0], grid_data_E[1], grid_data_H[0], grid_data_H[1]], + [ + r"Mode 0 | $|E(x,y)|$", + r"Mode 1 | $|E(x,y)|$", + r"Mode 0 | $|H(x,y)|$", + r"Mode 1 | $|H(x,y)|$", + ], +): + + ax.imshow( + np.sum(np.abs(data), axis=2), + origin="lower", + extent=[xmin, xmax, ymin, ymax], + cmap="jet", + interpolation="bicubic", + aspect="auto", + ) + ax.streamplot( + grid_x, grid_y, data.real[:, :, 0], data.real[:, :, 1], color="black", linewidth=0.5 + ) for key, polygon in polygons.items(): if type(polygon) is not LineString: - x,y = polygon.exterior.xy - ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + x, y = polygon.exterior.xy + ax.plot(np.asarray(x), np.asarray(y), color="pink") ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) - ax.set_xlabel('x (um)') - ax.set_ylabel('y (um)') - + ax.set_xlabel("x (um)") + ax.set_ylabel("y (um)") + ax.set_title(label) - + fig.tight_layout() # fig.savefig('modes.png', dpi = 400) @@ -538,49 +595,58 @@ # # Let us test this: + # %% @Functional(dtype=np.complex64) def current_form(w): - ''' + """ What this does is it takes the normal vector to the boundary and rotates it 90deg Then takes the inner product with the magnetic field in it's complex form - ''' + """ return inner(np.array([w.n[1], -w.n[0]]), w.H) + @Functional(dtype=np.complex64) def voltage_form(w): - ''' + """ What this does is it takes the normal vector to the boundary and rotates it 90deg Then takes the inner product with the electric field in it's complex form - ''' - + """ + return -inner(np.array([w.n[1], -w.n[0]]), w.E) -conductor = 'metal_sig_interface' -line = 'path_integral_right' + +conductor = "metal_sig_interface" +line = "path_integral_right" mode = modes[0] -p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), - mode.basis,np.conjugate(mode.H)) #The conjugate is due to femwell internal definition as conj(E) x H +p0 = calculate_scalar_product( + mode.basis, np.conjugate(mode.E), mode.basis, np.conjugate(mode.H) +) # The conjugate is due to femwell internal definition as conj(E) x H (ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) -i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) +i0 = current_form.assemble(facet_basis, H=facet_basis.interpolate(ht)) (et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) facet_basis = et_basis.boundary(facets=mesh.boundaries[line]) -v0 = voltage_form.assemble(facet_basis,E=facet_basis.interpolate(et)) - +v0 = voltage_form.assemble(facet_basis, E=facet_basis.interpolate(et)) -print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') -print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') -print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') +print( + f"PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians" +) +print( + f"PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians" +) +print( + f"VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians" +) # %% [markdown] -# As you can see all the model give a very close result for the characteristic impedance. +# As you can see all the model give a very close result for the characteristic impedance. # # Another interesting aspect of waveguide circuit theory is the fact that it can be shown that, under the quasi-TEM approximation, it is possible to **analytically** extract the RLGC parameters of a circuit via the field solutions. This fits perfectly with the FEM solutions as the evaluation of the integrals is straightforward. The parameters are extracted as: # @@ -600,105 +666,152 @@ def voltage_form(w): # R = \frac{\omega}{|i_0|^2}\left[ \int_S \mu^{\prime\prime} |\mathbf{h}_t|^2 dS + \int_S \epsilon^{\prime\prime} |\mathbf{e}_z|^2 dS \right] # $$ + # %% @Functional(dtype=np.complex64) def C_form(w): - return 1/np.abs(w.v0)**2*(np.real(w.epsilon)*inner(w.et, np.conj(w.et)) - - np.real(w.mu) * inner(w.hz, np.conj(w.hz))) + return ( + 1 + / np.abs(w.v0) ** 2 + * ( + np.real(w.epsilon) * inner(w.et, np.conj(w.et)) + - np.real(w.mu) * inner(w.hz, np.conj(w.hz)) + ) + ) + @Functional(dtype=np.complex64) def L_form(w): - return 1/np.abs(w.i0)**2*(np.real(w.mu)*inner(w.ht, np.conj(w.ht)) - - np.real(w.epsilon)*inner(w.ez, np.conj(w.ez))) + return ( + 1 + / np.abs(w.i0) ** 2 + * ( + np.real(w.mu) * inner(w.ht, np.conj(w.ht)) + - np.real(w.epsilon) * inner(w.ez, np.conj(w.ez)) + ) + ) + @Functional(dtype=np.complex64) def G_form(w): - #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i - #whereas with python we have eps = eps_r+1j*eps_i. - return -w.omega/np.abs(w.v0)**2*(np.imag(w.epsilon)*inner(w.et, np.conj(w.et))+ - np.imag(w.mu) * inner(w.hz, np.conj(w.hz))) + # The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + # whereas with python we have eps = eps_r+1j*eps_i. + return ( + -w.omega + / np.abs(w.v0) ** 2 + * ( + np.imag(w.epsilon) * inner(w.et, np.conj(w.et)) + + np.imag(w.mu) * inner(w.hz, np.conj(w.hz)) + ) + ) + @Functional(dtype=np.complex64) def R_form(w): - #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i - #whereas with python we have eps = eps_r+1j*eps_i. - return -w.omega/np.abs(w.i0)**2*(np.imag(w.epsilon)*inner(w.ez, np.conj(w.ez)) + - np.imag(w.mu) * inner(w.ht, np.conj(w.ht))) + # The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + # whereas with python we have eps = eps_r+1j*eps_i. + return ( + -w.omega + / np.abs(w.i0) ** 2 + * ( + np.imag(w.epsilon) * inner(w.ez, np.conj(w.ez)) + + np.imag(w.mu) * inner(w.ht, np.conj(w.ht)) + ) + ) + basis_t = et_basis basis_z = ez_basis basis_eps = basis0 -#Be careful with the units!! -#In this case just make sure you adjust the length unit to micrometers -#everything else can stay as is. - -C=C_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -L=L_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -G=G_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -R=R_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) - -gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) - -print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') -print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') -print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') -print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') - -print('=================== RLGC ==================') -print(f'R = {R.real/1e-3:.2f} mOhm/um') -print(f'L = {L.real/1e-12:.2f} picoHenry/um') -print(f'G = {G.real/1e-12:.2f} picoSiemens/um') -print(f'C = {C.real/1e-15:.2f} femtoFarad/um') - -print('================== propagation constant ===============') -print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') -print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') +# Be careful with the units!! +# In this case just make sure you adjust the length unit to micrometers +# everything else can stay as is. + +C = C_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +L = L_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +G = G_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +R = R_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +Z0 = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + / (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) + +gamma = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + * (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) + +print( + f"PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians" +) +print( + f"PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians" +) +print( + f"VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians" +) +print(f"RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians") + +print("=================== RLGC ==================") +print(f"R = {R.real/1e-3:.2f} mOhm/um") +print(f"L = {L.real/1e-12:.2f} picoHenry/um") +print(f"G = {G.real/1e-12:.2f} picoSiemens/um") +print(f"C = {C.real/1e-15:.2f} femtoFarad/um") + +print("================== propagation constant ===============") +print(f"beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ") +print(f"alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ") # %% [markdown] # While there are some slight differences, the results match quite well!! Now, let us try to repeat the process but including a symmetry plane. @@ -709,39 +822,55 @@ def R_form(w): # %% ######## Define FEM simulation ########### use_symmetry_plane = True -symmetry_plane = box(0,-np.inf, np.inf, np.inf) +symmetry_plane = box(0, -np.inf, np.inf, np.inf) near_field_width = 50 * reg.micrometer near_field_height = 10 * reg.micrometer ########################################## -metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, - 0, - w_sig.to(reg.micrometer).magnitude/2, - t_metal.to(reg.micrometer).magnitude) +metal_sig_box = box( + -w_sig.to(reg.micrometer).magnitude / 2, + 0, + w_sig.to(reg.micrometer).magnitude / 2, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, -(port_width/2).to(reg.micrometer).magnitude), - 0, - (-w_sig/2-sep).to(reg.micrometer).magnitude, - t_metal.to(reg.micrometer).magnitude) +metal_gnd_left_box = box( + max( + (-w_sig / 2 - sep - w_gnd).to(reg.micrometer).magnitude, + -(port_width / 2).to(reg.micrometer).magnitude, + ), + 0, + (-w_sig / 2 - sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, - 0, - min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, (port_width/2).to(reg.micrometer).magnitude), - t_metal.to(reg.micrometer).magnitude) +metal_gnd_right_box = box( + (w_sig / 2 + sep).to(reg.micrometer).magnitude, + 0, + min( + (w_sig / 2 + sep + w_gnd).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + ), + t_metal.to(reg.micrometer).magnitude, +) -air_box = box((-port_width/2).to(reg.micrometer).magnitude, - 0, - (port_width/2).to(reg.micrometer).magnitude, - top_height.to(reg.micrometer).magnitude) +air_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + 0, + (port_width / 2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude, +) -substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, - -(bottom_height).to(reg.micrometer).magnitude, - (port_width/2).to(reg.micrometer).magnitude, - 0) +substrate_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + 0, +) surface = unary_union([air_box, substrate_box]) @@ -750,10 +879,12 @@ def R_form(w): xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmax_1, (ymax_1+ymin_1)/2), - (xmin_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmax_1, (ymax_1 + ymin_1) / 2), + (xmin_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_right = LineString(points) @@ -761,102 +892,112 @@ def R_form(w): xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmin_1, (ymax_1+ymin_1)/2), - (xmax_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmin_1, (ymax_1 + ymin_1) / 2), + (xmax_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_left = LineString(points) polygons = OrderedDict( - surface = LineString(surface.exterior), - metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_sig.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - path_integral_right = path_integral_right, - path_integral_left = path_integral_left, - - metal_sig = metal_sig_box, - metal_gnd_left = metal_gnd_left_box, - metal_gnd_right = metal_gnd_right_box, - air = air_box, - substrate = substrate_box - ) + surface=LineString(surface.exterior), + metal_sig_interface=LineString( + clip_by_rect( + metal_sig_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_sig.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_left_interface=LineString( + clip_by_rect( + metal_gnd_left_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_right_interface=LineString( + clip_by_rect( + metal_gnd_right_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + path_integral_right=path_integral_right, + path_integral_left=path_integral_left, + metal_sig=metal_sig_box, + metal_gnd_left=metal_gnd_left_box, + metal_gnd_right=metal_gnd_right_box, + air=air_box, + substrate=substrate_box, +) - if use_symmetry_plane: keys_to_pop = [] - for key,poly in polygons.items(): + for key, poly in polygons.items(): if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): - poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) - + poly_tmp = clip_by_rect(poly, *symmetry_plane.bounds) + if type(poly_tmp) == MultiLineString: polygons[key] = linemerge(poly_tmp) - + elif poly_tmp.is_empty: keys_to_pop.append(key) else: polygons[key] = poly_tmp elif not poly.intersects(symmetry_plane): keys_to_pop.append(key) - + for key in keys_to_pop: polygons.pop(key) -#Add the boundary polygons so that you can set custom boundary conditions -surf_bounds = polygons['surface'].bounds +# Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons["surface"].bounds -left = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[0], surf_bounds[3])]) +left = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[0], surf_bounds[3])]) -bottom = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[2], surf_bounds[1])]) +bottom = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[2], surf_bounds[1])]) -right = LineString([(surf_bounds[2], surf_bounds[1]), - (surf_bounds[2], surf_bounds[3])]) +right = LineString([(surf_bounds[2], surf_bounds[1]), (surf_bounds[2], surf_bounds[3])]) -top = LineString([(surf_bounds[0], surf_bounds[3]), - (surf_bounds[2], surf_bounds[3])]) +top = LineString([(surf_bounds[0], surf_bounds[3]), (surf_bounds[2], surf_bounds[3])]) -polygons['left'] = left -polygons['bottom'] = bottom -polygons['right'] = right -polygons['top'] = top +polygons["left"] = left +polygons["bottom"] = bottom +polygons["right"] = right +polygons["top"] = top -polygons.move_to_end('top', last = False) -polygons.move_to_end('right', last = False) -polygons.move_to_end('bottom', last = False) -polygons.move_to_end('left', last = False) +polygons.move_to_end("top", last=False) +polygons.move_to_end("right", last=False) +polygons.move_to_end("bottom", last=False) +polygons.move_to_end("left", last=False) -polygons.pop('surface') +polygons.pop("surface") resolutions = dict( - metal_sig_interface = {'resolution': 0.1, 'distance': 10}, - metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, - path_integral_right = {'resolution': 0.2, 'distance': 10}, - path_integral_left = {'resolution': 0.2, 'distance': 10}, - metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, - metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - left = {'resolution': 10, 'distance': 0.1}, - right = {'resolution': 10, 'distance': 0.1}, - top = {'resolution': 10, 'distance': 0.1}, - bottom = {'resolution': 10, 'distance': 0.1}, - air = {'resolution': 10, 'distance': 0.1}, - substrate = {'resolution': 10, 'distance': 1}, - ) + metal_sig_interface={"resolution": 0.1, "distance": 10}, + metal_gnd_interface={"resolution": 0.5, "distance": 10}, + path_integral_right={"resolution": 0.2, "distance": 10}, + path_integral_left={"resolution": 0.2, "distance": 10}, + metal_sig={"resolution": 0.1, "distance": 0.1, "SizeMax": 0.1}, + metal_gnd_left={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + metal_gnd_right={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + left={"resolution": 10, "distance": 0.1}, + right={"resolution": 10, "distance": 0.1}, + top={"resolution": 10, "distance": 0.1}, + bottom={"resolution": 10, "distance": 0.1}, + air={"resolution": 10, "distance": 0.1}, + substrate={"resolution": 10, "distance": 1}, +) # %% fig = plt.figure() @@ -865,18 +1006,17 @@ def R_form(w): for key, polygon in polygons.items(): # print(polygon) if type(polygon) is not LineString: - x,y = polygon.exterior.xy - ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + x, y = polygon.exterior.xy + ax.plot(np.asarray(x), np.asarray(y), color="pink") else: ax.plot(*polygon.xy) # %% -#Mesh it -mesh = from_meshio(mesh_from_OrderedDict(polygons, - resolutions, - default_resolution_max = 100, - verbose = True)) +# Mesh it +mesh = from_meshio( + mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=100, verbose=True) +) print(mesh.nelements) @@ -885,117 +1025,143 @@ def R_form(w): # %% idx_freq = -1 -print(f'Frequency:{freq[idx_freq].magnitude:.2f} GHz') +print(f"Frequency:{freq[idx_freq].magnitude:.2f} GHz") -basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM -epsilon = basis0.ones(dtype = complex) +basis0 = Basis(mesh, ElementTriP0(), intorder=4) # Define the basis for the FEM +epsilon = basis0.ones(dtype=complex) -epsilon[basis0.get_dofs(elements = 'air')] = 1 -epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub -epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements="air")] = 1 +epsilon[basis0.get_dofs(elements="substrate")] = eps_r_sub +epsilon[basis0.get_dofs(elements="metal_sig")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude +) # epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) -epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) +epsilon[basis0.get_dofs(elements="metal_gnd_right")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude +) # %% start = time.time() -modes = compute_modes(basis0, - epsilon, - wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, - num_modes=1, - metallic_boundaries=[] - ) - +modes = compute_modes( + basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=1, + metallic_boundaries=[], +) + stop = time.time() -print(stop-start) +print(stop - start) # %% print(modes.n_effs.real) -print(20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude, 'dB/cm') +print( + 20 + / np.log(10) + * (modes.n_effs.imag * 2 * np.pi * freq[idx_freq] / c).to(reg.centimeter**-1).magnitude, + "dB/cm", +) # %% [markdown] # Much faster this time round # %% -from skfem import ( - ElementVector, - ElementDG, - ElementTriP1 -) +from skfem import ElementDG, ElementTriP1, ElementVector # Trying interpolator Nx = 25 Ny = 50 -grid_data_E = np.zeros((len(modes), Ny, Nx, 3), dtype = complex) -grid_data_H = np.zeros((len(modes), Ny, Nx, 3), dtype = complex) +grid_data_E = np.zeros((len(modes), Ny, Nx, 3), dtype=complex) +grid_data_H = np.zeros((len(modes), Ny, Nx, 3), dtype=complex) xmin = 0 xmax = 40 ymin = -10 ymax = 10 -grid_x, grid_y = np.meshgrid(np.linspace(xmin,xmax,Nx), np.linspace(ymin, ymax, Ny)) +grid_x, grid_y = np.meshgrid(np.linspace(xmin, xmax, Nx), np.linspace(ymin, ymax, Ny)) for i, mode in enumerate(modes): basis = mode.basis basis_fix = basis.with_element(ElementVector(ElementDG(ElementTriP1()))) (et, et_basis), (ez, ez_basis) = basis.split(mode.E) - (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) - + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split( + basis_fix.project(et_basis.interpolate(et)) + ) coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) start = time.time() - grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), - et_y_basis.interpolator(et_y)(coordinates), - ez_basis.interpolator(ez)(coordinates))).T + grid_data = np.array( + ( + et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates), + ) + ).T grid_data_E[i] = grid_data.reshape((*grid_x.shape, -1)) end = time.time() - print(end-start) + print(end - start) (et, et_basis), (ez, ez_basis) = basis.split(mode.H) - (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split(basis_fix.project(et_basis.interpolate(et))) + (et_x, et_x_basis), (et_y, et_y_basis) = basis_fix.split( + basis_fix.project(et_basis.interpolate(et)) + ) coordinates = np.array([grid_x.flatten(), grid_y.flatten()]) - - grid_data = np.array((et_x_basis.interpolator(et_x)(coordinates), - et_y_basis.interpolator(et_y)(coordinates), - ez_basis.interpolator(ez)(coordinates))).T + grid_data = np.array( + ( + et_x_basis.interpolator(et_x)(coordinates), + et_y_basis.interpolator(et_y)(coordinates), + ez_basis.interpolator(ez)(coordinates), + ) + ).T grid_data_H[i] = grid_data.reshape((*grid_x.shape, -1)) # %% -fig = plt.figure(figsize = (4,5)) -gs = GridSpec(2,1) +fig = plt.figure(figsize=(4, 5)) +gs = GridSpec(2, 1) -ax_even_E = fig.add_subplot(gs[0,0]) -ax_even_H = fig.add_subplot(gs[1,0]) +ax_even_E = fig.add_subplot(gs[0, 0]) +ax_even_H = fig.add_subplot(gs[1, 0]) mode_idx = 0 -for ax, data, label in zip([ax_even_E, ax_even_H], - [grid_data_E[mode_idx], grid_data_H[mode_idx]], - [r'Even mode | $|E(x,y)|$', r'Even mode | $|H(x,y)|$']): - - ax.imshow(np.sum(np.abs(data), axis = 2), origin = 'lower', - extent = [xmin,xmax,ymin,ymax], cmap = 'jet', interpolation = 'bicubic', aspect = 'auto') - ax.streamplot(grid_x, grid_y, data.real[:,:,0], data.real[:,:,1], color = 'black', linewidth = 0.5) +for ax, data, label in zip( + [ax_even_E, ax_even_H], + [grid_data_E[mode_idx], grid_data_H[mode_idx]], + [r"Even mode | $|E(x,y)|$", r"Even mode | $|H(x,y)|$"], +): + + ax.imshow( + np.sum(np.abs(data), axis=2), + origin="lower", + extent=[xmin, xmax, ymin, ymax], + cmap="jet", + interpolation="bicubic", + aspect="auto", + ) + ax.streamplot( + grid_x, grid_y, data.real[:, :, 0], data.real[:, :, 1], color="black", linewidth=0.5 + ) for key, polygon in polygons.items(): if type(polygon) is not LineString: - x,y = polygon.exterior.xy - ax.plot(np.asarray(x),np.asarray(y), color = 'pink') + x, y = polygon.exterior.xy + ax.plot(np.asarray(x), np.asarray(y), color="pink") ax.set_xlim(xmin, xmax) ax.set_ylim(ymin, ymax) - ax.set_xlabel('x (um)') - ax.set_ylabel('y (um)') - + ax.set_xlabel("x (um)") + ax.set_ylabel("y (um)") + ax.set_title(label) - + fig.tight_layout() # fig.savefig('modes_sym.png', dpi = 400) @@ -1008,178 +1174,237 @@ def R_form(w): # # + # %% @Functional(dtype=np.complex64) def current_form(w): - ''' + """ What this does is it takes the normal vector to the boundary and rotates it 90deg Then takes the inner product with the magnetic field in it's complex form - ''' + """ return inner(np.array([w.n[1], -w.n[0]]), w.H) + @Functional(dtype=np.complex64) def voltage_form(w): - ''' + """ What this does is it takes the normal vector to the boundary and rotates it 90deg Then takes the inner product with the electric field in it's complex form - ''' - + """ + return -inner(np.array([w.n[1], -w.n[0]]), w.E) -conductor = 'metal_sig_interface' -line = 'path_integral_right' + +conductor = "metal_sig_interface" +line = "path_integral_right" mode = modes[0] -p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), - mode.basis,np.conjugate(mode.H)) +p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), mode.basis, np.conjugate(mode.H)) print(p0) -#The conjugate is due to femwell internal definition as conj(E) x H -#The factor of 2 is to account for the fact that we are working with half the field only +# The conjugate is due to femwell internal definition as conj(E) x H +# The factor of 2 is to account for the fact that we are working with half the field only (ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) -i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) +i0 = current_form.assemble(facet_basis, H=facet_basis.interpolate(ht)) (et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) facet_basis = et_basis.boundary(facets=mesh.boundaries[line]) -v0 = voltage_form.assemble(facet_basis,E=facet_basis.interpolate(et)) - +v0 = voltage_form.assemble(facet_basis, E=facet_basis.interpolate(et)) -v0 = p0/i0.conj() -print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') -print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') -print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') + +v0 = p0 / i0.conj() +print( + f"PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians" +) +print( + f"PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians" +) +print( + f"VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians" +) # %% [markdown] # The fact that the Z0 is now double as before follows from the conclusion above. + # %% @Functional(dtype=np.complex64) def C_form(w): - return 1/np.abs(w.v0)**2*(np.real(w.epsilon)*inner(w.et, np.conj(w.et)) - - np.real(w.mu) * inner(w.hz, np.conj(w.hz))) + return ( + 1 + / np.abs(w.v0) ** 2 + * ( + np.real(w.epsilon) * inner(w.et, np.conj(w.et)) + - np.real(w.mu) * inner(w.hz, np.conj(w.hz)) + ) + ) + @Functional(dtype=np.complex64) def L_form(w): - return 1/np.abs(w.i0)**2*(np.real(w.mu)*inner(w.ht, np.conj(w.ht)) - - np.real(w.epsilon)*inner(w.ez, np.conj(w.ez))) + return ( + 1 + / np.abs(w.i0) ** 2 + * ( + np.real(w.mu) * inner(w.ht, np.conj(w.ht)) + - np.real(w.epsilon) * inner(w.ez, np.conj(w.ez)) + ) + ) + @Functional(dtype=np.complex64) def G_form(w): - #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i - #whereas with python we have eps = eps_r+1j*eps_i. - return -w.omega/np.abs(w.v0)**2*(np.imag(w.epsilon)*inner(w.et, np.conj(w.et))+ - np.imag(w.mu) * inner(w.hz, np.conj(w.hz))) + # The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + # whereas with python we have eps = eps_r+1j*eps_i. + return ( + -w.omega + / np.abs(w.v0) ** 2 + * ( + np.imag(w.epsilon) * inner(w.et, np.conj(w.et)) + + np.imag(w.mu) * inner(w.hz, np.conj(w.hz)) + ) + ) + @Functional(dtype=np.complex64) def R_form(w): - #The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i - #whereas with python we have eps = eps_r+1j*eps_i. - return -w.omega/np.abs(w.i0)**2*(np.imag(w.epsilon)*inner(w.ez, np.conj(w.ez)) + - np.imag(w.mu) * inner(w.ht, np.conj(w.ht))) + # The minus sign account for the fact that in the paper they define eps = eps_r-1j*eps_i + # whereas with python we have eps = eps_r+1j*eps_i. + return ( + -w.omega + / np.abs(w.i0) ** 2 + * ( + np.imag(w.epsilon) * inner(w.ez, np.conj(w.ez)) + + np.imag(w.mu) * inner(w.ht, np.conj(w.ht)) + ) + ) + basis_t = et_basis basis_z = ez_basis basis_eps = basis0 -#Be careful with the units!! -#In this case just make sure you adjust the length unit to micrometers -#everything else can stay as is. - -C=C_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -L=L_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -G=G_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -R=R_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - -Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) - -gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) - -print(f'PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians') -print(f'PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians') -print(f'VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians') -print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') - -print('=================== RLGC ==================') -print(f'R = {R.real/1e-3:.2f} mOhm/um') -print(f'L = {L.real/1e-12:.2f} picoHenry/um') -print(f'G = {G.real/1e-12:.2f} picoSiemens/um') -print(f'C = {C.real/1e-15:.2f} femtoFarad/um') - -print('================== propagation constant ===============') -print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') -print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') +# Be careful with the units!! +# In this case just make sure you adjust the length unit to micrometers +# everything else can stay as is. + +C = C_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +L = L_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +G = G_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +R = R_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), +) + +Z0 = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + / (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) + +gamma = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + * (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) + +print( + f"PI model : |Z0| = {np.abs(p0/np.abs(i0)**2):.2f} Ohm | angle(Z0) = {np.angle(p0/np.abs(i0)**2)/np.pi:.2f} pi radians" +) +print( + f"PV model : |Z0| = {np.abs(np.abs(v0)**2/p0.conj()):.2f} Ohm | angle(Z0) = {np.angle(np.abs(v0)**2/p0.conj())/np.pi:.2f} pi radians" +) +print( + f"VI model : |Z0| = {np.abs(v0/i0):.2f} Ohm | angle(Z0) = {np.angle(v0/i0)/np.pi:.2f} pi radians" +) +print(f"RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians") + +print("=================== RLGC ==================") +print(f"R = {R.real/1e-3:.2f} mOhm/um") +print(f"L = {L.real/1e-12:.2f} picoHenry/um") +print(f"G = {G.real/1e-12:.2f} picoSiemens/um") +print(f"C = {C.real/1e-15:.2f} femtoFarad/um") + +print("================== propagation constant ===============") +print(f"beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ") +print(f"alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ") # %% [markdown] -# To recover the RLGC values of the entire structure you simple half the Z and double the Y. +# To recover the RLGC values of the entire structure you simple half the Z and double the Y. # %% -R = R/2 -L = L/2 -C = 2*C -G = 2*G - -Z0 = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)/ - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) +R = R / 2 +L = L / 2 +C = 2 * C +G = 2 * G + +Z0 = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + / (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) -gamma = np.sqrt((R+1j*omega[idx_freq].to(reg.second**-1).magnitude * L)* - (G+1j*omega[idx_freq].to(reg.second**-1).magnitude * C)) +gamma = np.sqrt( + (R + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L) + * (G + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C) +) -print(f'RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians') +print(f"RLGC : |Z0| = {np.abs(Z0):.2f} Ohm | angle(Z0) = {np.angle(Z0)/np.pi:.2f} pi radians") -print('=================== RLGC ==================') -print(f'R = {R.real/1e-3:.2f} mOhm/um') -print(f'L = {L.real/1e-12:.2f} picoHenry/um') -print(f'G = {G.real/1e-12:.2f} picoSiemens/um') -print(f'C = {C.real/1e-15:.2f} femtoFarad/um') +print("=================== RLGC ==================") +print(f"R = {R.real/1e-3:.2f} mOhm/um") +print(f"L = {L.real/1e-12:.2f} picoHenry/um") +print(f"G = {G.real/1e-12:.2f} picoSiemens/um") +print(f"C = {C.real/1e-15:.2f} femtoFarad/um") -print('================== propagation constant ===============') -print(f'beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ') -print(f'alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ') +print("================== propagation constant ===============") +print(f"beta : RLGC = {gamma.imag*1e6:.2f} 1/m | FEM = {mode.k.real*1e6:.2f} 1/m ") +print(f"alpha: RLGC = {gamma.real*1e6:.2f} 1/m | FEM = {-mode.k.imag*1e6:.2f} 1/m ") # %% [markdown] # Well, having all this set up nicely, all we are left to do is to make a frequency sweep and compare the frequency dependent values with other works and check the validity of it. Let's just define some helper functions @@ -1191,23 +1416,23 @@ def R_form(w): reg = UnitRegistry() use_symmetry_plane = True -mesh_res = 'fine' +mesh_res = "fine" -#Define frequency range +# Define frequency range freq = np.logspace(np.log10(0.05), np.log10(45), 100) * reg.GHz n_modes = 1 -omega = 2*np.pi*freq -print('meshing') +omega = 2 * np.pi * freq +print("meshing") ################## MESHING ################################ ## Define universal constants -mu0 = 4*np.pi * 1e-7 * reg.henry/reg.meter #vacuum magnetic permeability -e0 = 8.854e-12 * reg.farad*reg.meter**-1 -c = 3e8 * reg.meter*reg.second**-1 #m s^-1 -e=1.602176634e-19 * reg.coulomb #Coulombs -kb=1.380649e-23 *reg.meter**2*reg.kg*reg.second**-2*reg.kelvin**-1 -T=300 * reg.kelvin +mu0 = 4 * np.pi * 1e-7 * reg.henry / reg.meter # vacuum magnetic permeability +e0 = 8.854e-12 * reg.farad * reg.meter**-1 +c = 3e8 * reg.meter * reg.second**-1 # m s^-1 +e = 1.602176634e-19 * reg.coulomb # Coulombs +kb = 1.380649e-23 * reg.meter**2 * reg.kg * reg.second**-2 * reg.kelvin**-1 +T = 300 * reg.kelvin w_sig = 7 * reg.micrometer sep = 10 * reg.micrometer @@ -1219,44 +1444,58 @@ def R_form(w): eps_r_sub = 13 * reg.dimensionless -sig_metal = 6e5 * reg.siemens/reg.centimeter +sig_metal = 6e5 * reg.siemens / reg.centimeter ######## Define FEM simulation ########### -symmetry_plane = box(0,-np.inf, np.inf, np.inf) -port_width = (w_sig+2*sep)*3 #um -bottom_height = (w_sig+2*sep)*1 -top_height = (w_sig+2*sep)*1 +symmetry_plane = box(0, -np.inf, np.inf, np.inf) +port_width = (w_sig + 2 * sep) * 3 # um +bottom_height = (w_sig + 2 * sep) * 1 +top_height = (w_sig + 2 * sep) * 1 ########################################## -metal_sig_box = box(-w_sig.to(reg.micrometer).magnitude/2, - 0, - w_sig.to(reg.micrometer).magnitude/2, - t_metal.to(reg.micrometer).magnitude) +metal_sig_box = box( + -w_sig.to(reg.micrometer).magnitude / 2, + 0, + w_sig.to(reg.micrometer).magnitude / 2, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_left_box = box(max((-w_sig/2-sep-w_gnd).to(reg.micrometer).magnitude, - -(port_width/2).to(reg.micrometer).magnitude), - 0, - (-w_sig/2-sep).to(reg.micrometer).magnitude, - t_metal.to(reg.micrometer).magnitude) +metal_gnd_left_box = box( + max( + (-w_sig / 2 - sep - w_gnd).to(reg.micrometer).magnitude, + -(port_width / 2).to(reg.micrometer).magnitude, + ), + 0, + (-w_sig / 2 - sep).to(reg.micrometer).magnitude, + t_metal.to(reg.micrometer).magnitude, +) -metal_gnd_right_box = box((w_sig/2+sep).to(reg.micrometer).magnitude, - 0, - min((w_sig/2+sep+w_gnd).to(reg.micrometer).magnitude, - (port_width/2).to(reg.micrometer).magnitude), - t_metal.to(reg.micrometer).magnitude) +metal_gnd_right_box = box( + (w_sig / 2 + sep).to(reg.micrometer).magnitude, + 0, + min( + (w_sig / 2 + sep + w_gnd).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + ), + t_metal.to(reg.micrometer).magnitude, +) -air_box = box((-port_width/2).to(reg.micrometer).magnitude, - 0, - (port_width/2).to(reg.micrometer).magnitude, - top_height.to(reg.micrometer).magnitude) +air_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + 0, + (port_width / 2).to(reg.micrometer).magnitude, + top_height.to(reg.micrometer).magnitude, +) -substrate_box = box((-port_width/2).to(reg.micrometer).magnitude, - -(bottom_height).to(reg.micrometer).magnitude, - (port_width/2).to(reg.micrometer).magnitude, - 0) +substrate_box = box( + (-port_width / 2).to(reg.micrometer).magnitude, + -(bottom_height).to(reg.micrometer).magnitude, + (port_width / 2).to(reg.micrometer).magnitude, + 0, +) surface = unary_union([air_box, substrate_box]) @@ -1265,10 +1504,12 @@ def R_form(w): xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_right_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmax_1, (ymax_1+ymin_1)/2), - (xmin_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmax_1, (ymax_1 + ymin_1) / 2), + (xmin_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_right = LineString(points) @@ -1276,111 +1517,121 @@ def R_form(w): xmin_1, ymin_1, xmax_1, ymax_1 = metal_sig_box.bounds xmin_2, ymin_2, xmax_2, ymax_2 = metal_gnd_left_box.bounds -points = [((xmax_1+xmin_1)/2, (ymax_1+ymin_1)/2), - (xmin_1, (ymax_1+ymin_1)/2), - (xmax_2, (ymax_2+ymin_2)/2), - ((xmax_2+xmin_2)/2, (ymax_2+ymin_2)/2)] +points = [ + ((xmax_1 + xmin_1) / 2, (ymax_1 + ymin_1) / 2), + (xmin_1, (ymax_1 + ymin_1) / 2), + (xmax_2, (ymax_2 + ymin_2) / 2), + ((xmax_2 + xmin_2) / 2, (ymax_2 + ymin_2) / 2), +] path_integral_left = LineString(points) polygons = OrderedDict( - surface = LineString(surface.exterior), - metal_sig_interface = LineString(clip_by_rect(metal_sig_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_sig.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - metal_gnd_left_interface = LineString(clip_by_rect(metal_gnd_left_box.buffer(min(t_metal.to(reg.um).magnitude/20, - w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - metal_gnd_right_interface = LineString(clip_by_rect(metal_gnd_right_box.buffer(min(t_metal.to(reg.um).magnitude/20, w_gnd.to(reg.um).magnitude/10), - join_style = 'bevel'), - *surface.bounds).exterior), - - path_integral_right = path_integral_right, - path_integral_left = path_integral_left, - - metal_sig = metal_sig_box, - metal_gnd_left = metal_gnd_left_box, - metal_gnd_right = metal_gnd_right_box, - air = air_box, - substrate = substrate_box - ) + surface=LineString(surface.exterior), + metal_sig_interface=LineString( + clip_by_rect( + metal_sig_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_sig.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_left_interface=LineString( + clip_by_rect( + metal_gnd_left_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + metal_gnd_right_interface=LineString( + clip_by_rect( + metal_gnd_right_box.buffer( + min(t_metal.to(reg.um).magnitude / 20, w_gnd.to(reg.um).magnitude / 10), + join_style="bevel", + ), + *surface.bounds, + ).exterior + ), + path_integral_right=path_integral_right, + path_integral_left=path_integral_left, + metal_sig=metal_sig_box, + metal_gnd_left=metal_gnd_left_box, + metal_gnd_right=metal_gnd_right_box, + air=air_box, + substrate=substrate_box, +) - if use_symmetry_plane: keys_to_pop = [] - for key,poly in polygons.items(): + for key, poly in polygons.items(): if poly.intersects(symmetry_plane) and not symmetry_plane.contains(poly): - poly_tmp=clip_by_rect(poly, *symmetry_plane.bounds) - + poly_tmp = clip_by_rect(poly, *symmetry_plane.bounds) + if type(poly_tmp) == MultiLineString: polygons[key] = linemerge(poly_tmp) - + elif poly_tmp.is_empty: keys_to_pop.append(key) else: polygons[key] = poly_tmp elif not poly.intersects(symmetry_plane): keys_to_pop.append(key) - + for key in keys_to_pop: polygons.pop(key) - -#Add the boundary polygons so that you can set custom boundary conditions -surf_bounds = polygons['surface'].bounds -left = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[0], surf_bounds[3])]) +# Add the boundary polygons so that you can set custom boundary conditions +surf_bounds = polygons["surface"].bounds -bottom = LineString([(surf_bounds[0], surf_bounds[1]), - (surf_bounds[2], surf_bounds[1])]) +left = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[0], surf_bounds[3])]) -right = LineString([(surf_bounds[2], surf_bounds[1]), - (surf_bounds[2], surf_bounds[3])]) +bottom = LineString([(surf_bounds[0], surf_bounds[1]), (surf_bounds[2], surf_bounds[1])]) -top = LineString([(surf_bounds[0], surf_bounds[3]), - (surf_bounds[2], surf_bounds[3])]) +right = LineString([(surf_bounds[2], surf_bounds[1]), (surf_bounds[2], surf_bounds[3])]) -polygons['left'] = left -polygons['bottom'] = bottom -polygons['right'] = right -polygons['top'] = top +top = LineString([(surf_bounds[0], surf_bounds[3]), (surf_bounds[2], surf_bounds[3])]) -polygons.move_to_end('top', last = False) -polygons.move_to_end('right', last = False) -polygons.move_to_end('bottom', last = False) -polygons.move_to_end('left', last = False) +polygons["left"] = left +polygons["bottom"] = bottom +polygons["right"] = right +polygons["top"] = top -polygons.pop('surface') +polygons.move_to_end("top", last=False) +polygons.move_to_end("right", last=False) +polygons.move_to_end("bottom", last=False) +polygons.move_to_end("left", last=False) + +polygons.pop("surface") # print(polygons.keys()) -if mesh_res == 'fine': +if mesh_res == "fine": resolutions = dict( - surface = {'resolution': 100, 'distance': 1}, - metal_sig_interface = {'resolution': 0.1, 'distance': 10}, - metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, - path_integral_right = {'resolution': 0.2, 'distance': 10}, - path_integral_left = {'resolution': 0.2, 'distance': 10}, - metal_sig = {'resolution': 0.1, 'distance': 0.1, 'SizeMax': 0.1}, - metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - air = {'resolution': 10, 'distance': 0.1}, - substrate = {'resolution': 10, 'distance': 1}, + surface={"resolution": 100, "distance": 1}, + metal_sig_interface={"resolution": 0.1, "distance": 10}, + metal_gnd_interface={"resolution": 0.5, "distance": 10}, + path_integral_right={"resolution": 0.2, "distance": 10}, + path_integral_left={"resolution": 0.2, "distance": 10}, + metal_sig={"resolution": 0.1, "distance": 0.1, "SizeMax": 0.1}, + metal_gnd_left={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + metal_gnd_right={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + air={"resolution": 10, "distance": 0.1}, + substrate={"resolution": 10, "distance": 1}, ) else: resolutions = dict( - surface = {'resolution': 100, 'distance': 1}, - metal_sig_interface = {'resolution': 0.5, 'distance': 10}, - metal_gnd_interface = {'resolution': 0.5, 'distance': 10}, - path_integral_right = {'resolution': 0.5, 'distance': 10}, - path_integral_left = {'resolution': 0.5, 'distance': 10}, - metal_sig = {'resolution': 0.5, 'distance': 0.1, 'SizeMax': 0.1}, - metal_gnd_left = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - metal_gnd_right = {'resolution': 0.5, 'distance': 0.2, 'SizeMax': 0.5}, - air = {'resolution': 20, 'distance': 0.1}, - substrate = {'resolution': 20, 'distance': 1}, + surface={"resolution": 100, "distance": 1}, + metal_sig_interface={"resolution": 0.5, "distance": 10}, + metal_gnd_interface={"resolution": 0.5, "distance": 10}, + path_integral_right={"resolution": 0.5, "distance": 10}, + path_integral_left={"resolution": 0.5, "distance": 10}, + metal_sig={"resolution": 0.5, "distance": 0.1, "SizeMax": 0.1}, + metal_gnd_left={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + metal_gnd_right={"resolution": 0.5, "distance": 0.2, "SizeMax": 0.5}, + air={"resolution": 20, "distance": 0.1}, + substrate={"resolution": 20, "distance": 1}, ) # fig = plt.figure() @@ -1392,131 +1643,147 @@ def R_form(w): # ax.plot(np.asarray(x),np.asarray(y), color = 'pink') # else: # ax.plot(*polygon.xy) -#Mesh it -mesh = from_meshio(mesh_from_OrderedDict(polygons, - resolutions, - default_resolution_max = 100, - verbose = True)) - +# Mesh it +mesh = from_meshio( + mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=100, verbose=True) +) + print(mesh.nelements) manager = enlighten.get_manager() -bar = manager.counter(total=len(freq), desc='Ticks', unit='ticks') +bar = manager.counter(total=len(freq), desc="Ticks", unit="ticks") -neff_all = np.zeros(len(freq), dtype = complex) -atten = np.zeros(len(freq), dtype = float) -z0 = np.zeros(len(freq), dtype = complex) -R = np.zeros(len(freq), dtype = complex) -L = np.zeros(len(freq), dtype = complex) -G = np.zeros(len(freq), dtype = complex) -C = np.zeros(len(freq), dtype = complex) -z0_RLGC = np.zeros(len(freq), dtype = complex) +neff_all = np.zeros(len(freq), dtype=complex) +atten = np.zeros(len(freq), dtype=float) +z0 = np.zeros(len(freq), dtype=complex) +R = np.zeros(len(freq), dtype=complex) +L = np.zeros(len(freq), dtype=complex) +G = np.zeros(len(freq), dtype=complex) +C = np.zeros(len(freq), dtype=complex) +z0_RLGC = np.zeros(len(freq), dtype=complex) for idx_freq in range(len(freq)): # break - basis0 = Basis(mesh, ElementTriP0(), intorder=4) #Define the basis for the FEM - epsilon = basis0.ones(dtype = complex) - - epsilon[basis0.get_dofs(elements = 'air')] = 1 - epsilon[basis0.get_dofs(elements = 'substrate')] = eps_r_sub - epsilon[basis0.get_dofs(elements = 'metal_sig')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + basis0 = Basis(mesh, ElementTriP0(), intorder=4) # Define the basis for the FEM + epsilon = basis0.ones(dtype=complex) + + epsilon[basis0.get_dofs(elements="air")] = 1 + epsilon[basis0.get_dofs(elements="substrate")] = eps_r_sub + epsilon[basis0.get_dofs(elements="metal_sig")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude + ) # epsilon[basis0.get_dofs(elements = 'metal_gnd_left')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) - epsilon[basis0.get_dofs(elements = 'metal_gnd_right')] = (1 - 1j*(sig_metal/omega/e0)[idx_freq].to(reg.dimensionless).magnitude) + epsilon[basis0.get_dofs(elements="metal_gnd_right")] = ( + 1 - 1j * (sig_metal / omega / e0)[idx_freq].to(reg.dimensionless).magnitude + ) # break modes = compute_modes( - basis0, - epsilon, - wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, - num_modes=n_modes, - metallic_boundaries=False, - ) - + basis0, + epsilon, + wavelength=(c / freq[idx_freq]).to(reg.micrometer).magnitude, + num_modes=n_modes, + metallic_boundaries=False, + ) + neff_all[idx_freq] = modes.n_effs - atten[idx_freq] = 20/np.log(10)*(modes.n_effs.imag*2*np.pi*freq[idx_freq]/c).to(reg.centimeter**-1).magnitude #dB/cm - + atten[idx_freq] = ( + 20 + / np.log(10) + * (modes.n_effs.imag * 2 * np.pi * freq[idx_freq] / c).to(reg.centimeter**-1).magnitude + ) # dB/cm + ###### CALCULATE Z0 ############ - conductor = 'metal_sig_interface' - line = 'path_integral_right' + conductor = "metal_sig_interface" + line = "path_integral_right" mode = modes[0] - p0 = calculate_scalar_product(mode.basis, np.conjugate(mode.E), - mode.basis,np.conjugate(mode.H)) #The conjugate is due to femwell internal definition as conj(E) x H - + p0 = calculate_scalar_product( + mode.basis, np.conjugate(mode.E), mode.basis, np.conjugate(mode.H) + ) # The conjugate is due to femwell internal definition as conj(E) x H (ht, ht_basis), (hz, hz_basis) = mode.basis.split(mode.H) (et, et_basis), (ez, ez_basis) = mode.basis.split(mode.E) - + facet_basis = ht_basis.boundary(facets=mesh.boundaries[conductor]) - i0 = current_form.assemble(facet_basis,H=facet_basis.interpolate(ht)) - - v0 = p0/i0.conj() - - z0_tmp = v0/i0 - + i0 = current_form.assemble(facet_basis, H=facet_basis.interpolate(ht)) + + v0 = p0 / i0.conj() + + z0_tmp = v0 / i0 + ########### CALCULATE RLGC PARAMETERS ############# basis_t = et_basis basis_z = ez_basis basis_eps = basis0 - - C_tmp=C_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - - L_tmp=L_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - - G_tmp=G_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - - R_tmp=R_form.assemble(mode.basis, - epsilon = basis_eps.interpolate(epsilon * e0.to(reg.farad/reg.micrometer).magnitude), - mu = mu0.to(reg.henry/reg.micrometer).magnitude, - i0=i0, - omega = omega[idx_freq].to(reg.second**-1).magnitude, - v0 = v0, - et = basis_t.interpolate(et), - ez = basis_z.interpolate(ez), - ht = basis_t.interpolate(ht), - hz = basis_z.interpolate(hz)) - - z0_RLGC_tmp = np.sqrt((R_tmp+1j*omega[idx_freq].to(reg.second**-1).magnitude * L_tmp)/ - (G_tmp+1j*omega[idx_freq].to(reg.second**-1).magnitude * C_tmp)) - + + C_tmp = C_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), + ) + + L_tmp = L_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), + ) + + G_tmp = G_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), + ) + + R_tmp = R_form.assemble( + mode.basis, + epsilon=basis_eps.interpolate(epsilon * e0.to(reg.farad / reg.micrometer).magnitude), + mu=mu0.to(reg.henry / reg.micrometer).magnitude, + i0=i0, + omega=omega[idx_freq].to(reg.second**-1).magnitude, + v0=v0, + et=basis_t.interpolate(et), + ez=basis_z.interpolate(ez), + ht=basis_t.interpolate(ht), + hz=basis_z.interpolate(hz), + ) + + z0_RLGC_tmp = np.sqrt( + (R_tmp + 1j * omega[idx_freq].to(reg.second**-1).magnitude * L_tmp) + / (G_tmp + 1j * omega[idx_freq].to(reg.second**-1).magnitude * C_tmp) + ) + ## Save the parameters for the full device - - z0[idx_freq] = z0_tmp/2 - z0_RLGC[idx_freq] = z0_RLGC_tmp/2 - R[idx_freq] = R_tmp/2 - L[idx_freq] = L_tmp/2 - G[idx_freq] = G_tmp*2 - C[idx_freq] = C_tmp*2 - - + + z0[idx_freq] = z0_tmp / 2 + z0_RLGC[idx_freq] = z0_RLGC_tmp / 2 + R[idx_freq] = R_tmp / 2 + L[idx_freq] = L_tmp / 2 + G[idx_freq] = G_tmp * 2 + C[idx_freq] = C_tmp * 2 + bar.update() - + mesh.draw() # fig = basis0.plot(epsilon.real, colorbar = True) @@ -1527,68 +1794,81 @@ def R_form(w): # %% ##DATA FROM PAPER -data_nu = np.asarray([[-1.317, 7.780], - [-1.218, 7.327], - [-1.106, 6.474], - [-0.983, 5.995], - [-0.800, 5.160], - [-0.647, 4.574], - [-0.499, 4.094], - [-0.270, 3.632], - [0.000, 3.188], - [0.311, 2.993], - [0.622, 2.940], - [0.958, 2.833], - [1.254, 2.833], - [1.559, 2.833]]) - - -data_alpha = np.asarray([[-1.287, 0.728], - [-1.060, 0.950], - [-0.780, 1.181], - [-0.520, 1.465], - [-0.270, 1.785], - [-0.025, 2.087], - [0.204, 2.353], - [0.392, 2.567], - [0.632, 2.886], - [0.846, 3.117], - [1.070, 3.739], - [1.310, 4.680], - [1.478, 5.426], - [1.590, 5.977]]) +data_nu = np.asarray( + [ + [-1.317, 7.780], + [-1.218, 7.327], + [-1.106, 6.474], + [-0.983, 5.995], + [-0.800, 5.160], + [-0.647, 4.574], + [-0.499, 4.094], + [-0.270, 3.632], + [0.000, 3.188], + [0.311, 2.993], + [0.622, 2.940], + [0.958, 2.833], + [1.254, 2.833], + [1.559, 2.833], + ] +) + + +data_alpha = np.asarray( + [ + [-1.287, 0.728], + [-1.060, 0.950], + [-0.780, 1.181], + [-0.520, 1.465], + [-0.270, 1.785], + [-0.025, 2.087], + [0.204, 2.353], + [0.392, 2.567], + [0.632, 2.886], + [0.846, 3.117], + [1.070, 3.739], + [1.310, 4.680], + [1.478, 5.426], + [1.590, 5.977], + ] +) ## DATA FROM TU DELFT SIM -freq_matlab = np.loadtxt('support/freq_delft.txt') #Frequency sampling from their simulator -ReZ0_delft = np.loadtxt('support/ReZ0_delft.txt') #Real part of the impedance -ImZ0_delft = np.loadtxt('support/ImZ0_delft.txt') #Imaginary part of the impedance -loss_delft = np.loadtxt('support/loss_delft.txt') #The loss of the mode -ReK_delft = np.loadtxt('support/ReK_delft.txt') #The real part of the wavevector +freq_matlab = np.loadtxt("support/freq_delft.txt") # Frequency sampling from their simulator +ReZ0_delft = np.loadtxt("support/ReZ0_delft.txt") # Real part of the impedance +ImZ0_delft = np.loadtxt("support/ImZ0_delft.txt") # Imaginary part of the impedance +loss_delft = np.loadtxt("support/loss_delft.txt") # The loss of the mode +ReK_delft = np.loadtxt("support/ReK_delft.txt") # The real part of the wavevector # %% fig = plt.figure() ax = fig.add_subplot(111) ax1 = ax.twinx() -ax.plot(freq, neff_all, color = 'blue', label = 'FEMWELL') -ax.plot(freq_matlab, ReK_delft, color = 'blue', linestyle = 'dotted', label = 'TU Delft simulator') +ax.plot(freq, neff_all, color="blue", label="FEMWELL") +ax.plot(freq_matlab, ReK_delft, color="blue", linestyle="dotted", label="TU Delft simulator") -ax.plot(10**data_nu[:,0], data_nu[:,1], - marker= '^', color = 'blue', linestyle = 'dashed', - label = 'Tuncer et al. 1992') -ax.set_xscale('log') +ax.plot( + 10 ** data_nu[:, 0], + data_nu[:, 1], + marker="^", + color="blue", + linestyle="dashed", + label="Tuncer et al. 1992", +) +ax.set_xscale("log") -ax.legend(loc = 'upper center') -ax1.plot(freq, -atten, color = 'red') -ax1.plot(10**data_alpha[:,0], data_alpha[:,1], marker= '^', color = 'red', linestyle = 'dashed') -ax1.plot(freq_matlab, loss_delft, color = 'red', linestyle = 'dotted') +ax.legend(loc="upper center") +ax1.plot(freq, -atten, color="red") +ax1.plot(10 ** data_alpha[:, 0], data_alpha[:, 1], marker="^", color="red", linestyle="dashed") +ax1.plot(freq_matlab, loss_delft, color="red", linestyle="dotted") -ax.set_xlabel('Frequency (GHz)') -ax.set_ylabel('Microwave index') -ax1.set_ylabel('Attenuation (dB/cm)') +ax.set_xlabel("Frequency (GHz)") +ax.set_ylabel("Microwave index") +ax1.set_ylabel("Attenuation (dB/cm)") -ax.arrow(0.1,5,-0.05,0, head_width = 0.1, head_length = 0.01, color = 'blue') -ax1.arrow(10,3,10,0, head_width = 0.1, head_length = 5, color = 'red') +ax.arrow(0.1, 5, -0.05, 0, head_width=0.1, head_length=0.01, color="blue") +ax1.arrow(10, 3, 10, 0, head_width=0.1, head_length=5, color="red") # fig.savefig('Tuncer_et_al_1992_results.png', dpi = 400) @@ -1596,17 +1876,17 @@ def R_form(w): fig = plt.figure() ax = fig.add_subplot(111) -ax.plot(freq, z0.real, label = 'FEMWELL - real', color = 'blue') -ax.plot(freq, z0.imag, label = 'FEMWELL - imag', color = 'red') -ax.plot(freq_matlab, ReZ0_delft, label = 'TU Delft sim - real', color = 'blue', linestyle = 'dashed') -ax.plot(freq_matlab, ImZ0_delft, label = 'TU Delft sim - imag', color = 'red', linestyle = 'dashed') +ax.plot(freq, z0.real, label="FEMWELL - real", color="blue") +ax.plot(freq, z0.imag, label="FEMWELL - imag", color="red") +ax.plot(freq_matlab, ReZ0_delft, label="TU Delft sim - real", color="blue", linestyle="dashed") +ax.plot(freq_matlab, ImZ0_delft, label="TU Delft sim - imag", color="red", linestyle="dashed") -ax.set_xlabel('Frequency (GHz)') -ax.set_ylabel('Z0 (Ohm)') +ax.set_xlabel("Frequency (GHz)") +ax.set_ylabel("Z0 (Ohm)") ax.legend() -ax.set_xscale('log') +ax.set_xscale("log") # fig.savefig('z0_delft.png', dpi = 400) @@ -1615,20 +1895,22 @@ def R_form(w): # %% fig = plt.figure() -gs = GridSpec(2,2) - -ax_C = fig.add_subplot(gs[0,0]) -ax_G = fig.add_subplot(gs[0,1]) -ax_L = fig.add_subplot(gs[1,0]) -ax_R = fig.add_subplot(gs[1,1]) - -for data, ax, label in zip([R/1e-3,L/1e-12,G/1e-12,C/1e-15], - [ax_R, ax_L, ax_G, ax_C], - ['kOhm/m', 'uHenry/m','uS/m', 'nF/m']): - - ax.plot(freq, data, color = 'blue') - ax.set_xlabel('Frequency (GHz)') +gs = GridSpec(2, 2) + +ax_C = fig.add_subplot(gs[0, 0]) +ax_G = fig.add_subplot(gs[0, 1]) +ax_L = fig.add_subplot(gs[1, 0]) +ax_R = fig.add_subplot(gs[1, 1]) + +for data, ax, label in zip( + [R / 1e-3, L / 1e-12, G / 1e-12, C / 1e-15], + [ax_R, ax_L, ax_G, ax_C], + ["kOhm/m", "uHenry/m", "uS/m", "nF/m"], +): + + ax.plot(freq, data, color="blue") + ax.set_xlabel("Frequency (GHz)") ax.set_ylabel(label) - - ax.set_xscale('log') + + ax.set_xscale("log") fig.tight_layout() diff --git a/femwell/mesh/mesh.py b/femwell/mesh/mesh.py index 118cd48b..4b056c87 100644 --- a/femwell/mesh/mesh.py +++ b/femwell/mesh/mesh.py @@ -14,7 +14,7 @@ Point, Polygon, ) -from shapely.ops import linemerge, polygonize, split, unary_union, snap +from shapely.ops import linemerge, polygonize, snap, split, unary_union from femwell.mesh.meshtracker import MeshTracker @@ -73,7 +73,9 @@ def mesh_from_Dict( rings = [ LineString(list(object.exterior.coords)) for object in listpoly - if not (object.geom_type in ["Point", "LineString", "MultiLineString"] or object.is_empty) + if not ( + object.geom_type in ["Point", "LineString", "MultiLineString"] or object.is_empty + ) ] union = unary_union(rings) @@ -205,7 +207,7 @@ def mesh_from_OrderedDict( model = geometry meshtracker = MeshTracker(model=model) - + # Break up shapes in order so that plane is tiled with non-overlapping layers, overriding shapes according to an order shapes_tiled_dict = OrderedDict() for lower_index, (lower_name, lower_shape) in reversed( @@ -251,8 +253,8 @@ def mesh_from_OrderedDict( else second_shape ) first_exterior_line = break_line_( - first_exterior_line, second_exterior_line, - meshtracker.atol) + first_exterior_line, second_exterior_line, meshtracker.atol + ) # Second line interiors for second_interior_line in ( second_shape.interiors @@ -261,8 +263,8 @@ def mesh_from_OrderedDict( ): second_interior_line = LineString(second_interior_line) first_exterior_line = break_line_( - first_exterior_line, second_interior_line, - meshtracker.atol) + first_exterior_line, second_interior_line, meshtracker.atol + ) # First line interiors if first_shape.geom_type in ["Polygon", "MultiPolygon"]: first_shape_interiors = [] @@ -287,8 +289,8 @@ def mesh_from_OrderedDict( else second_shape ) first_interior_line = break_line_( - first_interior_line, second_exterior_line, - meshtracker.atol) + first_interior_line, second_exterior_line, meshtracker.atol + ) # Interiors for second_interior_line in ( second_shape.interiors @@ -302,8 +304,10 @@ def mesh_from_OrderedDict( ) np.seterr(**initial_settings) first_interior_line = break_line_( - first_interior_line, second_interior_line, - meshtracker.atol) + first_interior_line, + second_interior_line, + meshtracker.atol, + ) first_shape_interiors.append(first_interior_line) if first_shape.geom_type in ["Polygon", "MultiPolygon"]: broken_shapes.append(Polygon(first_exterior_line, holes=first_shape_interiors)) @@ -319,7 +323,7 @@ def mesh_from_OrderedDict( ) # Add lines, reusing line segments - + for line_name, line in lines_broken_dict.items(): meshtracker.add_get_xy_line(line, line_name) From 6fc706f97a164592c674bf82cf1b095acdad4b1f Mon Sep 17 00:00:00 2001 From: duarte-jfs Date: Tue, 28 May 2024 11:29:36 +0200 Subject: [PATCH 11/12] added dependencies in pyproject and updated readme.md --- .ipynb_checkpoints/README-checkpoint.md | 103 ++++++++++++++++++ .ipynb_checkpoints/pyproject-checkpoint.toml | 52 +++++++++ README.md | 13 ++- .../contributors-checkpoint.md | 64 +++++++++++ pyproject.toml | 2 + 5 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 .ipynb_checkpoints/README-checkpoint.md create mode 100644 .ipynb_checkpoints/pyproject-checkpoint.toml create mode 100644 docs/.ipynb_checkpoints/contributors-checkpoint.md diff --git a/.ipynb_checkpoints/README-checkpoint.md b/.ipynb_checkpoints/README-checkpoint.md new file mode 100644 index 00000000..4edf733f --- /dev/null +++ b/.ipynb_checkpoints/README-checkpoint.md @@ -0,0 +1,103 @@ +# Femwell + +![logo](https://raw.githubusercontent.com/HelgeGehring/femwell/main/logo_inline.svg) + +[![Docs](https://github.com/helgegehring/femwell/actions/workflows/docs.yml/badge.svg)](https://HelgeGehring.github.io/femwell/) +[![Build](https://github.com/helgegehring/femwell/actions/workflows/build.yml/badge.svg)](https://github.com/HelgeGehring/femwell/actions/workflows/build.yml) +[![PiPy](https://img.shields.io/pypi/v/femwell)](https://pypi.org/project/femwell/) +[![Downloads](https://static.pepy.tech/badge/femwell/month)](https://pepy.tech/project/femwell) + +## Welcome to FEMWELL! + +FEMWELL is a physics simulation tool that utilises the Finite Element Method (FEM). With FEMWELL, you can simulate integrated circuits, electronic and photonic systems, and so much more. +The project is created to provide an Open-Source FEM solver. You are welcome to contribute to FEMWELL or just use it. Any feedback and input are valuable and will make FEMWELL better! + +## What is a FEM simulation? + +FEM is a method to solve various types of differential equations that appear in physics, maths or engineering. FEM methods are used when describing how fields behave in an inhomogeneous setting, for example are electromagnetic fields in structured matter described by Maxwell’s equations. FEM uses a mesh to discretize the space and solve Maxwell’s equations via these “finite elements”. + +A FEM simulation typically involves the following steps: + +1. Discretization (or Meshing) of the structure +2. Calculation of the problem in each element +3. Assembly accounting for boundary conditions between the elements +4. Solution via iterative solvers or direct solution methods + +FEM can be applied to various problems, from Maxwell’s equations to fluid simulation, heat transport or structural analysis. + +## What is special about FEMWELL? + +First and foremost: FEMWELL is open source! You can just use FEMWELL, you can contribute to FEMWELL and you can modify FEMWELL to fit your specific problem. + +At the moment we focus on photonic and electronic problems, meaning we concentrate on solving Maxwell’s equation. This is useful to understand the physics in modern devices used in classical or quantum computing technologies. + +We are actively working on extending FEMWELL to address other questions. You can find a list of examples below. + +## How can I use FEMWELL? + +The simplest thing it to try out the examples in the browser! Hover the rocket at the top on the example pages and click live code. (Might take some time to load). +For more involved calculations, we recommend installing FEMWELL following the instructions. +If you can to improve FEMWELL, please get in touch with us. We are looking forward to your contributions! + +### Please note: +The documentation is lagging behind the state of code, so there's several features for which there are only examples in the code. + +## Features + +- Photonic eigenmode solver +- Periodic photonic eigenmode solver +- Electric eigenmode solver +- Thermal mode solver (static and transient) +- Coulomb solver + +## Possible Simulations + +### Photonic problems + +- Eigenmodes of waveguides and determining their effective refractive index +- Coupling between neighboring waveguides +- Eigenmodes of bent waveguides +- Propagation loss of circular bends and mode mismatch loss with straight waveguides +- Calculation of the group velocity and its dispersion +- Calculation of overlap-integrals and confinement-factors +- Bragg grating cells +- Grating coupler cells +- Overlap integrals between waveguide modes +- Overlap integral between a waveguide mode and a fiber mode +- Coupled mode theory - coupling between adjacent waveguides +- Heat based photonic phase shifters +- Pockels based photonic phase shifters +- PN junction depletion modulator (analytical) + +### Heat transport +- Static thermal profiles +- Transient thermal behavior + +### Electronics problems + +- Coplanar waveguide RF design +- Eigenmode of a coaxial cable and its specific impedance +- Eigenmodes of electric transmission lines + and determining their propagation constant (in work) +- Static electric fields + +Something missing? Feel free to open an [issue](https://github.com/HelgeGehring/femwell/issues) :) + +## Contributors + +- Helge Gehring (Google, WWU Münster) +- Simon Bilodeau (Google, Princeton University) +- Joaquin Matres (Google) +- Marc de Cea Falco (Google, Massachusetts Institute of Technology) +- Lodovico Rossi (Princeton University) +- Doris Reiter (TU Dortmund University) +- Yannick Augenstein (Google, Karlsruhe Institute of Technology) +- Niko Savola (Google, Aalto University) +- Rouven Glauert (Idalab) +- Markus DeMartini (Google) +- Lucas Grosjean (Google, Femto-ST Institute) +- Eliza Leung (University of Adelaide) +- Duarte Silva (Eindhoven University of Technology) + +Happy about every form of contribution - +pull requests, feature requests, issues, questions, ... :) diff --git a/.ipynb_checkpoints/pyproject-checkpoint.toml b/.ipynb_checkpoints/pyproject-checkpoint.toml new file mode 100644 index 00000000..39cb7621 --- /dev/null +++ b/.ipynb_checkpoints/pyproject-checkpoint.toml @@ -0,0 +1,52 @@ +[build-system] +requires = ["poetry-core>=1.6.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "femwell" +version = "0.0.1" +authors = ["Helge Gehring"] +description = "Mode solver for photonic and electric waveguides based on FEM" +homepage = "https://github.com/HelgeGehring/femwell" +keywords = [ + "integrated photonics", + "silicon photonics", + "mode solving", + "finite element analysis" +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3", + "Topic :: Scientific/Engineering" +] +license = "GPLv3" +readme = "README.md" + +[tool.poetry.urls] +Documentation = "https://HelgeGehring.github.io/femwell/" + +[tool.poetry.dependencies] +python = ">=3.8" +scikit-fem = ">=8.1.0" +gmsh = "*" +pygmsh = "*" +matplotlib = "*" +meshwell = ">=1.0.2" + +[tool.poetry.group.test.dependencies] +pytest = "*" +flake8 = "*" + +[tool.poetry.group.docs.dependencies] +python = ">=3.10" +tqdm = "*" +enlighten = "*" +pint = ">0.20.1" +# sphinx-book-theme = "*" +jupytext = "*" +# myst-parser = "*" +pandas = "*" + +[tool.black] +line-length = 100 \ No newline at end of file diff --git a/README.md b/README.md index 33931f9e..4edf733f 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,6 @@ The documentation is lagging behind the state of code, so there's several featur - Calculation of overlap-integrals and confinement-factors - Bragg grating cells - Grating coupler cells -- Eigenmode of a coaxial cable and its specific impedance -- Eigenmodes of electric transmission lines - and determining their propagation constant (in work) -- Static electric fields - Overlap integrals between waveguide modes - Overlap integral between a waveguide mode and a fiber mode - Coupled mode theory - coupling between adjacent waveguides @@ -77,6 +73,14 @@ The documentation is lagging behind the state of code, so there's several featur - Static thermal profiles - Transient thermal behavior +### Electronics problems + +- Coplanar waveguide RF design +- Eigenmode of a coaxial cable and its specific impedance +- Eigenmodes of electric transmission lines + and determining their propagation constant (in work) +- Static electric fields + Something missing? Feel free to open an [issue](https://github.com/HelgeGehring/femwell/issues) :) ## Contributors @@ -93,6 +97,7 @@ Something missing? Feel free to open an [issue](https://github.com/HelgeGehring/ - Markus DeMartini (Google) - Lucas Grosjean (Google, Femto-ST Institute) - Eliza Leung (University of Adelaide) +- Duarte Silva (Eindhoven University of Technology) Happy about every form of contribution - pull requests, feature requests, issues, questions, ... :) diff --git a/docs/.ipynb_checkpoints/contributors-checkpoint.md b/docs/.ipynb_checkpoints/contributors-checkpoint.md new file mode 100644 index 00000000..0c855d95 --- /dev/null +++ b/docs/.ipynb_checkpoints/contributors-checkpoint.md @@ -0,0 +1,64 @@ +# Contributors + +## Helge Gehring + +```{figure} https://avatars.githubusercontent.com/HelgeGehring +--- +align: left +figclass: avatar +target: https://github.com/HelgeGehring +--- + +Helge received the B.Sc. and M.Sc. degrees in physics from the University of Münster in Germany. +He worked during his bachelor's studies as a software engineer at [DEOS](https://www.deos-ag.com/). +For his bachelor's thesis he investigated the quantum dynamics of quantum dots via large-scale simulations on supercomputers. +Subsequently, he switched to applied physics and worked on integrated photonics for his master's thesis, during which he had a research stay at the University of Oxford, UK. +Afterwards, he started his doctoral studies at the University of Münster where he combined photonic circuits with 3D micro-structures to enable low-loss ultra-broadband fiber-to-chip interfacing. +Helge received a scholarship for his B.Sc. and M.Sc. degrees and a fellowship for his doctoral studies from the Studienstiftung des deutschen Volkes. +He took a break from his doctoral studies to spend a year as a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X) and recently returned full-time to X. +``` + +## Simon Bilodeau + +```{figure} https://avatars.githubusercontent.com/simbilod +--- +align: left +figclass: avatar +target: https://github.com/simbilod +--- +Simon received the B.Sc. and M.Sc. degrees in physics from McGill University, Montreal, QC, Canada, where he conducted nanoelectronics research at ultralow temperatures. He is currently working toward the Ph.D. degree in electrical and computer engineering in the Lightwave Communications Research Lab, Princeton University, Princeton, NJ, USA. His research interests include nanophotonics, optoelectronics, integrated photonic systems, and neuromorphic computing. He is the recipient of Canada's NSERC and Québec's FRQNT Doctoral scholarships. Simon is currently a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X). +``` + +## Joaquin Matres + +```{figure} https://avatars.githubusercontent.com/joamatab +--- +align: left +figclass: avatar +target: https://github.com/joamatab +--- +Joaquin received his Engineering (2009), M.Sc. (2010) and PhD (2014) degree in Telecommunications from the Universidad Politecnica de Valencia, Spain. For his PhD he built CMOS-compatible all-optical switches and logic gates at IMEC and CEA-LETI foundries. During his 6 month internship in Intel Corporation and 2.5 years in Hewlett Packard Labs, he worked on hybrid tunable lasers, wavelength selective switches and microring based optical transceivers. Then as the first engineer that joined PsiQuantum he worked on design, layout and validation of large scale Quantum computing circuits, where he was involved in most tapeouts during 3 years. For the last 3 years he has been working at [X, the moonshot factory](https://x.company) (formerly known as Google X) developing Free Space Optical Transceivers to bring affordable internet to developing countries. +``` + +## Marc de Cea Falco + +```{figure} https://avatars.githubusercontent.com/mdecea +--- +align: left +figclass: avatar +target: https://github.com/mdecea +--- +Marc is a graduate student at MIT working in CMOS photonics with a high emphasis in communications applications. The research ranges from low power applications in the data center and high performance computing space to satellite and free space optical communications. He received his B.S. degree in Physics Engineering and Telecommunications Engineering from the Technical University of Catalonia in Barcelona, Spain. He worked as an intern at Ayar Labs designing and implementing automated test setups for optical devices. He is a recipient of the La Caixa fellowship. +Marc is currently a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X). +``` + +## What about you? + +```{figure} https://github.com/identicons/HelgeGehring.png +--- +align: left +figclass: avatar +target: . +--- +What about you? We're happy about everyone joining our project! +``` diff --git a/pyproject.toml b/pyproject.toml index 44bd60c3..39cb7621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,8 @@ flake8 = "*" [tool.poetry.group.docs.dependencies] python = ">=3.10" tqdm = "*" +enlighten = "*" +pint = ">0.20.1" # sphinx-book-theme = "*" jupytext = "*" # myst-parser = "*" From b05e575c342a4b897e087f890d4c93ed211ca456 Mon Sep 17 00:00:00 2001 From: Helge Gehring <42973196+HelgeGehring@users.noreply.github.com> Date: Tue, 28 May 2024 10:45:05 -0700 Subject: [PATCH 12/12] add .ipynb_checkpoints to .gitignore, remove checkpoints --- .gitignore | 3 +- .ipynb_checkpoints/README-checkpoint.md | 103 ------------------ .ipynb_checkpoints/pyproject-checkpoint.toml | 52 --------- .../contributors-checkpoint.md | 64 ----------- 4 files changed, 2 insertions(+), 220 deletions(-) delete mode 100644 .ipynb_checkpoints/README-checkpoint.md delete mode 100644 .ipynb_checkpoints/pyproject-checkpoint.toml delete mode 100644 docs/.ipynb_checkpoints/contributors-checkpoint.md diff --git a/.gitignore b/.gitignore index 70dd000b..570ba83b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ docs/build/ **/__pycache__/** build/ docs/_build +**/.ipynb_checkpoints/** *.msh *.xdmf @@ -25,4 +26,4 @@ docs/_build Manifest.toml # bibtex-tidy -*.bib.original \ No newline at end of file +*.bib.original diff --git a/.ipynb_checkpoints/README-checkpoint.md b/.ipynb_checkpoints/README-checkpoint.md deleted file mode 100644 index 4edf733f..00000000 --- a/.ipynb_checkpoints/README-checkpoint.md +++ /dev/null @@ -1,103 +0,0 @@ -# Femwell - -![logo](https://raw.githubusercontent.com/HelgeGehring/femwell/main/logo_inline.svg) - -[![Docs](https://github.com/helgegehring/femwell/actions/workflows/docs.yml/badge.svg)](https://HelgeGehring.github.io/femwell/) -[![Build](https://github.com/helgegehring/femwell/actions/workflows/build.yml/badge.svg)](https://github.com/HelgeGehring/femwell/actions/workflows/build.yml) -[![PiPy](https://img.shields.io/pypi/v/femwell)](https://pypi.org/project/femwell/) -[![Downloads](https://static.pepy.tech/badge/femwell/month)](https://pepy.tech/project/femwell) - -## Welcome to FEMWELL! - -FEMWELL is a physics simulation tool that utilises the Finite Element Method (FEM). With FEMWELL, you can simulate integrated circuits, electronic and photonic systems, and so much more. -The project is created to provide an Open-Source FEM solver. You are welcome to contribute to FEMWELL or just use it. Any feedback and input are valuable and will make FEMWELL better! - -## What is a FEM simulation? - -FEM is a method to solve various types of differential equations that appear in physics, maths or engineering. FEM methods are used when describing how fields behave in an inhomogeneous setting, for example are electromagnetic fields in structured matter described by Maxwell’s equations. FEM uses a mesh to discretize the space and solve Maxwell’s equations via these “finite elements”. - -A FEM simulation typically involves the following steps: - -1. Discretization (or Meshing) of the structure -2. Calculation of the problem in each element -3. Assembly accounting for boundary conditions between the elements -4. Solution via iterative solvers or direct solution methods - -FEM can be applied to various problems, from Maxwell’s equations to fluid simulation, heat transport or structural analysis. - -## What is special about FEMWELL? - -First and foremost: FEMWELL is open source! You can just use FEMWELL, you can contribute to FEMWELL and you can modify FEMWELL to fit your specific problem. - -At the moment we focus on photonic and electronic problems, meaning we concentrate on solving Maxwell’s equation. This is useful to understand the physics in modern devices used in classical or quantum computing technologies. - -We are actively working on extending FEMWELL to address other questions. You can find a list of examples below. - -## How can I use FEMWELL? - -The simplest thing it to try out the examples in the browser! Hover the rocket at the top on the example pages and click live code. (Might take some time to load). -For more involved calculations, we recommend installing FEMWELL following the instructions. -If you can to improve FEMWELL, please get in touch with us. We are looking forward to your contributions! - -### Please note: -The documentation is lagging behind the state of code, so there's several features for which there are only examples in the code. - -## Features - -- Photonic eigenmode solver -- Periodic photonic eigenmode solver -- Electric eigenmode solver -- Thermal mode solver (static and transient) -- Coulomb solver - -## Possible Simulations - -### Photonic problems - -- Eigenmodes of waveguides and determining their effective refractive index -- Coupling between neighboring waveguides -- Eigenmodes of bent waveguides -- Propagation loss of circular bends and mode mismatch loss with straight waveguides -- Calculation of the group velocity and its dispersion -- Calculation of overlap-integrals and confinement-factors -- Bragg grating cells -- Grating coupler cells -- Overlap integrals between waveguide modes -- Overlap integral between a waveguide mode and a fiber mode -- Coupled mode theory - coupling between adjacent waveguides -- Heat based photonic phase shifters -- Pockels based photonic phase shifters -- PN junction depletion modulator (analytical) - -### Heat transport -- Static thermal profiles -- Transient thermal behavior - -### Electronics problems - -- Coplanar waveguide RF design -- Eigenmode of a coaxial cable and its specific impedance -- Eigenmodes of electric transmission lines - and determining their propagation constant (in work) -- Static electric fields - -Something missing? Feel free to open an [issue](https://github.com/HelgeGehring/femwell/issues) :) - -## Contributors - -- Helge Gehring (Google, WWU Münster) -- Simon Bilodeau (Google, Princeton University) -- Joaquin Matres (Google) -- Marc de Cea Falco (Google, Massachusetts Institute of Technology) -- Lodovico Rossi (Princeton University) -- Doris Reiter (TU Dortmund University) -- Yannick Augenstein (Google, Karlsruhe Institute of Technology) -- Niko Savola (Google, Aalto University) -- Rouven Glauert (Idalab) -- Markus DeMartini (Google) -- Lucas Grosjean (Google, Femto-ST Institute) -- Eliza Leung (University of Adelaide) -- Duarte Silva (Eindhoven University of Technology) - -Happy about every form of contribution - -pull requests, feature requests, issues, questions, ... :) diff --git a/.ipynb_checkpoints/pyproject-checkpoint.toml b/.ipynb_checkpoints/pyproject-checkpoint.toml deleted file mode 100644 index 39cb7621..00000000 --- a/.ipynb_checkpoints/pyproject-checkpoint.toml +++ /dev/null @@ -1,52 +0,0 @@ -[build-system] -requires = ["poetry-core>=1.6.0"] -build-backend = "poetry.core.masonry.api" - -[tool.poetry] -name = "femwell" -version = "0.0.1" -authors = ["Helge Gehring"] -description = "Mode solver for photonic and electric waveguides based on FEM" -homepage = "https://github.com/HelgeGehring/femwell" -keywords = [ - "integrated photonics", - "silicon photonics", - "mode solving", - "finite element analysis" -] -classifiers = [ - "Development Status :: 4 - Beta", - "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3", - "Topic :: Scientific/Engineering" -] -license = "GPLv3" -readme = "README.md" - -[tool.poetry.urls] -Documentation = "https://HelgeGehring.github.io/femwell/" - -[tool.poetry.dependencies] -python = ">=3.8" -scikit-fem = ">=8.1.0" -gmsh = "*" -pygmsh = "*" -matplotlib = "*" -meshwell = ">=1.0.2" - -[tool.poetry.group.test.dependencies] -pytest = "*" -flake8 = "*" - -[tool.poetry.group.docs.dependencies] -python = ">=3.10" -tqdm = "*" -enlighten = "*" -pint = ">0.20.1" -# sphinx-book-theme = "*" -jupytext = "*" -# myst-parser = "*" -pandas = "*" - -[tool.black] -line-length = 100 \ No newline at end of file diff --git a/docs/.ipynb_checkpoints/contributors-checkpoint.md b/docs/.ipynb_checkpoints/contributors-checkpoint.md deleted file mode 100644 index 0c855d95..00000000 --- a/docs/.ipynb_checkpoints/contributors-checkpoint.md +++ /dev/null @@ -1,64 +0,0 @@ -# Contributors - -## Helge Gehring - -```{figure} https://avatars.githubusercontent.com/HelgeGehring ---- -align: left -figclass: avatar -target: https://github.com/HelgeGehring ---- - -Helge received the B.Sc. and M.Sc. degrees in physics from the University of Münster in Germany. -He worked during his bachelor's studies as a software engineer at [DEOS](https://www.deos-ag.com/). -For his bachelor's thesis he investigated the quantum dynamics of quantum dots via large-scale simulations on supercomputers. -Subsequently, he switched to applied physics and worked on integrated photonics for his master's thesis, during which he had a research stay at the University of Oxford, UK. -Afterwards, he started his doctoral studies at the University of Münster where he combined photonic circuits with 3D micro-structures to enable low-loss ultra-broadband fiber-to-chip interfacing. -Helge received a scholarship for his B.Sc. and M.Sc. degrees and a fellowship for his doctoral studies from the Studienstiftung des deutschen Volkes. -He took a break from his doctoral studies to spend a year as a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X) and recently returned full-time to X. -``` - -## Simon Bilodeau - -```{figure} https://avatars.githubusercontent.com/simbilod ---- -align: left -figclass: avatar -target: https://github.com/simbilod ---- -Simon received the B.Sc. and M.Sc. degrees in physics from McGill University, Montreal, QC, Canada, where he conducted nanoelectronics research at ultralow temperatures. He is currently working toward the Ph.D. degree in electrical and computer engineering in the Lightwave Communications Research Lab, Princeton University, Princeton, NJ, USA. His research interests include nanophotonics, optoelectronics, integrated photonic systems, and neuromorphic computing. He is the recipient of Canada's NSERC and Québec's FRQNT Doctoral scholarships. Simon is currently a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X). -``` - -## Joaquin Matres - -```{figure} https://avatars.githubusercontent.com/joamatab ---- -align: left -figclass: avatar -target: https://github.com/joamatab ---- -Joaquin received his Engineering (2009), M.Sc. (2010) and PhD (2014) degree in Telecommunications from the Universidad Politecnica de Valencia, Spain. For his PhD he built CMOS-compatible all-optical switches and logic gates at IMEC and CEA-LETI foundries. During his 6 month internship in Intel Corporation and 2.5 years in Hewlett Packard Labs, he worked on hybrid tunable lasers, wavelength selective switches and microring based optical transceivers. Then as the first engineer that joined PsiQuantum he worked on design, layout and validation of large scale Quantum computing circuits, where he was involved in most tapeouts during 3 years. For the last 3 years he has been working at [X, the moonshot factory](https://x.company) (formerly known as Google X) developing Free Space Optical Transceivers to bring affordable internet to developing countries. -``` - -## Marc de Cea Falco - -```{figure} https://avatars.githubusercontent.com/mdecea ---- -align: left -figclass: avatar -target: https://github.com/mdecea ---- -Marc is a graduate student at MIT working in CMOS photonics with a high emphasis in communications applications. The research ranges from low power applications in the data center and high performance computing space to satellite and free space optical communications. He received his B.S. degree in Physics Engineering and Telecommunications Engineering from the Technical University of Catalonia in Barcelona, Spain. He worked as an intern at Ayar Labs designing and implementing automated test setups for optical devices. He is a recipient of the La Caixa fellowship. -Marc is currently a resident at [X, the moonshot factory](https://x.company) (formerly known as Google X). -``` - -## What about you? - -```{figure} https://github.com/identicons/HelgeGehring.png ---- -align: left -figclass: avatar -target: . ---- -What about you? We're happy about everyone joining our project! -```