emerge 0.4.6__py3-none-any.whl → 0.4.8__py3-none-any.whl

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 (80) hide show
  1. emerge/__init__.py +54 -0
  2. emerge/__main__.py +5 -0
  3. emerge/_emerge/__init__.py +42 -0
  4. emerge/_emerge/bc.py +197 -0
  5. emerge/_emerge/coord.py +119 -0
  6. emerge/_emerge/cs.py +523 -0
  7. emerge/_emerge/dataset.py +36 -0
  8. emerge/_emerge/elements/__init__.py +19 -0
  9. emerge/_emerge/elements/femdata.py +212 -0
  10. emerge/_emerge/elements/index_interp.py +64 -0
  11. emerge/_emerge/elements/legrange2.py +172 -0
  12. emerge/_emerge/elements/ned2_interp.py +645 -0
  13. emerge/_emerge/elements/nedelec2.py +140 -0
  14. emerge/_emerge/elements/nedleg2.py +217 -0
  15. emerge/_emerge/geo/__init__.py +24 -0
  16. emerge/_emerge/geo/horn.py +107 -0
  17. emerge/_emerge/geo/modeler.py +449 -0
  18. emerge/_emerge/geo/operations.py +254 -0
  19. emerge/_emerge/geo/pcb.py +1244 -0
  20. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  21. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  22. emerge/_emerge/geo/pmlbox.py +204 -0
  23. emerge/_emerge/geo/polybased.py +529 -0
  24. emerge/_emerge/geo/shapes.py +427 -0
  25. emerge/_emerge/geo/step.py +77 -0
  26. emerge/_emerge/geo2d.py +86 -0
  27. emerge/_emerge/geometry.py +510 -0
  28. emerge/_emerge/howto.py +214 -0
  29. emerge/_emerge/logsettings.py +5 -0
  30. emerge/_emerge/material.py +118 -0
  31. emerge/_emerge/mesh3d.py +730 -0
  32. emerge/_emerge/mesher.py +339 -0
  33. emerge/_emerge/mth/common_functions.py +33 -0
  34. emerge/_emerge/mth/integrals.py +71 -0
  35. emerge/_emerge/mth/optimized.py +357 -0
  36. emerge/_emerge/periodic.py +263 -0
  37. emerge/_emerge/physics/__init__.py +0 -0
  38. emerge/_emerge/physics/microwave/__init__.py +1 -0
  39. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  40. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  41. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  42. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  43. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  44. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  45. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  46. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  47. emerge/_emerge/physics/microwave/periodic.py +82 -0
  48. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  49. emerge/_emerge/physics/microwave/sc.py +175 -0
  50. emerge/_emerge/physics/microwave/simjob.py +147 -0
  51. emerge/_emerge/physics/microwave/sparam.py +138 -0
  52. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  53. emerge/_emerge/plot/__init__.py +0 -0
  54. emerge/_emerge/plot/display.py +394 -0
  55. emerge/_emerge/plot/grapher.py +93 -0
  56. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  57. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  58. emerge/_emerge/plot/pyvista/display.py +931 -0
  59. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  60. emerge/_emerge/plot/simple_plots.py +551 -0
  61. emerge/_emerge/plot.py +225 -0
  62. emerge/_emerge/projects/__init__.py +0 -0
  63. emerge/_emerge/projects/_gen_base.txt +32 -0
  64. emerge/_emerge/projects/_load_base.txt +24 -0
  65. emerge/_emerge/projects/generate_project.py +40 -0
  66. emerge/_emerge/selection.py +596 -0
  67. emerge/_emerge/simmodel.py +444 -0
  68. emerge/_emerge/simulation_data.py +411 -0
  69. emerge/_emerge/solver.py +993 -0
  70. emerge/_emerge/system.py +54 -0
  71. emerge/cli.py +19 -0
  72. emerge/lib.py +57 -0
  73. emerge/plot.py +1 -0
  74. emerge/pyvista.py +1 -0
  75. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  76. emerge-0.4.8.dist-info/RECORD +78 -0
  77. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  78. emerge-0.4.6.dist-info/RECORD +0 -4
  79. emerge-0.4.6.dist-info/entry_points.txt +0 -2
  80. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
@@ -0,0 +1,569 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+ from ..microwave_bc import PEC, BoundaryCondition, RectangularWaveguide, RobinBC, PortBC, Periodic
20
+ from ....elements.nedelec2 import Nedelec2
21
+ from ....elements.nedleg2 import NedelecLegrange2
22
+ from ....mth.optimized import gaus_quad_tri
23
+ from scipy.sparse import csr_matrix, eye
24
+ from loguru import logger
25
+ from ..simjob import SimJob
26
+ from collections import defaultdict
27
+
28
+ C0 = 299792458
29
+ EPS0 = 8.854187818814e-12
30
+
31
+ def matprint(mat: np.ndarray) -> None:
32
+ factor = np.max(np.abs(mat.flatten()))
33
+ if factor == 0:
34
+ factor = 1
35
+ print(mat.real/factor)
36
+
37
+ def select_bc(bcs: list[BoundaryCondition], bctype):
38
+ return [bc for bc in bcs if isinstance(bc,bctype)]
39
+
40
+ def diagnose_matrix(mat: np.ndarray) -> None:
41
+
42
+ if not isinstance(mat, np.ndarray):
43
+ logger.debug('Converting sparse array to flattened array')
44
+ mat = mat[mat.nonzero()].A1
45
+ #mat = np.array(nonzero_mat)
46
+
47
+ ''' Prints all indices of Nan's and infinities in a matrix '''
48
+ ids = np.where(np.isnan(mat))
49
+ if len(ids[0]) > 0:
50
+ logger.error(f'Found NaN at {ids}')
51
+ ids = np.where(np.isinf(mat))
52
+ if len(ids[0]) > 0:
53
+ logger.error(f'Found Inf at {ids}')
54
+ ids = np.where(np.abs(mat) > 1e10)
55
+ if len(ids[0]) > 0:
56
+ logger.error(f'Found large values at {ids}')
57
+ logger.info('Diagnostics finished')
58
+
59
+ #############
60
+ def gen_key(coord, mult):
61
+ return tuple([int(round(c*mult)) for c in coord])
62
+
63
+ def plane_basis_from_points(points: np.ndarray) -> np.ndarray:
64
+ """
65
+ Compute an orthonormal basis from a cloud of 3D points dominantly
66
+ lying on one plane.
67
+
68
+ Parameters
69
+ ----------
70
+ points : ndarray, shape (3, N)
71
+ 3D coordinates of the point cloud.
72
+
73
+ Returns
74
+ -------
75
+ basis : ndarray, shape (3, 3)
76
+ Matrix whose columns are:
77
+ - first principal direction (plane X axis)
78
+ - second principal direction (plane Y axis)
79
+ - plane normal vector (Z axis)
80
+ """
81
+ if points.shape[0] != 3:
82
+ raise ValueError("Input must have shape (3, N)")
83
+
84
+ # Compute centroid
85
+ centroid = points.mean(axis=1, keepdims=True)
86
+
87
+ # Center the data
88
+ points_centered = points - centroid
89
+
90
+ # Compute covariance matrix (3x3)
91
+ C = (points_centered @ points_centered.T) / points.shape[1]
92
+
93
+ # Eigen decomposition
94
+ eigvals, eigvecs = np.linalg.eigh(C)
95
+
96
+ # Sort eigenvectors by descending eigenvalue
97
+ idx = np.argsort(eigvals)[::-1]
98
+ eigvecs = eigvecs[:, idx]
99
+
100
+ # Columns of eigvecs = principal axes
101
+ return eigvecs
102
+
103
+ class Assembler:
104
+
105
+ def __init__(self):
106
+ self.cached_matrices = None
107
+ self.conductivity_limit = 1e7
108
+
109
+
110
+ def assemble_bma_matrices(self,
111
+ field: Nedelec2,
112
+ er: np.ndarray,
113
+ ur: np.ndarray,
114
+ sig: np.ndarray,
115
+ k0: float,
116
+ port: PortBC,
117
+ bcs: list[BoundaryCondition]) -> tuple[np.ndarray, np.ndarray, np.ndarray, NedelecLegrange2]:
118
+
119
+ from .generalized_eigen import generelized_eigenvalue_matrix
120
+
121
+ mesh = field.mesh
122
+ tri_ids = mesh.get_triangles(port.tags)
123
+
124
+ origin = tuple([c-n for c,n in zip(port.cs.origin, port.cs.gzhat)])
125
+
126
+ boundary_surface = mesh.boundary_surface(port.tags, origin)
127
+ nedlegfield = NedelecLegrange2(boundary_surface, port.cs)
128
+
129
+ ermesh = er[:,:,tri_ids]
130
+ urmesh = ur[:,:,tri_ids]
131
+ sigmesh = sig[tri_ids]
132
+
133
+ ermesh = ermesh - 1j * sigmesh/(k0*C0*EPS0)
134
+
135
+ E, B = generelized_eigenvalue_matrix(nedlegfield, ermesh, urmesh, port.cs._basis, k0)
136
+
137
+ pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
138
+ if len(pecs) > 0:
139
+ logger.debug('Implementing PEC BCs')
140
+
141
+ pec_ids = []
142
+
143
+ # Process all concutors. Everything above the conductivity limit is considered pec.
144
+ for it in range(boundary_surface.n_tris):
145
+ if sigmesh[it] > self.conductivity_limit:
146
+ pec_ids.extend(list(nedlegfield.tri_to_field[:,it]))
147
+
148
+ # Process all PEC Boundary Conditions
149
+ pec_edges = []
150
+ pec_vertices = []
151
+ for pec in pecs:
152
+ face_tags = pec.tags
153
+
154
+ tri_ids = mesh.get_triangles(face_tags)
155
+ edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
156
+ for ii in edge_ids:
157
+ i2 = nedlegfield.mesh.from_source_edge(ii)
158
+ if i2 is None:
159
+ continue
160
+ eids = nedlegfield.edge_to_field[:, i2]
161
+ pec_ids.extend(list(eids))
162
+ pec_edges.append(eids[0])
163
+ pec_vertices.append(eids[3]-nedlegfield.n_xy)
164
+ pec_vertices.append(eids[4]-nedlegfield.n_xy)
165
+
166
+
167
+ for ii in tri_ids:
168
+ i2 = nedlegfield.mesh.from_source_tri(ii)
169
+ if i2 is None:
170
+ continue
171
+ tids = nedlegfield.tri_to_field[:, i2]
172
+ pec_ids.extend(list(tids))
173
+
174
+ port._field = nedlegfield
175
+ port._pece = pec_edges
176
+ port._pecv = pec_vertices
177
+ # Process all port boundary Conditions
178
+ pec_ids = set(pec_ids)
179
+ solve_ids = [i for i in range(nedlegfield.n_field) if i not in pec_ids]
180
+
181
+ return E, B, np.array(solve_ids), nedlegfield
182
+
183
+ def assemble_freq_matrix(self, field: Nedelec2,
184
+ er: np.ndarray,
185
+ ur: np.ndarray,
186
+ sig: np.ndarray,
187
+ bcs: list[BoundaryCondition],
188
+ frequency: float,
189
+ cache_matrices: bool = False) -> SimJob:
190
+
191
+ from .curlcurl import tet_mass_stiffness_matrices
192
+ from .robinbc import assemble_robin_bc, assemble_robin_bc_excited
193
+
194
+ mesh = field.mesh
195
+ w0 = 2*np.pi*frequency
196
+ k0 = w0/C0
197
+
198
+ er = er - 1j*sig/(w0*EPS0)*np.repeat(np.eye(3)[:, :, np.newaxis], er.shape[2], axis=2)
199
+
200
+ f_dependent_properties = np.any((sig > 0) & (sig < self.conductivity_limit))
201
+
202
+ if cache_matrices and not f_dependent_properties and self.cached_matrices is not None:
203
+ logger.debug(' Retreiving cached matricies')
204
+ E, B = self.cached_matrices
205
+ else:
206
+ logger.debug(' Assembling matrices')
207
+ E, B = tet_mass_stiffness_matrices(field, er, ur)
208
+ self.cached_matrices = (E, B)
209
+
210
+ K: csr_matrix = (E - B*(k0**2)).tocsr()
211
+
212
+ NF = E.shape[0]
213
+
214
+ pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
215
+ robin_bcs: list[RectangularWaveguide] = [bc for bc in bcs if isinstance(bc,RobinBC)]
216
+ ports: list[PortBC] = [bc for bc in bcs if isinstance(bc, PortBC)]
217
+ periodic: list[Periodic] = [bc for bc in bcs if isinstance(bc, Periodic)]
218
+
219
+ b = np.zeros((E.shape[0],)).astype(np.complex128)
220
+ port_vectors = {port.port_number: np.zeros((E.shape[0],)).astype(np.complex128) for port in ports}
221
+ # Process all PEC Boundary Conditions
222
+ pec_ids = []
223
+
224
+ logger.debug(' Implementing PEC Boundary Conditions.')
225
+
226
+ # Conductivity above al imit, consider it all PEC
227
+ for itet in range(field.n_tets):
228
+ if sig[itet] > self.conductivity_limit:
229
+ pec_ids.extend(field.tet_to_field[:,itet])
230
+
231
+ # PEC Boundary conditions
232
+ for pec in pecs:
233
+ if len(pec.tags)==0:
234
+ continue
235
+ face_tags = pec.tags
236
+ tri_ids = mesh.get_triangles(face_tags)
237
+ edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
238
+
239
+ for ii in edge_ids:
240
+ eids = field.edge_to_field[:, ii]
241
+ pec_ids.extend(list(eids))
242
+
243
+ for ii in tri_ids:
244
+ tids = field.tri_to_field[:, ii]
245
+ pec_ids.extend(list(tids))
246
+
247
+ # Robin BCs
248
+ if len(robin_bcs) > 0:
249
+ logger.debug(' Implementing Robin Boundary Conditions.')
250
+
251
+ gauss_points = gaus_quad_tri(4)
252
+
253
+ Bempty = field.empty_tri_matrix()
254
+
255
+ for bc in robin_bcs:
256
+
257
+ for tag in bc.tags:
258
+ face_tags = [tag,]
259
+
260
+ tri_ids = mesh.get_triangles(face_tags)
261
+ nodes = mesh.get_nodes(face_tags)
262
+ edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
263
+
264
+ gamma = bc.get_gamma(k0)
265
+
266
+ def Ufunc(x,y):
267
+ return bc.get_Uinc(x,y,k0)
268
+
269
+ ibasis = bc.get_inv_basis()
270
+ if ibasis is None:
271
+ basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
272
+ ibasis = np.linalg.pinv(basis)
273
+ if bc._include_force:
274
+
275
+ Bempty, b_p = assemble_robin_bc_excited(field, Bempty, tri_ids, Ufunc, gamma, ibasis, bc.cs.origin, gauss_points)
276
+
277
+ port_vectors[bc.port_number] += b_p
278
+
279
+ else:
280
+ Bempty = assemble_robin_bc(field, Bempty, tri_ids, gamma)
281
+ B_p = field.generate_csr(Bempty)
282
+ K = K + B_p
283
+
284
+ if len(periodic) > 0:
285
+ logger.debug(' Implementing Periodic Boundary Conditions.')
286
+
287
+
288
+ # Periodic BCs
289
+ Pmats = []
290
+ remove = set()
291
+ has_periodic = False
292
+
293
+ for bc in periodic:
294
+ Pmat = eye(NF, format='lil', dtype=np.complex128)
295
+ has_periodic = True
296
+ tri_ids_1 = mesh.get_triangles(bc.face1.tags)
297
+ tri_ids_2 = mesh.get_triangles(bc.face2.tags)
298
+ dv = np.array(bc.dv)
299
+ trimapdict = defaultdict(lambda: [None, None])
300
+ edgemapdict = defaultdict(lambda: [None, None])
301
+ mult = int(10**(-np.round(np.log10(min(mesh.edge_lengths.flatten())))+3))
302
+
303
+ edge_ids_1 = set()
304
+ edge_ids_2 = set()
305
+
306
+ phi = bc.phi(k0)
307
+ for i1, i2 in zip(tri_ids_1, tri_ids_2):
308
+ trimapdict[gen_key(mesh.tri_centers[:,i1], mult)][0] = i1
309
+ trimapdict[gen_key(mesh.tri_centers[:,i2]-dv, mult)][1] = i2
310
+
311
+ ie1, ie2, ie3 = mesh.tri_to_edge[:,i1]
312
+ edge_ids_1.update({ie1, ie2, ie3})
313
+ ie1, ie2, ie3 = mesh.tri_to_edge[:,i2]
314
+ edge_ids_2.update({ie1, ie2, ie3})
315
+
316
+ for i1, i2 in zip(list(edge_ids_1), list(edge_ids_2)):
317
+ edgemapdict[gen_key(mesh.edge_centers[:,i1], mult)][0] = i1
318
+ edgemapdict[gen_key(mesh.edge_centers[:,i2]-dv, mult)][1] = i2
319
+
320
+
321
+ for t1, t2 in trimapdict.values():
322
+ f11, f21 = field.tri_to_field[[3,7],t1]
323
+ f12, f22 = field.tri_to_field[[3,7],t2]
324
+ Pmat[f12,f12] = 0
325
+ Pmat[f22,f22] = 0
326
+ if Pmat[f12,f11] == 0:
327
+ Pmat[f12,f11] += phi
328
+ else:
329
+ Pmat[f12,f11] *= phi
330
+
331
+ if Pmat[f22,f21] == 0:
332
+ Pmat[f22,f21] += phi
333
+ else:
334
+ Pmat[f22,f21] *= phi
335
+ remove.add(f12)
336
+ remove.add(f22)
337
+
338
+ for e1, e2 in edgemapdict.values():
339
+ f11, f21 = field.edge_to_field[:,e1]
340
+ f12, f22 = field.edge_to_field[:,e2]
341
+ Pmat[f12,f12] = 0
342
+ Pmat[f22,f22] = 0
343
+ if Pmat[f12,f11] == 0:
344
+ Pmat[f12,f11] += phi
345
+ else:
346
+ Pmat[f12,f11] *= phi
347
+
348
+ if Pmat[f22,f21] == 0:
349
+ Pmat[f22,f21] += phi
350
+ else:
351
+ Pmat[f22,f21] *= phi
352
+ remove.add(f12)
353
+ remove.add(f22)
354
+
355
+ Pmats.append(Pmat)
356
+
357
+ if Pmats:
358
+ Pmat = Pmats[0]
359
+ for P2 in Pmats[1:]:
360
+ Pmat = Pmat @ P2
361
+ Pmat = Pmat.tocsr()
362
+ remove = np.array(sorted(list(remove)))
363
+ all_indices = np.arange(NF)
364
+ keep_indices = np.setdiff1d(all_indices, remove)
365
+ Pmat = Pmat[:,keep_indices]
366
+ else:
367
+ Pmat = None
368
+
369
+ pec_ids = set(pec_ids)
370
+ solve_ids = np.array([i for i in range(E.shape[0]) if i not in pec_ids])
371
+
372
+ if has_periodic:
373
+ mask = np.zeros((NF,))
374
+ mask[solve_ids] = 1
375
+ mask = mask[keep_indices]
376
+ solve_ids = np.argwhere(mask==1).flatten()
377
+
378
+ logger.debug(f'Number of tets: {mesh.n_tets}')
379
+ logger.debug(f'Number of DoF: {K.shape[0]}')
380
+ simjob = SimJob(K, b, k0*299792458/(2*np.pi), True)
381
+
382
+ simjob.port_vectors = port_vectors
383
+ simjob.solve_ids = solve_ids
384
+
385
+ if has_periodic:
386
+ simjob.P = Pmat
387
+ simjob.Pd = Pmat.getH()
388
+ simjob.has_periodic = has_periodic
389
+
390
+ return simjob
391
+
392
+ def assemble_eig_matrix(self, field: Nedelec2,
393
+ er: np.ndarray,
394
+ ur: np.ndarray,
395
+ sig: np.ndarray,
396
+ bcs: list[BoundaryCondition],
397
+ frequency: float) -> SimJob:
398
+
399
+ from .curlcurl import tet_mass_stiffness_matrices
400
+ from .robinbc import assemble_robin_bc
401
+
402
+ mesh = field.mesh
403
+ w0 = 2*np.pi*frequency
404
+ k0 = w0/C0
405
+
406
+ er = er - 1j*sig/(w0*EPS0)*np.repeat(np.eye(3)[:, :, np.newaxis], er.shape[2], axis=2)
407
+
408
+ logger.debug(' Assembling matrices')
409
+ E, B = tet_mass_stiffness_matrices(field, er, ur)
410
+ self.cached_matrices = (E, B)
411
+
412
+ NF = E.shape[0]
413
+
414
+ pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
415
+ robin_bcs: list[RectangularWaveguide] = [bc for bc in bcs if isinstance(bc,RobinBC)]
416
+ periodic: list[Periodic] = [bc for bc in bcs if isinstance(bc, Periodic)]
417
+
418
+ # Process all PEC Boundary Conditions
419
+ pec_ids = []
420
+
421
+ logger.debug(' Implementing PEC Boundary Conditions.')
422
+
423
+ # Conductivity above a limit, consider it all PEC
424
+ for itet in range(field.n_tets):
425
+ if sig[itet] > self.conductivity_limit:
426
+ pec_ids.extend(field.tet_to_field[:,itet])
427
+
428
+ # PEC Boundary conditions
429
+ for pec in pecs:
430
+ if len(pec.tags)==0:
431
+ continue
432
+ face_tags = pec.tags
433
+ tri_ids = mesh.get_triangles(face_tags)
434
+ edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
435
+
436
+ for ii in edge_ids:
437
+ eids = field.edge_to_field[:, ii]
438
+ pec_ids.extend(list(eids))
439
+
440
+ for ii in tri_ids:
441
+ tids = field.tri_to_field[:, ii]
442
+ pec_ids.extend(list(tids))
443
+
444
+ # Robin BCs
445
+ if len(robin_bcs) > 0:
446
+ logger.debug(' Implementing Robin Boundary Conditions.')
447
+
448
+ for bc in robin_bcs:
449
+ for tag in bc.tags:
450
+ face_tags = [tag,]#bc.tags
451
+
452
+ tri_ids = mesh.get_triangles(face_tags)
453
+ nodes = mesh.get_nodes(face_tags)
454
+ edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
455
+
456
+ gamma = bc.get_gamma(k0)
457
+
458
+ ibasis = bc.get_inv_basis()
459
+ if ibasis is None:
460
+ basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
461
+ ibasis = np.linalg.pinv(basis)
462
+ B_p = assemble_robin_bc(field, tri_ids, gamma)
463
+ if bc._include_stiff:
464
+ B = B + B_p
465
+
466
+ if len(periodic) > 0:
467
+ logger.debug(' Implementing Periodic Boundary Conditions.')
468
+
469
+ # Periodic BCs
470
+ Pmats = []
471
+ remove = set()
472
+ has_periodic = False
473
+
474
+ for bc in periodic:
475
+ Pmat = eye(NF, format='lil', dtype=np.complex128)
476
+ has_periodic = True
477
+ tri_ids_1 = mesh.get_triangles(bc.face1.tags)
478
+ tri_ids_2 = mesh.get_triangles(bc.face2.tags)
479
+ dv = np.array(bc.dv)
480
+ trimapdict = defaultdict(lambda: [None, None])
481
+ edgemapdict = defaultdict(lambda: [None, None])
482
+ mult = int(10**(-np.round(np.ceil(min(mesh.edge_lengths.flatten())))+2))
483
+ edge_ids_1 = set()
484
+ edge_ids_2 = set()
485
+
486
+ phi = bc.phi(k0)
487
+ for i1, i2 in zip(tri_ids_1, tri_ids_2):
488
+ trimapdict[gen_key(mesh.tri_centers[:,i1], mult)][0] = i1
489
+ trimapdict[gen_key(mesh.tri_centers[:,i2]-dv, mult)][1] = i2
490
+
491
+ ie1, ie2, ie3 = mesh.tri_to_edge[:,i1]
492
+ edge_ids_1.update({ie1, ie2, ie3})
493
+ ie1, ie2, ie3 = mesh.tri_to_edge[:,i2]
494
+ edge_ids_2.update({ie1, ie2, ie3})
495
+
496
+ for i1, i2 in zip(list(edge_ids_1), list(edge_ids_2)):
497
+ edgemapdict[gen_key(mesh.edge_centers[:,i1], mult)][0] = i1
498
+ edgemapdict[gen_key(mesh.edge_centers[:,i2]-dv, mult)][1] = i2
499
+
500
+
501
+ for t1, t2 in trimapdict.values():
502
+ f11, f21 = field.tri_to_field[[3,7],t1]
503
+ f12, f22 = field.tri_to_field[[3,7],t2]
504
+ Pmat[f12,f12] = 0
505
+ Pmat[f22,f22] = 0
506
+ if Pmat[f12,f11] == 0:
507
+ Pmat[f12,f11] += phi
508
+ else:
509
+ Pmat[f12,f11] *= phi
510
+
511
+ if Pmat[f22,f21] == 0:
512
+ Pmat[f22,f21] += phi
513
+ else:
514
+ Pmat[f22,f21] *= phi
515
+ remove.add(f12)
516
+ remove.add(f22)
517
+
518
+ for e1, e2 in edgemapdict.values():
519
+ f11, f21 = field.edge_to_field[:,e1]
520
+ f12, f22 = field.edge_to_field[:,e2]
521
+ Pmat[f12,f12] = 0
522
+ Pmat[f22,f22] = 0
523
+ if Pmat[f12,f11] == 0:
524
+ Pmat[f12,f11] += phi
525
+ else:
526
+ Pmat[f12,f11] *= phi
527
+
528
+ if Pmat[f22,f21] == 0:
529
+ Pmat[f22,f21] += phi
530
+ else:
531
+ Pmat[f22,f21] *= phi
532
+ remove.add(f12)
533
+ remove.add(f22)
534
+
535
+ Pmats.append(Pmat)
536
+
537
+ if Pmats:
538
+ Pmat = Pmats[0]
539
+ for P2 in Pmats[1:]:
540
+ Pmat = Pmat @ P2
541
+ Pmat = Pmat.tocsr()
542
+ remove = np.array(sorted(list(remove)))
543
+ all_indices = np.arange(NF)
544
+ keep_indices = np.setdiff1d(all_indices, remove)
545
+ Pmat = Pmat[:,keep_indices]
546
+ else:
547
+ Pmat = None
548
+
549
+ pec_ids = set(pec_ids)
550
+ solve_ids = np.array([i for i in range(E.shape[0]) if i not in pec_ids])
551
+
552
+ if has_periodic:
553
+ mask = np.zeros((NF,))
554
+ mask[solve_ids] = 1
555
+ mask = mask[keep_indices]
556
+ solve_ids = np.argwhere(mask==1).flatten()
557
+
558
+ logger.debug(f'Number of tets: {mesh.n_tets}')
559
+ logger.debug(f'Number of DoF: {E.shape[0]}')
560
+ simjob = SimJob(E, None, frequency, False, B=B)
561
+
562
+ simjob.solve_ids = solve_ids
563
+
564
+ if has_periodic:
565
+ simjob.P = Pmat
566
+ simjob.Pd = Pmat.getH()
567
+ simjob.has_periodic = has_periodic
568
+
569
+ return simjob