emerge 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

Files changed (127) hide show
  1. {emerge-1.1.0 → emerge-1.2.0}/.bumpversion.toml +8 -2
  2. {emerge-1.1.0 → emerge-1.2.0}/.gitignore +0 -2
  3. {emerge-1.1.0 → emerge-1.2.0}/PKG-INFO +1 -1
  4. {emerge-1.1.0 → emerge-1.2.0}/emerge/__init__.py +2 -1
  5. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/const.py +2 -1
  6. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/ned2_interp.py +122 -42
  7. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/__init__.py +1 -1
  8. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/operations.py +20 -0
  9. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/pcb.py +34 -16
  10. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/shapes.py +1 -0
  11. emerge-1.2.0/emerge/_emerge/geo/step.py +213 -0
  12. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geometry.py +166 -19
  13. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/material.py +2 -0
  14. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mesh3d.py +1 -3
  15. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mesher.py +33 -9
  16. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mth/common_functions.py +1 -1
  17. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mth/optimized.py +2 -2
  18. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/adaptive_mesh.py +451 -100
  19. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
  20. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/microwave_3d.py +2 -2
  21. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/microwave_bc.py +1 -0
  22. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/microwave_data.py +95 -5
  23. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/pyvista/display.py +42 -15
  24. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/simple_plots.py +116 -4
  25. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/selection.py +17 -2
  26. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/simmodel.py +111 -49
  27. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/solve_interfaces/cudss_interface.py +1 -1
  28. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/solver.py +3 -3
  29. {emerge-1.1.0 → emerge-1.2.0}/emerge/plot.py +1 -1
  30. emerge-1.2.0/examples/Connector_MicrostripBoard_Solder.step +3975 -0
  31. {emerge-1.1.0 → emerge-1.2.0}/examples/demo0_parallel_plate.py +1 -1
  32. {emerge-1.1.0 → emerge-1.2.0}/examples/demo10_sgh.py +1 -1
  33. {emerge-1.1.0 → emerge-1.2.0}/examples/demo11_lumped_element_filter.py +1 -1
  34. {emerge-1.1.0 → emerge-1.2.0}/examples/demo12_mode_alignment.py +1 -1
  35. {emerge-1.1.0 → emerge-1.2.0}/examples/demo13_helix_antenna.py +1 -1
  36. {emerge-1.1.0 → emerge-1.2.0}/examples/demo14_boundary_selection.py +1 -1
  37. {emerge-1.1.0 → emerge-1.2.0}/examples/demo15_strip_slotline_transition.py +5 -4
  38. emerge-1.2.0/examples/demo16_differential_common_mode.py +97 -0
  39. emerge-1.2.0/examples/demo17_step_import.py +131 -0
  40. emerge-1.2.0/examples/demo18_plotting_and_visualization.py +244 -0
  41. {emerge-1.1.0 → emerge-1.2.0}/examples/demo1_stepped_imp_filter.py +4 -5
  42. {emerge-1.1.0 → emerge-1.2.0}/examples/demo2_combline_filter.py +6 -5
  43. {emerge-1.1.0 → emerge-1.2.0}/examples/demo3_coupled_line_filter.py +1 -2
  44. {emerge-1.1.0 → emerge-1.2.0}/examples/demo4_patch_antenna.py +8 -7
  45. {emerge-1.1.0 → emerge-1.2.0}/examples/demo5_revolve.py +1 -1
  46. {emerge-1.1.0 → emerge-1.2.0}/examples/demo6_striplines_with_vias.py +3 -3
  47. {emerge-1.1.0 → emerge-1.2.0}/examples/demo7_periodic_cells.py +1 -1
  48. {emerge-1.1.0 → emerge-1.2.0}/examples/demo8_waveguide_bpf_synthesis.py +4 -1
  49. {emerge-1.1.0 → emerge-1.2.0}/examples/demo9_dielectric_resonator.py +1 -1
  50. {emerge-1.1.0 → emerge-1.2.0}/pyproject.toml +1 -1
  51. {emerge-1.1.0 → emerge-1.2.0}/uv.lock +1 -1
  52. emerge-1.1.0/.nova/Configuration.json +0 -3
  53. emerge-1.1.0/emerge/_emerge/geo/step.py +0 -77
  54. {emerge-1.1.0 → emerge-1.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  55. {emerge-1.1.0 → emerge-1.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  56. {emerge-1.1.0 → emerge-1.2.0}/.python-version +0 -0
  57. {emerge-1.1.0 → emerge-1.2.0}/LICENSE +0 -0
  58. {emerge-1.1.0 → emerge-1.2.0}/README.md +0 -0
  59. {emerge-1.1.0 → emerge-1.2.0}/THIRD_PARTY_LICENSES.md +0 -0
  60. {emerge-1.1.0 → emerge-1.2.0}/UMFPACK_Install_windows.md +0 -0
  61. {emerge-1.1.0 → emerge-1.2.0}/emerge/__main__.py +0 -0
  62. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/__init__.py +0 -0
  63. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/bc.py +0 -0
  64. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/cacherun.py +0 -0
  65. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/coord.py +0 -0
  66. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/cs.py +0 -0
  67. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/dataset.py +0 -0
  68. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/__init__.py +0 -0
  69. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/femdata.py +0 -0
  70. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/index_interp.py +0 -0
  71. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/nedelec2.py +0 -0
  72. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/elements/nedleg2.py +0 -0
  73. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/emerge_update.py +0 -0
  74. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/horn.py +0 -0
  75. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/modeler.py +0 -0
  76. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  77. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/pcb_tools/dxf.py +0 -0
  78. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  79. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/pmlbox.py +0 -0
  80. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo/polybased.py +0 -0
  81. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/geo2d.py +0 -0
  82. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/howto.py +0 -0
  83. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/logsettings.py +0 -0
  84. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mth/_cache_check.py +0 -0
  85. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mth/integrals.py +0 -0
  86. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/mth/pairing.py +0 -0
  87. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/periodic.py +0 -0
  88. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/__init__.py +0 -0
  89. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  90. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  91. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  92. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  93. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  94. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  95. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +0 -0
  96. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  97. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  98. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  99. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/sc.py +0 -0
  100. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  101. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  102. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  103. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/__init__.py +0 -0
  104. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/display.py +0 -0
  105. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  106. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  107. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/pyvista/cmap_maker.py +0 -0
  108. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  109. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/plot.py +0 -0
  110. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/projects/__init__.py +0 -0
  111. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/projects/_gen_base.txt +0 -0
  112. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/projects/_load_base.txt +0 -0
  113. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/projects/generate_project.py +0 -0
  114. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/settings.py +0 -0
  115. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/simstate.py +0 -0
  116. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/simulation_data.py +0 -0
  117. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  118. {emerge-1.1.0 → emerge-1.2.0}/emerge/_emerge/system.py +0 -0
  119. {emerge-1.1.0 → emerge-1.2.0}/emerge/beta/dxf.py +0 -0
  120. {emerge-1.1.0 → emerge-1.2.0}/emerge/cli.py +0 -0
  121. {emerge-1.1.0 → emerge-1.2.0}/emerge/ext.py +0 -0
  122. {emerge-1.1.0 → emerge-1.2.0}/emerge/lib.py +0 -0
  123. {emerge-1.1.0 → emerge-1.2.0}/emerge/materials/__init__.py +0 -0
  124. {emerge-1.1.0 → emerge-1.2.0}/emerge/materials/isola.py +0 -0
  125. {emerge-1.1.0 → emerge-1.2.0}/emerge/materials/rogers.py +0 -0
  126. {emerge-1.1.0 → emerge-1.2.0}/emerge/pyvista.py +0 -0
  127. {emerge-1.1.0 → emerge-1.2.0}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.1.0"
2
+ current_version = "1.2.0"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -75,4 +75,10 @@ filename = "examples/demo13_helix_antenna.py"
75
75
  filename = "examples/demo14_boundary_selection.py"
76
76
 
77
77
  [[tool.bumpversion.files]]
78
- filename = "examples/demo15_strip_slotline_transition.py"
78
+ filename = "examples/demo15_strip_slotline_transition.py"
79
+
80
+ [[tool.bumpversion.files]]
81
+ filename = "examples/demo16_differential_common_mode.py"
82
+
83
+ [[tool.bumpversion.files]]
84
+ filename = "examples/demo17_step_import.py"
@@ -25,8 +25,6 @@ version_update.txt
25
25
 
26
26
  # EMerge data files
27
27
  *.emerge
28
- *.stl
29
- *.step
30
28
  *.s1p
31
29
  *.s2p
32
30
  *.s3p
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
@@ -32,7 +32,7 @@ warnings.filterwarnings(
32
32
  ############################################################
33
33
  import os
34
34
 
35
- __version__ = "1.1.0"
35
+ __version__ = "1.2.0"
36
36
 
37
37
  NTHREADS = "1"
38
38
  os.environ["EMERGE_STD_LOGLEVEL"] = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
@@ -74,6 +74,7 @@ from ._emerge.mesher import Algorithm2D, Algorithm3D
74
74
  from . import lib
75
75
  from ._emerge.howto import _HowtoClass
76
76
  from ._emerge.emerge_update import update_emerge
77
+
77
78
  howto = _HowtoClass()
78
79
 
79
80
  logger.debug('Importing complete!')
@@ -2,4 +2,5 @@ C0 = 299792458
2
2
  Z0 = 376.73031366857
3
3
  PI = 3.14159265358979323846
4
4
  EPS0 = 8.854187818814e-12
5
- MU0 = 1/(C0*C0*EPS0)#1.2566370612720e-6
5
+ #MU0 = 1.2566370612720e-6 #
6
+ MU0 = 1/(C0*C0*EPS0)#
@@ -14,9 +14,35 @@
14
14
  # You should have received a copy of the GNU General Public License
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
- from numba import njit, f8, c16, i8, types # type: ignore
17
+ from numba import njit, f8, c16, i8, types, prange # type: ignore
18
18
  import numpy as np
19
- from ..mth.optimized import compute_distances
19
+ from ..mth.optimized import compute_distances, matmul
20
+
21
+
22
+ @njit(f8[:,:](f8[:,:]), cache=True, nogil=True)
23
+ def matinv(M: np.ndarray) -> np.ndarray:
24
+ """Optimized matrix inverse of 3x3 matrix
25
+
26
+ Args:
27
+ M (np.ndarray): Input matrix M of shape (3,3)
28
+
29
+ Returns:
30
+ np.ndarray: The matrix inverse inv(M)
31
+ """
32
+ out = np.zeros((3,3), dtype=np.float64)
33
+
34
+ det = M[0,0]*M[1,1]*M[2,2] - M[0,0]*M[1,2]*M[2,1] - M[0,1]*M[1,0]*M[2,2] + M[0,1]*M[1,2]*M[2,0] + M[0,2]*M[1,0]*M[2,1] - M[0,2]*M[1,1]*M[2,0]
35
+ out[0,0] = M[1,1]*M[2,2] - M[1,2]*M[2,1]
36
+ out[0,1] = -M[0,1]*M[2,2] + M[0,2]*M[2,1]
37
+ out[0,2] = M[0,1]*M[1,2] - M[0,2]*M[1,1]
38
+ out[1,0] = -M[1,0]*M[2,2] + M[1,2]*M[2,0]
39
+ out[1,1] = M[0,0]*M[2,2] - M[0,2]*M[2,0]
40
+ out[1,2] = -M[0,0]*M[1,2] + M[0,2]*M[1,0]
41
+ out[2,0] = M[1,0]*M[2,1] - M[1,1]*M[2,0]
42
+ out[2,1] = -M[0,0]*M[2,1] + M[0,1]*M[2,0]
43
+ out[2,2] = M[0,0]*M[1,1] - M[0,1]*M[1,0]
44
+ out = out/det
45
+ return out
20
46
 
21
47
  @njit(types.Tuple((f8[:], f8[:], f8[:], f8[:], f8))(f8[:], f8[:], f8[:]), cache = True, nogil=True)
22
48
  def tet_coefficients(xs, ys, zs):
@@ -112,7 +138,7 @@ def local_mapping(vertex_ids, triangle_ids):
112
138
 
113
139
  return out
114
140
 
115
- @njit(types.Tuple((c16[:], c16[:], c16[:]))(f8[:,:], c16[:], i8[:,:], i8[:,:], i8[:,:], f8[:,:], i8[:,:], i8[:]), cache=True, nogil=True)
141
+ @njit(types.Tuple((c16[:], c16[:], c16[:]))(f8[:,:], c16[:], i8[:,:], i8[:,:], i8[:,:], f8[:,:], i8[:,:], i8[:]), cache=True, nogil=True, parallel=False)
116
142
  def ned2_tet_interp(coords: np.ndarray,
117
143
  solutions: np.ndarray,
118
144
  tets: np.ndarray,
@@ -130,22 +156,20 @@ def ned2_tet_interp(coords: np.ndarray,
130
156
  ys = coords[1,:]
131
157
  zs = coords[2,:]
132
158
 
133
- Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
134
- Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
135
- Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
159
+ # Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
160
+ # Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
161
+ # Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
162
+ Ex = np.zeros((nNodes, ), dtype=np.complex128)
163
+ Ey = np.zeros((nNodes, ), dtype=np.complex128)
164
+ Ez = np.zeros((nNodes, ), dtype=np.complex128)
165
+ setnan = np.zeros((nNodes, ), dtype=np.complex128)
166
+ assigned = np.zeros((nNodes,), dtype=np.int64)-1
136
167
 
137
168
  for i_iter in range(tetids.shape[0]):
138
169
  itet = tetids[i_iter]
139
170
 
140
171
  iv1, iv2, iv3, iv4 = tets[:, itet]
141
172
 
142
- g_node_ids = tets[:, itet]
143
- g_edge_ids = edges[:, tet_to_field[:6, itet]]
144
- g_tri_ids = tris[:, tet_to_field[6:10, itet]-nEdges]
145
-
146
- l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
147
- l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
148
-
149
173
  v1 = nodes[:,iv1]
150
174
  v2 = nodes[:,iv2]
151
175
  v3 = nodes[:,iv3]
@@ -159,19 +183,30 @@ def ned2_tet_interp(coords: np.ndarray,
159
183
  blocal[:,0] = bv1
160
184
  blocal[:,1] = bv2
161
185
  blocal[:,2] = bv3
162
- basis = np.linalg.pinv(blocal)
186
+ basis = matinv(blocal)
163
187
 
164
- coords_offset = coords - v1[:,np.newaxis]
165
- coords_local = (basis @ (coords_offset))
166
-
167
- field_ids = tet_to_field[:, itet]
168
- Etet = solutions[field_ids]
188
+ v1x, v1y, v1z = v1[0], v1[1], v1[2]
189
+
190
+ coords_offset = coords*1.0
191
+ coords_offset[0,:] = coords_offset[0,:] - v1x
192
+ coords_offset[1,:] = coords_offset[1,:] - v1y
193
+ coords_offset[2,:] = coords_offset[2,:] - v1z
194
+
195
+ coords_local = matmul(basis, coords_offset)#(basis @ (coords_offset))
169
196
 
170
197
  inside = ((coords_local[0,:] + coords_local[1,:] + coords_local[2,:]) <= 1.00000001) & (coords_local[0,:] >= -1e-6) & (coords_local[1,:] >= -1e-6) & (coords_local[2,:] >= -1e-6)
171
198
 
172
199
  if inside.sum() == 0:
173
200
  continue
174
201
 
202
+ assigned[inside] = itet
203
+
204
+ for i_iter in range(tetids.shape[0]):
205
+ itet = tetids[i_iter]
206
+
207
+ inside = assigned==itet
208
+ if inside.sum() == 0:
209
+ continue
175
210
  ######### INSIDE THE TETRAHEDRON #########
176
211
 
177
212
  x = xs[inside==1]
@@ -184,6 +219,16 @@ def ned2_tet_interp(coords: np.ndarray,
184
219
 
185
220
  a_s, b_s, c_s, d_s, V = tet_coefficients(xvs, yvs, zvs)
186
221
 
222
+ g_node_ids = tets[:, itet]
223
+ g_edge_ids = edges[:, tet_to_field[:6, itet]]
224
+ g_tri_ids = tris[:, tet_to_field[6:10, itet]-nEdges]
225
+
226
+ l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
227
+ l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
228
+
229
+ field_ids = tet_to_field[:, itet]
230
+ Etet = solutions[field_ids]
231
+
187
232
  Em1s = Etet[0:6]
188
233
  Ef1s = Etet[6:10]
189
234
  Em2s = Etet[10:16]
@@ -192,6 +237,7 @@ def ned2_tet_interp(coords: np.ndarray,
192
237
  Exl = np.zeros(x.shape, dtype=np.complex128)
193
238
  Eyl = np.zeros(x.shape, dtype=np.complex128)
194
239
  Ezl = np.zeros(x.shape, dtype=np.complex128)
240
+
195
241
  V1 = (216*V**3)
196
242
  for ie in range(6):
197
243
  Em1, Em2 = Em1s[ie], Em2s[ie]
@@ -243,12 +289,17 @@ def ned2_tet_interp(coords: np.ndarray,
243
289
  Eyl += ey
244
290
  Ezl += ez
245
291
 
246
- Ex[inside] = Exl
247
- Ey[inside] = Eyl
248
- Ez[inside] = Ezl
292
+ Ex[inside] += Exl
293
+ Ey[inside] += Eyl
294
+ Ez[inside] += Ezl
295
+ setnan[inside] += 1
296
+
297
+ Ex[setnan==0] = np.nan
298
+ Ey[setnan==0] = np.nan
299
+ Ez[setnan==0] = np.nan
249
300
  return Ex, Ey, Ez
250
301
 
251
- @njit(types.Tuple((c16[:], c16[:], c16[:]))(f8[:,:], c16[:], i8[:,:], i8[:,:], i8[:,:], f8[:,:], i8[:,:], c16[:], i8[:]), cache=True, nogil=True)
302
+ @njit(types.Tuple((c16[:], c16[:], c16[:]))(f8[:,:], c16[:], i8[:,:], i8[:,:], i8[:,:], f8[:,:], i8[:,:], c16[:], i8[:]), cache=True, nogil=True, parallel=False)
252
303
  def ned2_tet_interp_curl(coords: np.ndarray,
253
304
  solutions: np.ndarray,
254
305
  tets: np.ndarray,
@@ -266,22 +317,21 @@ def ned2_tet_interp_curl(coords: np.ndarray,
266
317
  xs = coords[0,:]
267
318
  ys = coords[1,:]
268
319
  zs = coords[2,:]
269
-
270
- Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
271
- Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
272
- Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
320
+
321
+ # Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
322
+ # Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
323
+ # Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
324
+ Ex = np.zeros((nNodes, ), dtype=np.complex128)
325
+ Ey = np.zeros((nNodes, ), dtype=np.complex128)
326
+ Ez = np.zeros((nNodes, ), dtype=np.complex128)
327
+ setnan = np.zeros((nNodes, ), dtype=np.complex128)
328
+ assigned = np.zeros((nNodes,), dtype=np.int64)-1
273
329
 
274
330
  for i_iter in range(tetids.shape[0]):
275
331
  itet = tetids[i_iter]
276
332
 
277
333
  iv1, iv2, iv3, iv4 = tets[:, itet]
278
334
 
279
- g_node_ids = tets[:, itet]
280
- g_edge_ids = edges[:, tet_to_field[:6, itet]]
281
- g_tri_ids = tris[:, tet_to_field[6:10, itet]-nEdges]
282
-
283
- l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
284
- l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
285
335
 
286
336
  v1 = nodes[:,iv1]
287
337
  v2 = nodes[:,iv2]
@@ -296,19 +346,43 @@ def ned2_tet_interp_curl(coords: np.ndarray,
296
346
  blocal[:,0] = bv1
297
347
  blocal[:,1] = bv2
298
348
  blocal[:,2] = bv3
299
- basis = np.linalg.pinv(blocal)
300
-
301
- coords_offset = coords - v1[:,np.newaxis]
302
- coords_local = (basis @ (coords_offset))
349
+ basis = matinv(blocal)
303
350
 
304
- field_ids = tet_to_field[:, itet]
305
- Etet = solutions[field_ids]
351
+ v1x, v1y, v1z = v1[0], v1[1], v1[2]
352
+
353
+ coords_offset = coords*1.0
354
+ coords_offset[0,:] = coords_offset[0,:] - v1x
355
+ coords_offset[1,:] = coords_offset[1,:] - v1y
356
+ coords_offset[2,:] = coords_offset[2,:] - v1z
357
+
358
+ #coords_local = (basis @ (coords_offset))
359
+ coords_local = matmul(basis, coords_offset)
306
360
 
307
361
  inside = ((coords_local[0,:] + coords_local[1,:] + coords_local[2,:]) <= 1.00000001) & (coords_local[0,:] >= -1e-6) & (coords_local[1,:] >= -1e-6) & (coords_local[2,:] >= -1e-6)
308
362
 
309
363
  if inside.sum() == 0:
310
364
  continue
311
365
 
366
+ assigned[inside] = itet
367
+
368
+
369
+ for i_iter in range(tetids.shape[0]):
370
+ itet = tetids[i_iter]
371
+
372
+ inside = (assigned==itet)
373
+ if inside.sum() == 0:
374
+ continue
375
+
376
+ g_node_ids = tets[:, itet]
377
+ g_edge_ids = edges[:, tet_to_field[:6, itet]]
378
+ g_tri_ids = tris[:, tet_to_field[6:10, itet]-nEdges]
379
+
380
+ l_edge_ids = local_mapping(g_node_ids, g_edge_ids)
381
+ l_tri_ids = local_mapping(g_node_ids, g_tri_ids)
382
+
383
+ field_ids = tet_to_field[:, itet]
384
+ Etet = solutions[field_ids]
385
+
312
386
  const = c[itet]
313
387
  ######### INSIDE THE TETRAHEDRON #########
314
388
 
@@ -420,9 +494,15 @@ def ned2_tet_interp_curl(coords: np.ndarray,
420
494
  Eyl += ey
421
495
  Ezl += ez
422
496
 
423
- Ex[inside] = Exl*const
424
- Ey[inside] = Eyl*const
425
- Ez[inside] = Ezl*const
497
+ Ex[inside] += Exl*const
498
+ Ey[inside] += Eyl*const
499
+ Ez[inside] += Ezl*const
500
+ setnan[inside] += 1
501
+
502
+ Ex[setnan==0] = np.nan
503
+ Ey[setnan==0] = np.nan
504
+ Ez[setnan==0] = np.nan
505
+
426
506
  return Ex, Ey, Ez
427
507
 
428
508
  @njit(types.Tuple((c16[:], c16[:], c16[:]))(f8[:,:], c16[:], i8[:,:], f8[:,:], i8[:,:]), cache=True, nogil=True)
@@ -19,6 +19,6 @@ from .pcb import PCB, PCBLayer
19
19
  from .pmlbox import pmlbox
20
20
  from .horn import Horn
21
21
  from .shapes import Cylinder, CoaxCylinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment, Cone
22
- from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect, unite, expand_surface, stretch
22
+ from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect, unite, expand_surface, stretch, extrude
23
23
  from .polybased import XYPolygon, GeoPrism, Disc, Curve
24
24
  from .step import STEPItems
@@ -282,6 +282,24 @@ def stretch(main: GeoObject, fx: float = 1, fy: float = 1, fz: float = 1, origin
282
282
 
283
283
  return main
284
284
 
285
+ def extrude(main: GeoSurface, dx: float = 0.0, dy: float = 0.0, dz: float = 0.0) -> GeoObject:
286
+ """Extrudes a surface entity by a displacement
287
+
288
+ Args:
289
+ main (GeoSurface): _description_
290
+ dx (float): _description_
291
+ dy (float): _description_
292
+ dz (float): _description_
293
+
294
+ Returns:
295
+ GeoObject: _description_
296
+ """
297
+ dtout = gmsh.model.occ.extrude(main.dimtags, dx, dy, dz)
298
+ out = [dt[1] for dt in dtout if dt[0]==3]
299
+ obj_out = GeoVolume(out, name=f'Extrusion[{main.name}]')
300
+ gmsh.model.occ.synchronize()
301
+ return obj_out
302
+
285
303
 
286
304
  @overload
287
305
  def unite(*objects: GeoVolume) -> GeoVolume: ...
@@ -313,7 +331,9 @@ def unite(*objects: GeoObject) -> GeoObject:
313
331
  other._exists = False
314
332
  new_dimtags, mapping = gmsh.model.occ.fuse(dts, main.dimtags)
315
333
 
334
+ newname = 'Union[' + ','.join([obj.name for obj in objects]) + ']'
316
335
  new_obj = GeoObject.from_dimtags(new_dimtags)._take_tools(*objects)
336
+ new_obj.name = newname
317
337
  new_obj.set_material(main.material)
318
338
  new_obj.prio_set(main._priority)
319
339
 
@@ -25,7 +25,7 @@ from .polybased import XYPolygon
25
25
  from .operations import change_coordinate_system, unite
26
26
  from .pcb_tools.macro import parse_macro
27
27
  from .pcb_tools.calculator import PCBCalculator
28
-
28
+ from ..logsettings import DEBUG_COLLECTOR
29
29
  import numpy as np
30
30
  from loguru import logger
31
31
  from typing import Literal, Callable, overload
@@ -960,7 +960,8 @@ class PCB:
960
960
  stack: list[PCBLayer] = None,
961
961
  name: str | None = None,
962
962
  trace_thickness: float | None = None,
963
- zs: np.ndarray | None = None
963
+ zs: np.ndarray | None = None,
964
+ thick_traces: bool = False,
964
965
  ):
965
966
  """Creates a new PCB layout class instance
966
967
 
@@ -974,9 +975,11 @@ class PCB:
974
975
  stack (list[PCBLayer], optional): Optional list of PCBLayer classes for multilayer PCB with different dielectrics. Defaults to None.
975
976
  name (str | None, optional): The PCB object name. Defaults to None.
976
977
  trace_thickness (float | None, optional): The conductor trace thickness if important. Defaults to None.
978
+ thick_traces: (bool, optional): If traces should be given a thickness and modeled in 3D. Defaults to False
977
979
  """
978
980
 
979
981
  self.thickness: float = thickness
982
+ self._thick_traces: bool = thick_traces
980
983
  self._stack: list[PCBLayer] = []
981
984
 
982
985
  if zs is not None:
@@ -1016,9 +1019,10 @@ class PCB:
1016
1019
 
1017
1020
  self.dielectric_priority: int = 11
1018
1021
  self.via_priority: int = 12
1022
+ self.conductor_priority: int = 13
1019
1023
 
1020
- self.traces: list[GeoPolygon] = []
1021
- self.ports: list[GeoPolygon] = []
1024
+ self.traces: list[GeoPolygon | GeoVolume] = []
1025
+ self.ports: list[GeoPolygon | GeoVolume] = []
1022
1026
  self.vias: list[Via] = []
1023
1027
 
1024
1028
  self.xs: list[float] = []
@@ -1032,7 +1036,10 @@ class PCB:
1032
1036
  self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
1033
1037
 
1034
1038
  self.name: str = _NAME_MANAGER(name, self._DEFNAME)
1035
-
1039
+
1040
+ if not self._thick_traces and self.trace_material is not PEC:
1041
+ DEBUG_COLLECTOR.add_report('Non PEC surface materials are used without thick traces. The SurfaceImpedance boundary condition will be used that is known to not be accurate.' +
1042
+ 'Please set thick_traces=True in the PCB constructor to ensure accurate losses until this issue is fixed.')
1036
1043
 
1037
1044
  ############################################################
1038
1045
  # PROPERTIES #
@@ -1099,7 +1106,7 @@ class PCB:
1099
1106
  self.paths.append(StripPath(self))
1100
1107
  return self.paths[path_nr]
1101
1108
 
1102
- def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
1109
+ def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon | GeoVolume:
1103
1110
  """ Generates a GeoPoly out of a list of (x,y) coordinate tuples.
1104
1111
 
1105
1112
 
@@ -1120,8 +1127,16 @@ class PCB:
1120
1127
 
1121
1128
  tag_wire = gmsh.model.occ.addWire(ltags)
1122
1129
  planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
1123
- poly = GeoPolygon([planetag,], name=name)
1124
- poly._store('thickness', self.trace_thickness)
1130
+ if self._thick_traces:
1131
+ if self.trace_thickness is None:
1132
+ raise ValueError('Trace thickness not defined, cannot generate polygons. Make sure to define a trace thickness in the PCB() constructor.')
1133
+ dx, dy, dz = self.cs.zax.np*self.trace_thickness
1134
+ dimtags = gmsh.model.occ.extrude([(2,planetag),], dx, dy, dz)
1135
+ voltags = [dt[1] for dt in dimtags if dt[0]==3]
1136
+ poly = GeoVolume(voltags, name=name).prio_set(self.conductor_priority)
1137
+ else:
1138
+ poly = GeoPolygon([planetag,], name=name)
1139
+ poly._store('thickness', self.trace_thickness)
1125
1140
  return poly
1126
1141
 
1127
1142
 
@@ -1236,7 +1251,7 @@ class PCB:
1236
1251
  height: float | None = None,
1237
1252
  origin: tuple[float, float] | None = None,
1238
1253
  alignment: Alignment = Alignment.CORNER,
1239
- name: str | None = None) -> GeoSurface:
1254
+ name: str | None = None) -> GeoSurface | GeoVolume:
1240
1255
  """Generates a generic rectangular plate in the XY grid.
1241
1256
  If no size is provided, it defaults to the entire PCB size assuming that the bounds are determined.
1242
1257
 
@@ -1264,10 +1279,13 @@ class PCB:
1264
1279
  origin[1] - height*self.unit/2,
1265
1280
  origin[2])
1266
1281
 
1267
- plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0), name=name) # type: ignore
1268
- plane._store('thickness', self.thickness)
1269
- plane = change_coordinate_system(plane, self.cs) # type: ignore
1270
- plane.set_material(self.trace_material)
1282
+ if self._thick_traces:
1283
+ plane = Box(width*self.unit, height*self.unit, self.trace_thickness, position=origin, name=name).set_material(self.trace_material)
1284
+ else:
1285
+ plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0), name=name) # type: ignore
1286
+ plane._store('thickness', self.thickness)
1287
+ plane = change_coordinate_system(plane, self.cs) # type: ignore
1288
+ plane.set_material(self.trace_material)
1271
1289
  return plane # type: ignore
1272
1290
 
1273
1291
  def radial_stub(self, pos: tuple[float, float], length: float, angle: float, direction: tuple[float, float], Nsections: int = 8, w0: float = 0, z: float = 0, material: Material = None, name: str = None) -> None:
@@ -1525,12 +1543,12 @@ class PCB:
1525
1543
  self.polies.append(poly)
1526
1544
 
1527
1545
  @overload
1528
- def compile_paths(self, merge: Literal[True]) -> GeoSurface: ...
1546
+ def compile_paths(self, merge: Literal[True]) -> GeoSurface | GeoVolume: ...
1529
1547
 
1530
1548
  @overload
1531
- def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface]: ...
1549
+ def compile_paths(self, merge: Literal[False] = ...) -> list[GeoSurface] | list[GeoVolume]: ...
1532
1550
 
1533
- def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface:
1551
+ def compile_paths(self, merge: bool = False) -> list[GeoPolygon] | GeoSurface | GeoVolume:
1534
1552
  """Compiles the striplines and returns a list of polygons or asingle one.
1535
1553
 
1536
1554
  The Z=0 argument determines the height of the striplines. Z=0 corresponds to the top of
@@ -314,6 +314,7 @@ class Cylinder(GeoVolume):
314
314
  height*ax[0], height*ax[1], height*ax[2],
315
315
  radius)
316
316
  super().__init__(cyl, name=name)
317
+
317
318
  self._add_face_pointer('front', cs.origin, -cs.zax.np)
318
319
  self._add_face_pointer('back', cs.origin+height*cs.zax.np, cs.zax.np)
319
320
  self._add_face_pointer('bottom', cs.origin, -cs.zax.np)