emerge 0.6.9__tar.gz → 0.6.11__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 (110) hide show
  1. {emerge-0.6.9 → emerge-0.6.11}/.bumpversion.toml +1 -1
  2. {emerge-0.6.9 → emerge-0.6.11}/PKG-INFO +1 -1
  3. {emerge-0.6.9 → emerge-0.6.11}/emerge/__init__.py +12 -3
  4. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/bc.py +1 -1
  5. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/cs.py +36 -0
  6. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/polybased.py +58 -26
  7. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/shapes.py +6 -0
  8. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geometry.py +17 -8
  9. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/logsettings.py +13 -2
  10. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/material.py +10 -1
  11. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mesh3d.py +15 -4
  12. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/microwave_3d.py +14 -10
  13. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/microwave_bc.py +18 -20
  14. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/matplotlib/mpldisplay.py +1 -1
  15. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/pyvista/display.py +15 -14
  16. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/simple_plots.py +15 -3
  17. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/simmodel.py +6 -5
  18. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/solve_interfaces/cudss_interface.py +10 -4
  19. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/solver.py +14 -4
  20. {emerge-0.6.9 → emerge-0.6.11}/examples/demo10_sgh.py +1 -1
  21. {emerge-0.6.9 → emerge-0.6.11}/examples/demo11_lumped_element_filter.py +1 -1
  22. {emerge-0.6.9 → emerge-0.6.11}/examples/demo12_mode_alignment.py +1 -1
  23. {emerge-0.6.9 → emerge-0.6.11}/examples/demo13_helix_antenna.py +2 -2
  24. {emerge-0.6.9 → emerge-0.6.11}/examples/demo14_boundary_selection.py +1 -1
  25. {emerge-0.6.9 → emerge-0.6.11}/examples/demo1_stepped_imp_filter.py +1 -1
  26. {emerge-0.6.9 → emerge-0.6.11}/examples/demo2_combline_filter.py +1 -1
  27. {emerge-0.6.9 → emerge-0.6.11}/examples/demo3_coupled_line_filter.py +1 -1
  28. {emerge-0.6.9 → emerge-0.6.11}/examples/demo4_patch_antenna.py +1 -1
  29. emerge-0.6.11/examples/demo5_revolve.py +15 -0
  30. {emerge-0.6.9 → emerge-0.6.11}/examples/demo6_striplines_with_vias.py +1 -1
  31. {emerge-0.6.9 → emerge-0.6.11}/examples/demo7_periodic_cells.py +1 -1
  32. {emerge-0.6.9 → emerge-0.6.11}/examples/demo8_waveguide_bpf_synthesis.py +1 -1
  33. {emerge-0.6.9 → emerge-0.6.11}/examples/demo9_dielectric_resonator.py +2 -2
  34. {emerge-0.6.9 → emerge-0.6.11}/pyproject.toml +1 -1
  35. {emerge-0.6.9 → emerge-0.6.11}/uv.lock +1 -1
  36. emerge-0.6.9/examples/demo5_revolve.py +0 -25
  37. {emerge-0.6.9 → emerge-0.6.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  38. {emerge-0.6.9 → emerge-0.6.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  39. {emerge-0.6.9 → emerge-0.6.11}/.gitignore +0 -0
  40. {emerge-0.6.9 → emerge-0.6.11}/.opt +0 -0
  41. {emerge-0.6.9 → emerge-0.6.11}/.python-version +0 -0
  42. {emerge-0.6.9 → emerge-0.6.11}/LICENSE +0 -0
  43. {emerge-0.6.9 → emerge-0.6.11}/README.md +0 -0
  44. {emerge-0.6.9 → emerge-0.6.11}/THIRD_PARTY_LICENSES.md +0 -0
  45. {emerge-0.6.9 → emerge-0.6.11}/UMFPACK_Install_windows.md +0 -0
  46. {emerge-0.6.9 → emerge-0.6.11}/UMFPACK_installer_windows.py +0 -0
  47. {emerge-0.6.9 → emerge-0.6.11}/emerge/__main__.py +0 -0
  48. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/__init__.py +0 -0
  49. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/_cache_check.py +0 -0
  50. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/const.py +0 -0
  51. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/coord.py +0 -0
  52. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/dataset.py +0 -0
  53. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/__init__.py +0 -0
  54. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/femdata.py +0 -0
  55. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/index_interp.py +0 -0
  56. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/ned2_interp.py +0 -0
  57. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/nedelec2.py +0 -0
  58. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/elements/nedleg2.py +0 -0
  59. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/__init__.py +0 -0
  60. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/horn.py +0 -0
  61. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/modeler.py +0 -0
  62. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/operations.py +0 -0
  63. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/pcb.py +0 -0
  64. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  65. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  66. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/pmlbox.py +0 -0
  67. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo/step.py +0 -0
  68. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/geo2d.py +0 -0
  69. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/howto.py +0 -0
  70. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mesher.py +0 -0
  71. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mth/common_functions.py +0 -0
  72. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mth/integrals.py +0 -0
  73. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mth/optimized.py +0 -0
  74. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/mth/pairing.py +0 -0
  75. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/periodic.py +0 -0
  76. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/__init__.py +0 -0
  77. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  78. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  79. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
  80. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  81. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  82. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  83. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  84. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  85. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/microwave_data.py +0 -0
  86. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  87. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  88. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/sc.py +0 -0
  89. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  90. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  91. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  92. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/__init__.py +0 -0
  93. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/display.py +0 -0
  94. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  95. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  96. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/plot.py +0 -0
  97. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/projects/__init__.py +0 -0
  98. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/projects/_gen_base.txt +0 -0
  99. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/projects/_load_base.txt +0 -0
  100. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/projects/generate_project.py +0 -0
  101. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/selection.py +0 -0
  102. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/simulation_data.py +0 -0
  103. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  104. {emerge-0.6.9 → emerge-0.6.11}/emerge/_emerge/system.py +0 -0
  105. {emerge-0.6.9 → emerge-0.6.11}/emerge/cli.py +0 -0
  106. {emerge-0.6.9 → emerge-0.6.11}/emerge/ext.py +0 -0
  107. {emerge-0.6.9 → emerge-0.6.11}/emerge/lib.py +0 -0
  108. {emerge-0.6.9 → emerge-0.6.11}/emerge/plot.py +0 -0
  109. {emerge-0.6.9 → emerge-0.6.11}/emerge/pyvista.py +0 -0
  110. {emerge-0.6.9 → emerge-0.6.11}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.6.9"
2
+ current_version = "0.6.11"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.6.9
3
+ Version: 0.6.11
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
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "0.6.9"
21
+ __version__ = "0.6.11"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -43,12 +43,13 @@ from loguru import logger
43
43
 
44
44
  LOG_CONTROLLER.set_default()
45
45
  logger.debug('Importing modules')
46
+ LOG_CONTROLLER._set_log_buffer()
46
47
 
47
48
  from ._emerge.simmodel import Simulation
48
49
  from ._emerge.material import Material, FreqCoordDependent, FreqDependent, CoordDependent
49
50
  from ._emerge import bc
50
51
  from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
51
- from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
52
+ from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE, cs
52
53
  from ._emerge.coord import Line
53
54
  from ._emerge import geo
54
55
  from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
@@ -62,4 +63,12 @@ from ._emerge.howto import _HowtoClass
62
63
 
63
64
  howto = _HowtoClass()
64
65
 
65
- logger.debug('Importing complete!')
66
+ logger.debug('Importing complete!')
67
+
68
+
69
+ ############################################################
70
+ # CONSTANTS #
71
+ ############################################################
72
+
73
+ CENTER = geo.Alignment.CENTER
74
+ CORNER = geo.Alignment.CORNER
@@ -53,7 +53,7 @@ class BoundaryCondition:
53
53
 
54
54
 
55
55
  if isinstance(assignment, GeoObject):
56
- assignment = assignment.select
56
+ assignment = assignment.selection
57
57
 
58
58
  self.selection: Selection = assignment
59
59
  self.tags: list[int] = self.selection.tags
@@ -530,3 +530,39 @@ CS = CoordinateSystem
530
530
  # The global coordinate system
531
531
  GCS = CoordinateSystem(XAX, YAX, ZAX, np.zeros(3), _is_global=True)
532
532
 
533
+ def cs(axes: str, origin: tuple[float, float, float] = (0.,0.,0.,)) -> CoordinateSystem:
534
+ """Generate a coordinate system based on a simple string
535
+ The string must contain the letters x, X, y, Y, z and/or Z.
536
+ Small letters refer to positive axes and capitals to negative axes.
537
+
538
+ The string must be 3 characters long.
539
+
540
+ The first position indices which global axis gets assigned to the new local X-axis
541
+ The second position indicates the Y-axis
542
+ The third position indicates the Z-axis
543
+
544
+ Thus, rotating the global XYZ coordinate system 90 degrees around the Z axis would yield: yXz
545
+
546
+ Args:
547
+ axes (str): The axis description
548
+ origin (tuple[float, float, float], optional): The origin of the coordinate system. Defaults to (0.,0.,0.,).
549
+
550
+ Returns:
551
+ CoordinateSystem: The resultant coordinate system
552
+ """
553
+ if len(axes) != 3:
554
+ raise ValueError('Axis object must be of length 3')
555
+
556
+ axlib = {
557
+ 'x': Axis(np.array([1.0,0.,0.])),
558
+ 'X': Axis(np.array([-1.0,0.,0.])),
559
+ 'y': Axis(np.array([0.,1.0,0.])),
560
+ 'Y': Axis(np.array([0.,-1.0,0.])),
561
+ 'z': Axis(np.array([0.,0.,1.0])),
562
+ 'Z': Axis(np.array([0.,0.,-1.0])),
563
+ }
564
+ ax_obj = [axlib[ax] for ax in axes]
565
+
566
+ return (ax_obj[0]*ax_obj[1]*ax_obj[2]).displace(*origin)
567
+
568
+
@@ -206,6 +206,7 @@ def orthonormalize(axis: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray
206
206
  Zaxis = np.abs(Zaxis/np.linalg.norm(Zaxis))
207
207
  return Xaxis, Yaxis, Zaxis
208
208
 
209
+
209
210
  class GeoPrism(GeoVolume):
210
211
  """The GepPrism class generalizes the GeoVolume for extruded convex polygons.
211
212
  Besides having a volumetric definitions, the class offers a .front_face
@@ -216,35 +217,65 @@ class GeoPrism(GeoVolume):
216
217
  """
217
218
  def __init__(self,
218
219
  volume_tag: int,
219
- front_tag: int,
220
- side_tags: list[int],):
220
+ front_tag: int | None = None,
221
+ side_tags: list[int] | None = None,
222
+ _axis: Axis | None = None):
221
223
  super().__init__(volume_tag)
222
- self.front_tag: int = front_tag
223
- self.back_tag: int = None
224
+
225
+
226
+
227
+ if front_tag is not None and side_tags is not None:
228
+ self.front_tag: int = front_tag
229
+ self.back_tag: int = None
224
230
 
225
- gmsh.model.occ.synchronize()
226
- o1 = gmsh.model.occ.get_center_of_mass(2, self.front_tag)
227
- n1 = gmsh.model.get_normal(self.front_tag, (0,0))
228
- self._add_face_pointer('back', o1, n1)
231
+ gmsh.model.occ.synchronize()
232
+ self._add_face_pointer('back', tag=self.front_tag)
229
233
 
230
- tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
231
-
232
- for dim, tag in tags:
233
- if (dim,tag) in side_tags:
234
- continue
235
- o2 = gmsh.model.occ.get_center_of_mass(2, tag)
236
- n2 = gmsh.model.get_normal(tag, (0,0))
237
- self._add_face_pointer('front', o2, n2)
238
- self.back_tag = tag
239
- break
234
+ tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
235
+
236
+ for dim, tag in tags:
237
+ if (dim,tag) in side_tags:
238
+ continue
239
+ self._add_face_pointer('front',tag=tag)
240
+ self.back_tag = tag
241
+ break
240
242
 
241
- self.side_tags: list[int] = [dt[1] for dt in tags if dt[1]!=self.front_tag and dt[1]!=self.back_tag]
243
+ self.side_tags: list[int] = [dt[1] for dt in tags if dt[1]!=self.front_tag and dt[1]!=self.back_tag]
242
244
 
243
- for tag in self.side_tags:
244
- o2 = gmsh.model.occ.get_center_of_mass(2, tag)
245
- n2 = gmsh.model.get_normal(tag, (0,0))
246
- self._add_face_pointer(f'side{tag}', o2, n2)
247
- self.back_tag = tag
245
+ for tag in self.side_tags:
246
+
247
+ self._add_face_pointer(f'side{tag}', tag=tag)
248
+ self.back_tag = tag
249
+
250
+ elif _axis is not None:
251
+ _axis = _parse_axis(_axis)
252
+ gmsh.model.occ.synchronize()
253
+ tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
254
+ faces = []
255
+ for dim, tag in tags:
256
+ o1 = np.array(gmsh.model.occ.get_center_of_mass(2, tag))
257
+ n1 = np.array(gmsh.model.get_normal(tag, (0,0)))
258
+ if abs(np.sum(n1*_axis.np)) > 0.99:
259
+ dax = sum(o1 * _axis.np)
260
+ faces.append((o1, n1, dax, tag))
261
+
262
+ faces = sorted(faces, key=lambda x: x[2])
263
+ ftags = []
264
+ if len(faces) >= 2:
265
+ ftags.append(faces[0][3])
266
+ ftags.append(faces[-1][3])
267
+ self._add_face_pointer('front',faces[0][0], faces[0][1])
268
+ self._add_face_pointer('back', faces[-1][0], faces[-1][1])
269
+ elif len(faces)==1:
270
+ ftags.append(faces[0][3])
271
+ self._add_face_pointer('cap',faces[0][0], faces[0][1])
272
+
273
+ ictr = 1
274
+ for dim, tag in tags:
275
+ if tag in ftags:
276
+ continue
277
+ self._add_face_pointer(f'side{ictr}', tag=tag)
278
+ ictr += 1
248
279
 
249
280
  def outside(self, *exclude: Literal['front','back']) -> FaceSelection:
250
281
  """Select all outside faces except for the once specified by outside
@@ -462,9 +493,10 @@ class XYPolygon:
462
493
  ax, ay, az = axis
463
494
 
464
495
  volume = gmsh.model.occ.revolve(poly_fin.dimtags, x,y,z, ax, ay, az, angle*np.pi/180)
496
+
465
497
  tags = [t for d,t in volume if d==3]
466
- surftags = [t for d,t in volume if d==2]
467
- return GeoPrism(tags, surftags[0], surftags)
498
+ poly_fin.remove()
499
+ return GeoPrism(tags, _axis=axis)
468
500
 
469
501
  @staticmethod
470
502
  def circle(radius: float,
@@ -27,6 +27,12 @@ from typing import Literal
27
27
  from functools import reduce
28
28
 
29
29
  class Alignment(Enum):
30
+ """The alignment Enum describes if a box, cube or rectangle location
31
+ is specified for the center or the bottom - front - left corner (min X Y and Z)
32
+
33
+ Args:
34
+ Enum (_type_): _description_
35
+ """
30
36
  CENTER = 1
31
37
  CORNER = 2
32
38
 
@@ -250,7 +250,7 @@ class GeoObject:
250
250
  return self.material._metal
251
251
 
252
252
  @property
253
- def select(self) -> Selection:
253
+ def selection(self) -> Selection:
254
254
  '''Returns a corresponding Face/Domain or Edge Selection object'''
255
255
  if self.dim==1:
256
256
  return EdgeSelection(self.tags)
@@ -285,9 +285,18 @@ class GeoObject:
285
285
 
286
286
  def _add_face_pointer(self,
287
287
  name: str,
288
- origin: np.ndarray,
289
- normal: np.ndarray):
290
- self._face_pointers[name] = _FacePointer(origin, normal)
288
+ origin: np.ndarray | None = None,
289
+ normal: np.ndarray | None = None,
290
+ tag: int | None = None):
291
+ if tag is not None:
292
+ o = gmsh.model.occ.get_center_of_mass(2, tag)
293
+ n = gmsh.model.get_normal(tag, (0,0))
294
+ self._face_pointers[name] = _FacePointer(o, n)
295
+ return
296
+ if origin is not None and normal is not None:
297
+ self._face_pointers[name] = _FacePointer(origin, normal)
298
+ return
299
+ raise ValueError('Eitehr a tag or an origin + normal must be provided!')
291
300
 
292
301
  def make_copy(self) -> GeoObject:
293
302
  new_dimtags = gmsh.model.occ.copy(self.dimtags)
@@ -508,14 +517,14 @@ class GeoVolume(GeoObject):
508
517
  self.tags = [tag,]
509
518
 
510
519
  @property
511
- def select(self) -> DomainSelection:
520
+ def selection(self) -> DomainSelection:
512
521
  return DomainSelection(self.tags)
513
522
 
514
523
  class GeoPoint(GeoObject):
515
524
  dim = 0
516
525
 
517
526
  @property
518
- def select(self) -> PointSelection:
527
+ def selection(self) -> PointSelection:
519
528
  return PointSelection(self.tags)
520
529
 
521
530
  def __init__(self, tag: int | list[int]):
@@ -531,7 +540,7 @@ class GeoEdge(GeoObject):
531
540
  dim = 1
532
541
 
533
542
  @property
534
- def select(self) -> EdgeSelection:
543
+ def selection(self) -> EdgeSelection:
535
544
  return EdgeSelection(self.tags)
536
545
 
537
546
  def __init__(self, tag: int | list[int]):
@@ -549,7 +558,7 @@ class GeoSurface(GeoObject):
549
558
  dim = 2
550
559
 
551
560
  @property
552
- def select(self) -> FaceSelection:
561
+ def selection(self) -> FaceSelection:
553
562
  return FaceSelection(self.tags)
554
563
 
555
564
  def __init__(self, tag: int | list[int]):
@@ -18,11 +18,14 @@
18
18
  from loguru import logger
19
19
  import sys
20
20
  from typing import Literal
21
- from enum import Enum
22
21
  from pathlib import Path
23
22
  import os
24
- import gmsh
23
+ from collections import deque
25
24
 
25
+ _LOG_BUFFER = deque()
26
+
27
+ def _log_sink(message):
28
+ _LOG_BUFFER.append(message)
26
29
  ############################################################
27
30
  # FORMATS #
28
31
  ############################################################
@@ -82,6 +85,14 @@ class LogController:
82
85
  format=FORMAT_DICT.get(loglevel, INFO_FORMAT))
83
86
  self.std_handlers.append(handle_id)
84
87
 
88
+ def _set_log_buffer(self):
89
+ logger.add(_log_sink)
90
+
91
+ def _flush_log_buffer(self):
92
+ for msg in list(_LOG_BUFFER):
93
+ logger.opt(depth=6).log(msg.record["level"].name, msg.record["message"])
94
+ _LOG_BUFFER.clear()
95
+
85
96
  def set_std_loglevel(self, loglevel: str):
86
97
  handler = {"sink": sys.stdout,
87
98
  "level": loglevel,
@@ -310,6 +310,8 @@ class Material:
310
310
 
311
311
  self.color: str = color
312
312
  self.opacity: float = opacity
313
+ self._hash_key: int = -1
314
+
313
315
  if _neff is None:
314
316
  self._neff: Callable = lambda f: np.sqrt(self.ur._fmax(f)*self.er._fmax(f))
315
317
  else:
@@ -318,8 +320,14 @@ class Material:
318
320
  self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
319
321
  self._metal: bool = _metal
320
322
 
323
+ def __hash__(self):
324
+ return self._hash_key
325
+
321
326
  def __str__(self) -> str:
322
- return f'Material({self.name})'
327
+ return f'Material({self.name}, {self._hash_key})'
328
+
329
+ def __repr__(self):
330
+ return f'Material({self.name}, {self._hash_key})'
323
331
 
324
332
  def initialize(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, ids: np.ndarray):
325
333
  """Initializes the Material properties to be evaluated at xyz-coordinates for
@@ -343,6 +351,7 @@ class Material:
343
351
  self.ur.reset()
344
352
  self.tand.reset()
345
353
  self.cond.reset()
354
+ self._hash_key = -1
346
355
 
347
356
  @property
348
357
  def frequency_dependent(self) -> bool:
@@ -544,22 +544,33 @@ class Mesh3D(Mesh):
544
544
  #arry = np.zeros((3,3,self.n_tets,), dtype=np.complex128)
545
545
  for vol in volumes:
546
546
  vol.material.reset()
547
-
547
+
548
548
  materials = []
549
+ i = 0
550
+ for vol in volumes:
551
+ if vol.material not in materials:
552
+ materials.append(vol.material)
553
+ vol.material._hash_key = i
554
+ i += 1
549
555
 
550
556
  xs = self.centers[0,:]
551
557
  ys = self.centers[1,:]
552
558
  zs = self.centers[2,:]
553
559
 
560
+ matassign = -1*np.ones((self.n_tets,), dtype=np.int64)
561
+
554
562
  for volume in sorted(volumes, key=lambda x: x._priority):
555
563
 
556
564
  for dimtag in volume.dimtags:
557
565
  etype, etag_list, ntags = gmsh.model.mesh.get_elements(*dimtag)
558
566
  for etags in etag_list:
559
567
  tet_ids = np.array([self.tet_t2i[t] for t in etags])
560
- volume.material.initialize(xs[tet_ids], ys[tet_ids], zs[tet_ids], tet_ids)
561
- if volume.material not in materials:
562
- materials.append(volume.material)
568
+ matassign[tet_ids] = volume.material._hash_key
569
+
570
+ for mat in materials:
571
+ ids = np.argwhere(matassign==mat._hash_key).flatten()
572
+ mat.initialize(xs[ids], ys[ids], zs[ids], ids)
573
+
563
574
 
564
575
  return materials
565
576
 
@@ -53,7 +53,7 @@ def run_job_multi(job: SimJob) -> SimJob:
53
53
  Returns:
54
54
  SimJob: The solved SimJob
55
55
  """
56
- routine = DEFAULT_ROUTINE.duplicate()._configure_routine('MP')
56
+ routine = DEFAULT_ROUTINE._configure_routine('MP')
57
57
  for A, b, ids, reuse, aux in job.iter_Ab():
58
58
  solution, report = routine.solve(A, b, ids, reuse, id=job.id)
59
59
  report.add(**aux)
@@ -311,22 +311,25 @@ class Microwave3D:
311
311
 
312
312
  if points.size==0:
313
313
  raise SimulationError(f'The lumped port {port} has no nodes associated with it')
314
+
314
315
  xs = self.mesh.nodes[0,points]
315
316
  ys = self.mesh.nodes[1,points]
316
317
  zs = self.mesh.nodes[2,points]
317
318
 
318
319
  dotprod = xs*field_axis[0] + ys*field_axis[1] + zs*field_axis[2]
319
320
 
320
- start_id = points[np.argwhere(dotprod == np.min(dotprod))]
321
+ start_id = np.argwhere(dotprod == np.min(dotprod)).flatten()
322
+
323
+ xs = xs[start_id]
324
+ ys = ys[start_id]
325
+ zs = zs[start_id]
321
326
 
322
- start = _pick_central(self.mesh.nodes[:,start_id.flatten()])
323
- logger.info(f'Starting node = {_dimstring(start)}')
324
- end = start + port.Vdirection.np*port.height
325
-
326
-
327
- port.vintline = Line.from_points(start, end, 21)
328
327
 
329
- logger.info(f'Ending node = {_dimstring(end)}')
328
+ for x,y,z in zip(xs, ys, zs):
329
+ start = np.array([x,y,z])
330
+ end = start + port.Vdirection.np*port.height
331
+ port.vintline.append(Line.from_points(start, end, 21))
332
+ logger.trace(f'Port[{port.port_number}] integration line {start} -> {end}.')
330
333
 
331
334
  port.v_integration = True
332
335
 
@@ -1003,7 +1006,8 @@ class Microwave3D:
1003
1006
  if bc.Z0 is None:
1004
1007
  raise SimulationError('Trying to compute the impedance of a boundary condition with no characteristic impedance.')
1005
1008
 
1006
- V = bc.vintline.line_integral(fieldfunction)
1009
+ Voltages = [line.line_integral(fieldfunction) for line in bc.vintline]
1010
+ V = sum(Voltages)/len(Voltages)
1007
1011
 
1008
1012
  if bc.active:
1009
1013
  if bc.voltage is None:
@@ -306,11 +306,17 @@ class AbsorbingBoundary(RobinBC):
306
306
  Returns:
307
307
  complex: The γ-constant
308
308
  """
309
- return 1j*self.get_beta(k0)
309
+ if self.order == 2:
310
+
311
+ p0 = 1.06103
312
+ p2 = -0.84883
313
+ ky = k0*0.5
314
+ return 1j*k0*p0 - 1j*p2*ky**2/k0
315
+ else:
316
+ Factor = 1
317
+ return 1j*self.get_beta(k0)*Factor
310
318
 
311
- def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
312
- return np.zeros((3, len(x_local)), dtype=np.complex128)
313
-
319
+
314
320
  @dataclass
315
321
  class PortMode:
316
322
  modefield: np.ndarray
@@ -825,10 +831,10 @@ class LumpedPort(PortBC):
825
831
  self.Vdirection: Axis = direction # type: ignore
826
832
  self.type = 'TEM'
827
833
 
828
- logger.info('Constructing coordinate system from normal port')
829
- self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
830
-
831
- self.vintline: Line | None = None
834
+ # logger.info('Constructing coordinate system from normal port')
835
+ # self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
836
+ self.cs = GCS
837
+ self.vintline: list[Line] = []
832
838
  self.v_integration = True
833
839
 
834
840
  @property
@@ -881,14 +887,7 @@ class LumpedPort(PortBC):
881
887
  k0: float,
882
888
  which: Literal['E','H'] = 'E') -> np.ndarray:
883
889
  ''' Compute the port mode E-field in local coordinates (XY) + Z out of plane.'''
884
-
885
- px, py, pz = self.cs.in_local_basis(*self.Vdirection.np)
886
-
887
- Ex = px*np.ones_like(x_local)
888
- Ey = py*np.ones_like(x_local)
889
- Ez = pz*np.ones_like(x_local)
890
- Exyz = np.array([Ex, Ey, Ez])
891
- return Exyz
890
+ raise RuntimeError('This function should never be called in this context.')
892
891
 
893
892
  def port_mode_3d_global(self,
894
893
  x_global: np.ndarray,
@@ -911,10 +910,9 @@ class LumpedPort(PortBC):
911
910
  Returns:
912
911
  np.ndarray: The E-field in (3,N) indexing.
913
912
  """
914
- xl, yl, _ = self.cs.in_local_cs(x_global, y_global, z_global)
915
- Ex, Ey, Ez = self.port_mode_3d(xl, yl, k0)
916
- Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
917
- return np.array([Exg, Eyg, Ezg])
913
+ ON = np.ones_like(x_global)
914
+ Ex, Ey, Ez = self.Vdirection.np
915
+ return np.array([Ex*ON, Ey*ON, Ez*ON])
918
916
 
919
917
 
920
918
  class LumpedElement(RobinBC):
@@ -145,7 +145,7 @@ def _norm(x, y, z):
145
145
 
146
146
  def _select(obj: GeoObject | Selection) -> Selection:
147
147
  if isinstance(obj, GeoObject):
148
- return obj.select
148
+ return obj.selection
149
149
  return obj
150
150
 
151
151
  def _merge(lst: list[GeoObject | Selection]) -> Selection:
@@ -186,7 +186,7 @@ def _norm(x, y, z):
186
186
 
187
187
  def _select(obj: GeoObject | Selection) -> Selection:
188
188
  if isinstance(obj, GeoObject):
189
- return obj.select
189
+ return obj.selection
190
190
  return obj
191
191
 
192
192
  def _merge(lst: Iterable[GeoObject | Selection]) -> Selection:
@@ -523,18 +523,19 @@ class PVDisplay(BaseDisplay):
523
523
  d = _min_distance(xf, yf, zf)
524
524
 
525
525
  if port.vintline is not None:
526
- xs, ys, zs = port.vintline.cpoint
527
- p_line = pv.Line(
528
- pointa=(xs[0], ys[0], zs[0]),
529
- pointb=(xs[-1], ys[-1], zs[-1]),
530
- )
531
- self._plot.add_mesh(
532
- p_line,
533
- color='red',
534
- pickable=False,
535
- line_width=3.0,
536
- )
537
-
526
+ for line in port.vintline:
527
+ xs, ys, zs = line.cpoint
528
+ p_line = pv.Line(
529
+ pointa=(xs[0], ys[0], zs[0]),
530
+ pointb=(xs[-1], ys[-1], zs[-1]),
531
+ )
532
+ self._plot.add_mesh(
533
+ p_line,
534
+ color='red',
535
+ pickable=False,
536
+ line_width=3.0,
537
+ )
538
+
538
539
  if k0 is None:
539
540
  if isinstance(port, ModalPort):
540
541
  k0 = port.get_mode(0).k0
@@ -596,7 +597,7 @@ class PVDisplay(BaseDisplay):
596
597
 
597
598
 
598
599
  if scale=='log':
599
- T = lambda x: np.log10(np.abs(x))
600
+ T = lambda x: np.log10(np.abs(x+1e-12))
600
601
  elif scale=='symlog':
601
602
  T = lambda x: np.sign(x) * np.log10(1 + np.abs(x*np.log(10)))
602
603
  else:
@@ -422,9 +422,14 @@ def plot_sp(f: np.ndarray | list[np.ndarray], S: list[np.ndarray] | np.ndarray,
422
422
  show_plot: bool = True,
423
423
  figdata: tuple | None = None) -> tuple[plt.Figure, plt.Axes, plt.Axes]:
424
424
  """Plot S-parameters in dB and phase
425
+
426
+ One may provide:
427
+ - A single frequency with a single S-parameter
428
+ - A single frequency with a list of S-parameters
429
+ - A list of frequencies with a list of S-parameters
425
430
 
426
431
  Args:
427
- f (np.ndarray): Frequency vector
432
+ f (np.ndarray | list[np.ndarray]): Frequency vector or list of frequencies
428
433
  S (list[np.ndarray] | np.ndarray): S-parameters to plot (list or single array)
429
434
  dblim (list, optional): Decibel y-axis limit. Defaults to [-80, 5].
430
435
  xunit (str, optional): Frequency unit. Defaults to "GHz".
@@ -535,7 +540,7 @@ def plot_sp(f: np.ndarray | list[np.ndarray], S: list[np.ndarray] | np.ndarray,
535
540
 
536
541
 
537
542
  def plot_ff(
538
- theta: np.ndarray,
543
+ theta: np.ndarray | list[np.ndarray],
539
544
  E: Union[np.ndarray, Sequence[np.ndarray]],
540
545
  grid: bool = True,
541
546
  dB: bool = False,
@@ -554,7 +559,7 @@ def plot_ff(
554
559
 
555
560
  Parameters
556
561
  ----------
557
- theta : np.ndarray
562
+ theta : np.ndarray | list[np.ndarray]
558
563
  Angle array (radians).
559
564
  E : np.ndarray or sequence of np.ndarray
560
565
  Complex E-field samples; magnitude will be plotted.
@@ -575,6 +580,12 @@ def plot_ff(
575
580
  E_list = [E]
576
581
  else:
577
582
  E_list = list(E)
583
+
584
+ if not isinstance(theta, list):
585
+ thetas = [theta for _ in E_list]
586
+ else:
587
+ thetas = theta
588
+
578
589
  n_series = len(E_list)
579
590
 
580
591
  # Style broadcasting
@@ -591,6 +602,7 @@ def plot_ff(
591
602
 
592
603
  fig, ax = plt.subplots()
593
604
  for i, Ei in enumerate(E_list):
605
+ theta = thetas[i]
594
606
  mag = np.abs(Ei)
595
607
  if dB:
596
608
  mag = 20*np.log10(mag)