emerge 1.0.5__tar.gz → 1.0.7__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 (120) hide show
  1. {emerge-1.0.5 → emerge-1.0.7}/.bumpversion.toml +1 -1
  2. {emerge-1.0.5 → emerge-1.0.7}/PKG-INFO +3 -2
  3. {emerge-1.0.5 → emerge-1.0.7}/emerge/__init__.py +2 -2
  4. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/index_interp.py +45 -0
  5. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/horn.py +17 -1
  6. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/pcb.py +10 -6
  7. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/polybased.py +16 -1
  8. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/shapes.py +93 -1
  9. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geometry.py +22 -4
  10. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/material.py +32 -3
  11. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mesh3d.py +33 -3
  12. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mesher.py +24 -5
  13. emerge-1.0.7/emerge/_emerge/physics/microwave/adaptive_mesh.py +576 -0
  14. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/assembler.py +7 -6
  15. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +78 -77
  16. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/microwave_3d.py +137 -21
  17. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/microwave_bc.py +27 -5
  18. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/microwave_data.py +32 -6
  19. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/pyvista/display.py +93 -2
  20. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/simmodel.py +87 -16
  21. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/solver.py +93 -73
  22. {emerge-1.0.5 → emerge-1.0.7}/examples/demo10_sgh.py +2 -2
  23. {emerge-1.0.5 → emerge-1.0.7}/examples/demo11_lumped_element_filter.py +2 -2
  24. {emerge-1.0.5 → emerge-1.0.7}/examples/demo12_mode_alignment.py +1 -1
  25. {emerge-1.0.5 → emerge-1.0.7}/examples/demo13_helix_antenna.py +1 -1
  26. {emerge-1.0.5 → emerge-1.0.7}/examples/demo14_boundary_selection.py +4 -2
  27. {emerge-1.0.5 → emerge-1.0.7}/examples/demo1_stepped_imp_filter.py +3 -3
  28. {emerge-1.0.5 → emerge-1.0.7}/examples/demo2_combline_filter.py +5 -2
  29. {emerge-1.0.5 → emerge-1.0.7}/examples/demo3_coupled_line_filter.py +2 -2
  30. {emerge-1.0.5 → emerge-1.0.7}/examples/demo4_patch_antenna.py +1 -2
  31. {emerge-1.0.5 → emerge-1.0.7}/examples/demo5_revolve.py +6 -6
  32. {emerge-1.0.5 → emerge-1.0.7}/examples/demo6_striplines_with_vias.py +5 -5
  33. {emerge-1.0.5 → emerge-1.0.7}/examples/demo7_periodic_cells.py +3 -3
  34. {emerge-1.0.5 → emerge-1.0.7}/examples/demo8_waveguide_bpf_synthesis.py +6 -6
  35. {emerge-1.0.5 → emerge-1.0.7}/examples/demo9_dielectric_resonator.py +1 -1
  36. {emerge-1.0.5 → emerge-1.0.7}/pyproject.toml +3 -2
  37. {emerge-1.0.5 → emerge-1.0.7}/uv.lock +115 -55
  38. emerge-1.0.5/UMFPACK_installer_windows.py +0 -213
  39. {emerge-1.0.5 → emerge-1.0.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {emerge-1.0.5 → emerge-1.0.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {emerge-1.0.5 → emerge-1.0.7}/.gitignore +0 -0
  42. {emerge-1.0.5 → emerge-1.0.7}/.nova/Configuration.json +0 -0
  43. {emerge-1.0.5 → emerge-1.0.7}/.python-version +0 -0
  44. {emerge-1.0.5 → emerge-1.0.7}/LICENSE +0 -0
  45. {emerge-1.0.5 → emerge-1.0.7}/README.md +0 -0
  46. {emerge-1.0.5 → emerge-1.0.7}/THIRD_PARTY_LICENSES.md +0 -0
  47. {emerge-1.0.5 → emerge-1.0.7}/UMFPACK_Install_windows.md +0 -0
  48. {emerge-1.0.5 → emerge-1.0.7}/emerge/__main__.py +0 -0
  49. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/__init__.py +0 -0
  50. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/bc.py +0 -0
  51. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/cacherun.py +0 -0
  52. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/const.py +0 -0
  53. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/coord.py +0 -0
  54. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/cs.py +0 -0
  55. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/dataset.py +0 -0
  56. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/__init__.py +0 -0
  57. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/femdata.py +0 -0
  58. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/ned2_interp.py +0 -0
  59. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/nedelec2.py +0 -0
  60. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/elements/nedleg2.py +0 -0
  61. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/emerge_update.py +0 -0
  62. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/__init__.py +0 -0
  63. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/modeler.py +0 -0
  64. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/operations.py +0 -0
  65. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  66. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/pcb_tools/dxf.py +0 -0
  67. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  68. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/pmlbox.py +0 -0
  69. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo/step.py +0 -0
  70. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/geo2d.py +0 -0
  71. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/howto.py +0 -0
  72. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/logsettings.py +0 -0
  73. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mth/_cache_check.py +0 -0
  74. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mth/common_functions.py +0 -0
  75. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mth/integrals.py +0 -0
  76. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mth/optimized.py +0 -0
  77. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/mth/pairing.py +0 -0
  78. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/periodic.py +0 -0
  79. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/__init__.py +0 -0
  80. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  81. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  82. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  83. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  84. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  85. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +0 -0
  86. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  87. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  88. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  89. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/sc.py +0 -0
  90. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  91. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  92. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  93. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/__init__.py +0 -0
  94. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/display.py +0 -0
  95. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  96. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  97. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/pyvista/cmap_maker.py +0 -0
  98. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  99. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot/simple_plots.py +0 -0
  100. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/plot.py +0 -0
  101. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/projects/__init__.py +0 -0
  102. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/projects/_gen_base.txt +0 -0
  103. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/projects/_load_base.txt +0 -0
  104. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/projects/generate_project.py +0 -0
  105. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/selection.py +0 -0
  106. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/settings.py +0 -0
  107. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/simulation_data.py +0 -0
  108. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
  109. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  110. {emerge-1.0.5 → emerge-1.0.7}/emerge/_emerge/system.py +0 -0
  111. {emerge-1.0.5 → emerge-1.0.7}/emerge/beta/dxf.py +0 -0
  112. {emerge-1.0.5 → emerge-1.0.7}/emerge/cli.py +0 -0
  113. {emerge-1.0.5 → emerge-1.0.7}/emerge/ext.py +0 -0
  114. {emerge-1.0.5 → emerge-1.0.7}/emerge/lib.py +0 -0
  115. {emerge-1.0.5 → emerge-1.0.7}/emerge/materials/__init__.py +0 -0
  116. {emerge-1.0.5 → emerge-1.0.7}/emerge/materials/isola.py +0 -0
  117. {emerge-1.0.5 → emerge-1.0.7}/emerge/materials/rogers.py +0 -0
  118. {emerge-1.0.5 → emerge-1.0.7}/emerge/plot.py +0 -0
  119. {emerge-1.0.5 → emerge-1.0.7}/emerge/pyvista.py +0 -0
  120. {emerge-1.0.5 → emerge-1.0.7}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.0.5"
2
+ current_version = "1.0.7"
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: 1.0.5
3
+ Version: 1.0.7
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
@@ -11,7 +11,8 @@ Requires-Dist: joblib>=1.5.1
11
11
  Requires-Dist: loguru>=0.7.3
12
12
  Requires-Dist: matplotlib>=3.8.0
13
13
  Requires-Dist: mkl!=2024.0; platform_machine == 'x86_64' or platform_machine == 'AMD64'
14
- Requires-Dist: numba>=0.57.0
14
+ Requires-Dist: numba<=0.60.0,>=0.57.0; platform_system == 'Linux'
15
+ Requires-Dist: numba>=0.57.0; platform_system != 'Linux'
15
16
  Requires-Dist: numpy<2.3,>=1.24
16
17
  Requires-Dist: pyvista>=0.45.2
17
18
  Requires-Dist: scipy>=1.14.0
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "1.0.5"
21
+ __version__ = "1.0.7"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -33,7 +33,7 @@ os.environ["OPENBLAS_NUM_THREADS"] = NTHREADS
33
33
  os.environ["VECLIB_NUM_THREADS"] = NTHREADS
34
34
  os.environ["VECLIB_MAXIMUM_THREADS"] = NTHREADS
35
35
  os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
36
- os.environ["NUMBA_NUM_THREADS"] = "4"
36
+ os.environ["NUMBA_NUM_THREADS"] = os.getenv("NUMBA_NUM_THREADS", default="4")
37
37
  os.environ.setdefault("NUMBA_THREADING_LAYER", "workqueue")
38
38
 
39
39
  ############################################################
@@ -61,3 +61,48 @@ def index_interp(coords: np.ndarray,
61
61
  prop[inside] = itet
62
62
 
63
63
  return prop
64
+
65
+ @njit(f8[:](f8[:,:], i8[:,:], f8[:,:], i8[:], f8[:]), cache=True, nogil=True)
66
+ def constant_interp(coords: np.ndarray,
67
+ tets: np.ndarray,
68
+ nodes: np.ndarray,
69
+ tetids: np.ndarray,
70
+ value: np.ndarray):
71
+ ''' Nedelec 2 tetrahedral interpolation of the analytic curl'''
72
+ # Solution has shape (nEdges, nsols)
73
+ nNodes = coords.shape[1]
74
+
75
+ prop = np.full((nNodes, ), 0, dtype=np.float64)
76
+
77
+ for i_iter in range(tetids.shape[0]):
78
+ itet = tetids[i_iter]
79
+
80
+ iv1, iv2, iv3, iv4 = tets[:, itet]
81
+
82
+ v1 = nodes[:,iv1]
83
+ v2 = nodes[:,iv2]
84
+ v3 = nodes[:,iv3]
85
+ v4 = nodes[:,iv4]
86
+
87
+ bv1 = v2 - v1
88
+ bv2 = v3 - v1
89
+ bv3 = v4 - v1
90
+
91
+ blocal = np.zeros((3,3))
92
+ blocal[:,0] = bv1
93
+ blocal[:,1] = bv2
94
+ blocal[:,2] = bv3
95
+ basis = np.linalg.pinv(blocal)
96
+
97
+ coords_offset = coords - v1[:,np.newaxis]
98
+ coords_local = (basis @ (coords_offset))
99
+
100
+
101
+ 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)
102
+
103
+ if inside.sum() == 0:
104
+ continue
105
+
106
+ prop[inside] = value[itet]
107
+
108
+ return prop
@@ -17,6 +17,7 @@
17
17
 
18
18
  from ..geometry import GeoVolume
19
19
  from ..cs import CoordinateSystem
20
+ from ..selection import FaceSelection
20
21
 
21
22
  import gmsh # type: ignore
22
23
 
@@ -103,4 +104,19 @@ class Horn(GeoVolume):
103
104
 
104
105
  pc = p0 + dax * height/2
105
106
  self._add_face_pointer('front', pc - height/2*dax, -dax)
106
- self._add_face_pointer('back', pc + height/2*dax, dax)
107
+ self._add_face_pointer('back', pc + height/2*dax, dax)
108
+
109
+ @property
110
+ def front(self) -> FaceSelection:
111
+ """The first local -Z face of the Horn."""
112
+ return self.face('front')
113
+
114
+ @property
115
+ def back(self) -> FaceSelection:
116
+ """The back local +Z face of the Horn."""
117
+ return self.face('back')
118
+
119
+ @property
120
+ def sides(self) -> FaceSelection:
121
+ """The outside faces excluding the top and bottom."""
122
+ return self.boundary(exclude=('front','back'))
@@ -1331,7 +1331,6 @@ class PCB:
1331
1331
  poly._aux_data['width'] = stripline.width*self.unit
1332
1332
  poly._aux_data['height'] = height*self.unit
1333
1333
  poly._aux_data['vdir'] = self.cs.zax
1334
- poly._aux_data['idir'] = Axis(self.cs.xax.np*stripline.dirright[0] + self.cs.yax.np*stripline.dirright[1])
1335
1334
 
1336
1335
  return poly
1337
1336
 
@@ -1346,6 +1345,7 @@ class PCB:
1346
1345
  point: StripLine,
1347
1346
  height: float,
1348
1347
  width_multiplier: float = 5.0,
1348
+ width: float | None = None,
1349
1349
  name: str | None = 'ModalPort'
1350
1350
  ) -> GeoSurface:
1351
1351
  """Generate a wave-port as a GeoSurface.
@@ -1365,11 +1365,16 @@ class PCB:
1365
1365
 
1366
1366
  height = (self.thickness + height)
1367
1367
 
1368
+ if width is not None:
1369
+ W = width
1370
+ else:
1371
+ W = point.width*width_multiplier
1372
+
1368
1373
  ds = point.dirright
1369
- x0 = point.x - ds[0]*point.width*width_multiplier/2
1370
- y0 = point.y - ds[1]*point.width*width_multiplier/2
1374
+ x0 = point.x - ds[0]*W/2
1375
+ y0 = point.y - ds[1]*W/2
1371
1376
  z0 = - self.thickness
1372
- ax1 = np.array([ds[0], ds[1], 0])*self.unit*point.width*width_multiplier
1377
+ ax1 = np.array([ds[0], ds[1], 0])*self.unit*W
1373
1378
  ax2 = np.array([0,0,1])*height*self.unit
1374
1379
 
1375
1380
  plate = Plate(np.array([x0,y0,z0])*self.unit, ax1, ax2, name=name)
@@ -1443,7 +1448,7 @@ class PCB:
1443
1448
  tag_wire = gmsh.model.occ.addWire(ltags)
1444
1449
  planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
1445
1450
  poly = GeoPolygon([planetag,], name=name)
1446
- poly._store('thickness', self.thickness)
1451
+ poly._store('thickness', self.trace_thickness)
1447
1452
  return poly
1448
1453
 
1449
1454
  @overload
@@ -1508,6 +1513,5 @@ class PCB:
1508
1513
 
1509
1514
  if merge:
1510
1515
  polys = unite(*polys)
1511
-
1512
1516
  return polys
1513
1517
 
@@ -231,7 +231,22 @@ class GeoPrism(GeoVolume):
231
231
  tagslist = [self._face_tags(name) for name in self._face_pointers.keys() if name not in exclude]
232
232
 
233
233
  tags = list(reduce(lambda a,b: a+b, tagslist))
234
- return FaceSelection(tags)
234
+ return FaceSelection(tags)
235
+
236
+ @property
237
+ def front(self) -> FaceSelection:
238
+ """The first local -Z face of the prism."""
239
+ return self.face('front')
240
+
241
+ @property
242
+ def back(self) -> FaceSelection:
243
+ """The back local +Z face of the prism."""
244
+ return self.face('back')
245
+
246
+ @property
247
+ def sides(self) -> FaceSelection:
248
+ """The outside faces excluding the top and bottom."""
249
+ return self.boundary(exclude=('front','back'))
235
250
 
236
251
  class XYPolygon:
237
252
  """This class generalizes a polygon in an un-embedded XY space that can be embedded in 3D space.
@@ -97,6 +97,35 @@ class Box(GeoVolume):
97
97
  self._add_face_pointer('top', pc + height/2*hax, hax)
98
98
  self._add_face_pointer('bottom', pc - height/2*hax, -hax)
99
99
 
100
+ @property
101
+ def left(self) -> FaceSelection:
102
+ """The left (-X) face."""
103
+ return self.face('left')
104
+
105
+ @property
106
+ def right(self) -> FaceSelection:
107
+ """The right (+X) face."""
108
+ return self.face('right')
109
+
110
+ @property
111
+ def top(self) -> FaceSelection:
112
+ """The top (+Z) face."""
113
+ return self.face('top')
114
+
115
+ @property
116
+ def bottom(self) -> FaceSelection:
117
+ """The bottom (-Z) face."""
118
+ return self.face('bottom')
119
+
120
+ @property
121
+ def front(self) -> FaceSelection:
122
+ """The front (-Y) face."""
123
+ return self.face('front')
124
+
125
+ @property
126
+ def back(self) -> FaceSelection:
127
+ """The back (+Y) face."""
128
+ return self.face('back')
100
129
 
101
130
  def outside(self, *exclude: Literal['bottom','top','right','left','front','back']) -> FaceSelection:
102
131
  """Select all outside faces except for the once specified by outside
@@ -131,6 +160,11 @@ class Sphere(GeoVolume):
131
160
  x,y,z = position
132
161
  self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
133
162
 
163
+ @property
164
+ def outside(self) -> FaceSelection:
165
+ """The outside boundary of the sphere.
166
+ """
167
+ return self.boundary()
134
168
 
135
169
  class XYPlate(GeoSurface):
136
170
  """Generates and XY-plane oriented plate
@@ -301,6 +335,21 @@ class Cylinder(GeoVolume):
301
335
 
302
336
  xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
303
337
  return xo, yo, zo
338
+
339
+ @property
340
+ def front(self) -> FaceSelection:
341
+ """The first local -Z face of the cylinder."""
342
+ return self.face('front')
343
+
344
+ @property
345
+ def back(self) -> FaceSelection:
346
+ """The back local +Z face of the cylinder."""
347
+ return self.face('back')
348
+
349
+ @property
350
+ def shell(self) -> FaceSelection:
351
+ """The outside faces excluding the top and bottom."""
352
+ return self.boundary(exclude=('front','back'))
304
353
 
305
354
 
306
355
  class CoaxCylinder(GeoVolume):
@@ -382,6 +431,16 @@ class CoaxCylinder(GeoVolume):
382
431
  xo, yo, zo = self.cs.in_global_cs(x.flatten(), y.flatten(), z.flatten())
383
432
  return xo, yo, zo
384
433
 
434
+ @property
435
+ def front(self) -> FaceSelection:
436
+ """The first local -Z face of the cylinder."""
437
+ return self.face('front')
438
+
439
+ @property
440
+ def back(self) -> FaceSelection:
441
+ """The back local +Z face of the cylinder."""
442
+ return self.face('back')
443
+
385
444
  class HalfSphere(GeoVolume):
386
445
  """A half sphere volume."""
387
446
  _default_name: str = 'HalfSphere'
@@ -411,7 +470,25 @@ class HalfSphere(GeoVolume):
411
470
  self._add_face_pointer('back',np.array(position), np.array(direction))
412
471
  self._add_face_pointer('bottom',np.array(position), np.array(direction))
413
472
  self._add_face_pointer('face',np.array(position), np.array(direction))
473
+ self._add_face_pointer('disc',np.array(position), np.array(direction))
414
474
 
475
+ @property
476
+ def outside(self) -> FaceSelection:
477
+ """The outside of the sphere excluding the flat disc face
478
+
479
+ Returns:
480
+ FaceSelection: _description_
481
+ """
482
+ return self.boundary(exclude=('disc',))
483
+
484
+ @property
485
+ def disc(self) -> FaceSelection:
486
+ """The flat disc face that cuts the sphere in half
487
+
488
+ Returns:
489
+ FaceSelection: _description_
490
+ """
491
+ return self.face('disc')
415
492
 
416
493
  class OldBox(GeoVolume):
417
494
  '''The sided box class creates a box just like the Box class but with selectable face tags.
@@ -564,4 +641,19 @@ class Cone(GeoVolume):
564
641
 
565
642
  self._add_face_pointer('front', p0, ds)
566
643
  if r2>0:
567
- self._add_face_pointer('back', p0+ds, ds)
644
+ self._add_face_pointer('back', p0+ds, ds)
645
+
646
+ @property
647
+ def front(self) -> FaceSelection:
648
+ """The first local -Z face of the Cone."""
649
+ return self.face('front')
650
+
651
+ @property
652
+ def back(self) -> FaceSelection:
653
+ """The back local +Z face of the Cone. If the tip of the cone has a 0 radius, no back face can be selected."""
654
+ return self.face('back')
655
+
656
+ @property
657
+ def shell(self) -> FaceSelection:
658
+ """The outside faces excluding the top and bottom."""
659
+ return self.boundary(exclude=('front','back'))
@@ -549,17 +549,26 @@ class GeoObject:
549
549
  self._priority = _GEOMANAGER.highest_priority()+10
550
550
  return self
551
551
 
552
- def boundary(self, exclude: tuple[FaceNames,...] | None = None,
552
+ def boundary(self,
553
+ exclude: Iterable[FaceNames,...] | str | None = None,
553
554
  tags: list[int] | None = None,
554
555
  tool: GeoObject | None = None) -> FaceSelection:
555
556
  """Returns the complete set of boundary faces.
556
-
557
+
557
558
  If implemented, it is possible to exclude a set of faces based on their name
558
- or a list of tags (integers)
559
+ or a list of tags.
560
+
561
+ Args:
562
+ exclude: (Iterable[str], str, None): A single string or list/tuple of strings.
563
+ tags: A list of face integers (if known)
564
+ tool: The tool object to base the selection face names one.
559
565
 
560
566
  Returns:
561
567
  FaceSelection: The selected faces
562
568
  """
569
+ if isinstance(exclude, str):
570
+ exclude = (exclude,)
571
+
563
572
  if exclude is None:
564
573
  exclude = tuple()
565
574
 
@@ -572,17 +581,23 @@ class GeoObject:
572
581
  dimtags = gmsh.model.get_boundary(self.dimtags, True, False)
573
582
  return FaceSelection([t for d,t in dimtags if t not in tags])
574
583
 
575
- def face(self, name: FaceNames, tool: GeoObject | None = None) -> FaceSelection:
584
+ def face(self, name: FaceNames = None, tool: GeoObject | None = None, no: FaceNames = None) -> FaceSelection:
576
585
  """Returns the FaceSelection for a given face name.
577
586
 
578
587
  The face name must be defined for the type of geometry.
579
588
 
589
+ FaceNames include: front, back, left, right, top, bottom, disc
590
+
580
591
  Args:
581
592
  name (FaceNames): The name of the face to select.
593
+ tool (GeoObject, None): Which object should be used as a source for the face selection.
594
+ no (FaceNames): If everything BUT a face name should be selected, Equivalent to .boundary(exclude=name).
582
595
 
583
596
  Returns:
584
597
  FaceSelection: The selected face
585
598
  """
599
+ if no is not None:
600
+ return self.boundary(exclude=no)
586
601
 
587
602
  return FaceSelection(self._face_tags(name, tool))
588
603
 
@@ -593,6 +608,7 @@ class GeoObject:
593
608
 
594
609
  Args:
595
610
  name (FaceNames): The name of the face to select.
611
+ tool (GeoObject, None): The tool object to use as source of the selection.
596
612
 
597
613
  Returns:
598
614
  FaceSelection: The selected face
@@ -633,10 +649,12 @@ class GeoVolume(GeoObject):
633
649
  specific geometry data.'''
634
650
  dim = 3
635
651
  _default_name: str = 'GeoVolume'
652
+
636
653
  def __init__(self, tag: int | Iterable[int], name: str | None = None):
637
654
  super().__init__(name=name)
638
655
 
639
656
  self.tags: list[int] = []
657
+
640
658
  if isinstance(tag, Iterable):
641
659
  self.tags = list(tag)
642
660
  else:
@@ -14,10 +14,11 @@
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
-
17
+ from __future__ import annotations
18
18
  import numpy as np
19
19
  from typing import Callable
20
20
  import inspect
21
+ from .const import C0
21
22
 
22
23
 
23
24
  def num_args(func):
@@ -58,7 +59,7 @@ class MatProperty:
58
59
  self._z: np.ndarray = np.array([], dtype=np.float64)
59
60
 
60
61
  self._fmax = lambda f: value
61
-
62
+
62
63
  def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
63
64
  self._apply_to = np.concatenate([self._apply_to, ids])
64
65
  self._x = np.concatenate([self._x, x])
@@ -328,7 +329,7 @@ class Material:
328
329
  hex_str = self.color.lstrip('#')
329
330
  self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
330
331
  self._metal: bool = _metal
331
-
332
+
332
333
  def __getstate__(self):
333
334
  state = self.__dict__.copy()
334
335
  for k in self._pickle_exclude:
@@ -401,6 +402,34 @@ class Material:
401
402
  def color_rgb(self) -> tuple[float,float,float]:
402
403
  return self._color_rgb
403
404
 
405
+ @staticmethod
406
+ def drude_model(conductivity: float,
407
+ colission_time: float,
408
+ er: float = 1.0,
409
+ ur: float = 1.0,
410
+ color: str = "#aaaaaa",
411
+ opacity: float = 0.3,
412
+ metal: bool = True) -> Material:
413
+ """Creates a Material using the Drume model for conductivity
414
+ Requires at least the DC bulk condutivity σ₀ [S/m] and the
415
+ collision time τ.
416
+
417
+ Args:
418
+ conductivity (float): The DC bulk conductivity σ₀ in S/m
419
+ colission_time (float): The collision time.
420
+ er (float, optional): The dielectric constant. Defaults to 1.0.
421
+ ur (float, optional): The relative permeability. Defaults to 1.0.
422
+ color (str, optional): The material rendering color. Defaults to "#aaaaaa".
423
+ opacity (float, optional): The material rendering opacity. Defaults to 0.3.
424
+ metal (bool, optional): If it should be rendered as a metal.. Defaults to True.
425
+
426
+ Returns:
427
+ Material: The resultant material.
428
+ """
429
+ colission_dist = colission_time/C0
430
+ fsigma = FreqDependent(scalar = lambda f: conductivity/(1 - 1j*2*np.pi*f*colission_dist))
431
+ return Material(er, ur, 0.0, fsigma, color=color, opacity=opacity, _metal=metal)
432
+
404
433
  AIR = Material(color="#4496f3", opacity=0.05, name='Air')
405
434
  COPPER = Material(cond=5.8e7, color="#62290c", _metal=True, name='Copper')
406
435
  PEC = Material(color="#ff78aa", opacity=1.0, cond=1e30, _metal=True, name="PEC")
@@ -196,7 +196,6 @@ class Mesh3D(Mesh):
196
196
  raise ValueError(f'There is no tetrahedron with indices {i1}, {i2}, {i3}, {i4}')
197
197
  return output
198
198
 
199
-
200
199
  def get_tetrahedra(self, vol_tags: Union[int, list[int]]) -> np.ndarray:
201
200
  if isinstance(vol_tags, int):
202
201
  vol_tags = [vol_tags,]
@@ -270,6 +269,37 @@ class Mesh3D(Mesh):
270
269
  nodes.update(self.get_nodes(tags))
271
270
  return np.array([i for i, tet in enumerate(self.tets.T) if not set(tet).isdisjoint(nodes)])
272
271
 
272
+ def _get_dimtags(self, nodes: list[int] | None = None, edges: list[int] | None = None) -> list[tuple[int, int]]:
273
+ """Returns the geometry dimtags associated with a set of nodes and edges"""
274
+ if nodes is None:
275
+ nodes = []
276
+ if edges is None:
277
+ edges = []
278
+ nodes = set(nodes)
279
+ edges = set(edges)
280
+ dimtags = []
281
+
282
+ # Test faces
283
+ for tag, f_nodes in self.ftag_to_node.items():
284
+ if set(f_nodes).isdisjoint(nodes):
285
+ continue
286
+ dimtags.append((2,tag))
287
+
288
+ for tag, f_edges in self.ftag_to_edge.items():
289
+ if set(f_edges).isdisjoint(edges):
290
+ continue
291
+ dimtags.append((2,tag))
292
+
293
+ # test volumes
294
+ for tag, f_tets in self.vtag_to_tet.items():
295
+ v_nodes = set(self.tets[:,f_tets].flatten())
296
+ if not v_nodes.isdisjoint(nodes):
297
+ dimtags.append((3,tag))
298
+ v_edges = set(self.tet_to_edge[:,f_tets].flatten())
299
+ if not v_edges.isdisjoint(edges):
300
+ dimtags.append((3,tag))
301
+ return sorted(dimtags)
302
+
273
303
  def get_nodes(self, face_tags: Union[int, list[int]]) -> np.ndarray:
274
304
  '''Returns a numpyarray of all the nodes that belong to the given face tags'''
275
305
  if isinstance(face_tags, int):
@@ -572,7 +602,7 @@ class Mesh3D(Mesh):
572
602
  materials.append(vol.material)
573
603
  vol.material._hash_key = i
574
604
  i += 1
575
-
605
+
576
606
  xs = self.centers[0,:]
577
607
  ys = self.centers[1,:]
578
608
  zs = self.centers[2,:]
@@ -597,7 +627,7 @@ class Mesh3D(Mesh):
597
627
  def plot_gmsh(self) -> None:
598
628
  gmsh.fltk.run()
599
629
 
600
- def find_edge_groups(self, edge_ids: np.ndarray) -> list[tuple[Any,...]]:
630
+ def find_edge_groups(self, edge_ids: np.ndarray) -> list[tuple[int,...]]:
601
631
  """
602
632
  Find the groups of edges in the mesh.
603
633
 
@@ -75,6 +75,7 @@ class Mesher:
75
75
  self.objects: list[GeoObject] = []
76
76
  self.size_definitions: list[tuple[int, float]] = []
77
77
  self.mesh_fields: list[int] = []
78
+ self._amr_fields: list[int] = []
78
79
  self.min_size: float = None
79
80
  self.max_size: float = None
80
81
  self.periodic_cell: PeriodicCell = None
@@ -222,8 +223,13 @@ class Mesher:
222
223
  gmsh.model.mesh.field.set_numbers(ctag, "CurvesList", tags)
223
224
  gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
224
225
  self.mesh_fields.append(ctag)
226
+
227
+ def _reset_amr_points(self) -> None:
228
+ for tag in self._amr_fields:
229
+ gmsh.model.mesh.field.remove(tag)
230
+ self._amr_fields = []
225
231
 
226
- def _set_size_on_point(self, tags: list[int], max_size: float) -> None:
232
+ def _set_amr_point(self, tags: list[int], max_size: float) -> None:
227
233
  """Define the size of the mesh on a point
228
234
 
229
235
  Args:
@@ -233,7 +239,7 @@ class Mesher:
233
239
  ctag = gmsh.model.mesh.field.add("Constant")
234
240
  gmsh.model.mesh.field.set_numbers(ctag, "PointsList", tags)
235
241
  gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
236
- self.mesh_fields.append(ctag)
242
+ self._amr_fields.append(ctag)
237
243
 
238
244
  def _configure_mesh_size(self, discretizer: Callable, resolution: float):
239
245
  """Defines the mesh sizes based on a discretization callable.
@@ -267,7 +273,7 @@ class Mesher:
267
273
  logger.debug(f'Setting mesh size:{1000*size:.3f}mm in domains: {tag}')
268
274
  self._set_size_in_domain([tag,], size)
269
275
 
270
- gmsh.model.mesh.field.setNumbers(mintag, "FieldsList", self.mesh_fields)
276
+ gmsh.model.mesh.field.setNumbers(mintag, "FieldsList", self.mesh_fields + self._amr_fields)
271
277
  gmsh.model.mesh.field.setAsBackgroundMesh(mintag)
272
278
 
273
279
  for tag, size in self.size_definitions:
@@ -279,8 +285,21 @@ class Mesher:
279
285
  logger.trace(f'Unsetting mesh size constraint for domains: {dimtags}')
280
286
  for dimtag in dimtags:
281
287
  gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
282
-
283
- def set_boundary_size(self, boundary: GeoObject | Selection | Iterable,
288
+
289
+ def add_refinement_point(self,
290
+ coordinate: np.ndarray,
291
+ refinement: float,
292
+ size: float,
293
+ gr: float = 1.5):
294
+ x0, y0, z0 = coordinate
295
+ disttag = gmsh.model.mesh.field.add("MathEval")
296
+ newsize = refinement*size
297
+ funcstr = f"({newsize})/({gr}) - (1-{gr})/({gr}) * Sqrt((x-({x0}))^2+ (y-({y0}))^2 + (z-({z0}))^2)"
298
+ gmsh.model.mesh.field.setString(disttag, "F", funcstr)
299
+ self.mesh_fields.append(disttag)
300
+
301
+ def set_boundary_size(self,
302
+ boundary: GeoObject | Selection | Iterable,
284
303
  size:float,
285
304
  growth_rate: float = 1.4,
286
305
  max_size: float | None = None) -> None: