emerge 1.0.4__tar.gz → 1.0.6__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 (119) hide show
  1. {emerge-1.0.4 → emerge-1.0.6}/.bumpversion.toml +1 -1
  2. emerge-1.0.6/.nova/Configuration.json +3 -0
  3. {emerge-1.0.4 → emerge-1.0.6}/PKG-INFO +2 -2
  4. {emerge-1.0.4 → emerge-1.0.6}/README.md +1 -1
  5. {emerge-1.0.4 → emerge-1.0.6}/emerge/__init__.py +2 -2
  6. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/horn.py +17 -1
  7. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/operations.py +4 -2
  8. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/pcb.py +10 -6
  9. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/polybased.py +27 -1
  10. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/shapes.py +93 -1
  11. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geometry.py +22 -4
  12. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/material.py +31 -2
  13. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mesher.py +2 -1
  14. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/assembler.py +1 -1
  15. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +78 -77
  16. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/microwave_3d.py +12 -9
  17. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/microwave_bc.py +2 -2
  18. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/microwave_data.py +9 -0
  19. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/pyvista/display.py +88 -0
  20. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/simmodel.py +3 -1
  21. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/solver.py +96 -72
  22. {emerge-1.0.4 → emerge-1.0.6}/examples/demo10_sgh.py +2 -4
  23. {emerge-1.0.4 → emerge-1.0.6}/examples/demo11_lumped_element_filter.py +2 -2
  24. {emerge-1.0.4 → emerge-1.0.6}/examples/demo12_mode_alignment.py +1 -1
  25. {emerge-1.0.4 → emerge-1.0.6}/examples/demo13_helix_antenna.py +1 -1
  26. {emerge-1.0.4 → emerge-1.0.6}/examples/demo14_boundary_selection.py +4 -2
  27. {emerge-1.0.4 → emerge-1.0.6}/examples/demo1_stepped_imp_filter.py +6 -6
  28. {emerge-1.0.4 → emerge-1.0.6}/examples/demo2_combline_filter.py +5 -2
  29. {emerge-1.0.4 → emerge-1.0.6}/examples/demo3_coupled_line_filter.py +2 -2
  30. {emerge-1.0.4 → emerge-1.0.6}/examples/demo4_patch_antenna.py +1 -2
  31. {emerge-1.0.4 → emerge-1.0.6}/examples/demo5_revolve.py +6 -6
  32. {emerge-1.0.4 → emerge-1.0.6}/examples/demo6_striplines_with_vias.py +5 -5
  33. {emerge-1.0.4 → emerge-1.0.6}/examples/demo7_periodic_cells.py +3 -3
  34. {emerge-1.0.4 → emerge-1.0.6}/examples/demo8_waveguide_bpf_synthesis.py +6 -6
  35. {emerge-1.0.4 → emerge-1.0.6}/examples/demo9_dielectric_resonator.py +1 -1
  36. {emerge-1.0.4 → emerge-1.0.6}/pyproject.toml +1 -1
  37. {emerge-1.0.4 → emerge-1.0.6}/uv.lock +1 -1
  38. emerge-1.0.4/UMFPACK_installer_windows.py +0 -213
  39. {emerge-1.0.4 → emerge-1.0.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {emerge-1.0.4 → emerge-1.0.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {emerge-1.0.4 → emerge-1.0.6}/.gitignore +0 -0
  42. {emerge-1.0.4 → emerge-1.0.6}/.python-version +0 -0
  43. {emerge-1.0.4 → emerge-1.0.6}/LICENSE +0 -0
  44. {emerge-1.0.4 → emerge-1.0.6}/THIRD_PARTY_LICENSES.md +0 -0
  45. {emerge-1.0.4 → emerge-1.0.6}/UMFPACK_Install_windows.md +0 -0
  46. {emerge-1.0.4 → emerge-1.0.6}/emerge/__main__.py +0 -0
  47. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/__init__.py +0 -0
  48. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/bc.py +0 -0
  49. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/cacherun.py +0 -0
  50. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/const.py +0 -0
  51. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/coord.py +0 -0
  52. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/cs.py +0 -0
  53. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/dataset.py +0 -0
  54. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/__init__.py +0 -0
  55. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/femdata.py +0 -0
  56. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/index_interp.py +0 -0
  57. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/ned2_interp.py +0 -0
  58. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/nedelec2.py +0 -0
  59. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/elements/nedleg2.py +0 -0
  60. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/emerge_update.py +0 -0
  61. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/__init__.py +0 -0
  62. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/modeler.py +0 -0
  63. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  64. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/pcb_tools/dxf.py +0 -0
  65. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  66. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/pmlbox.py +0 -0
  67. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo/step.py +0 -0
  68. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/geo2d.py +0 -0
  69. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/howto.py +0 -0
  70. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/logsettings.py +0 -0
  71. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mesh3d.py +0 -0
  72. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mth/_cache_check.py +0 -0
  73. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mth/common_functions.py +0 -0
  74. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mth/integrals.py +0 -0
  75. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mth/optimized.py +0 -0
  76. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/mth/pairing.py +0 -0
  77. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/periodic.py +0 -0
  78. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/__init__.py +0 -0
  79. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  80. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  81. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  82. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  83. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  84. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +0 -0
  85. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  86. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  87. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  88. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/sc.py +0 -0
  89. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  90. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  91. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  92. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/__init__.py +0 -0
  93. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/display.py +0 -0
  94. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  95. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  96. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/pyvista/cmap_maker.py +0 -0
  97. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  98. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot/simple_plots.py +0 -0
  99. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/plot.py +0 -0
  100. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/projects/__init__.py +0 -0
  101. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/projects/_gen_base.txt +0 -0
  102. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/projects/_load_base.txt +0 -0
  103. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/projects/generate_project.py +0 -0
  104. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/selection.py +0 -0
  105. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/settings.py +0 -0
  106. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/simulation_data.py +0 -0
  107. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
  108. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  109. {emerge-1.0.4 → emerge-1.0.6}/emerge/_emerge/system.py +0 -0
  110. {emerge-1.0.4 → emerge-1.0.6}/emerge/beta/dxf.py +0 -0
  111. {emerge-1.0.4 → emerge-1.0.6}/emerge/cli.py +0 -0
  112. {emerge-1.0.4 → emerge-1.0.6}/emerge/ext.py +0 -0
  113. {emerge-1.0.4 → emerge-1.0.6}/emerge/lib.py +0 -0
  114. {emerge-1.0.4 → emerge-1.0.6}/emerge/materials/__init__.py +0 -0
  115. {emerge-1.0.4 → emerge-1.0.6}/emerge/materials/isola.py +0 -0
  116. {emerge-1.0.4 → emerge-1.0.6}/emerge/materials/rogers.py +0 -0
  117. {emerge-1.0.4 → emerge-1.0.6}/emerge/plot.py +0 -0
  118. {emerge-1.0.4 → emerge-1.0.6}/emerge/pyvista.py +0 -0
  119. {emerge-1.0.4 → emerge-1.0.6}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.0.4"
2
+ current_version = "1.0.6"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -0,0 +1,3 @@
1
+ {
2
+ "python.pythonPath" : "\/Users\/robertfennis\/Documents\/EMerge\/.venv\/bin\/python"
3
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 1.0.4
3
+ Version: 1.0.6
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 @@ Hello everybody. Thanks for showing interest in this repository.
32
32
  Feel free to download your version of EMerge and start playing around with it!
33
33
  If you have suggestions/changes/questions either use the Github issue system or join the Discord using the following link:
34
34
 
35
- **https://discord.gg/uArxywnz**
35
+ **[Discord Invitation](https://discord.gg/VMftDCZcNz)**
36
36
 
37
37
  ## How to install
38
38
 
@@ -5,7 +5,7 @@ Hello everybody. Thanks for showing interest in this repository.
5
5
  Feel free to download your version of EMerge and start playing around with it!
6
6
  If you have suggestions/changes/questions either use the Github issue system or join the Discord using the following link:
7
7
 
8
- **https://discord.gg/uArxywnz**
8
+ **[Discord Invitation](https://discord.gg/VMftDCZcNz)**
9
9
 
10
10
  ## How to install
11
11
 
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "1.0.4"
21
+ __version__ = "1.0.6"
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
  ############################################################
@@ -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'))
@@ -51,11 +51,13 @@ def add(main: T, tool: T,
51
51
  GeoSurface | GeoVolume
52
52
  A new object that is the union of the main and tool objects.
53
53
  '''
54
+
54
55
  out_dim_tags, out_dim_tags_map = gmsh.model.occ.fuse(main.dimtags, tool.dimtags, removeObject=remove_object, removeTool=remove_tool)
55
56
  if out_dim_tags[0][0] == 3:
56
57
  output = GeoVolume([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
57
58
  elif out_dim_tags[0][0] == 2:
58
59
  output = GeoSurface([dt[1] for dt in out_dim_tags])._take_tools(tool,main)
60
+
59
61
  if remove_object:
60
62
  main._exists = False
61
63
  if remove_tool:
@@ -306,10 +308,10 @@ def unite(*objects: GeoObject) -> GeoObject:
306
308
 
307
309
  main._exists = False
308
310
  dts = []
309
- for other in rest:
311
+ for other in objects:
310
312
  dts.extend(other.dimtags)
311
313
  other._exists = False
312
- new_dimtags, mapping = gmsh.model.occ.fuse(main.dimtags, dts)
314
+ new_dimtags, mapping = gmsh.model.occ.fuse(dts, main.dimtags)
313
315
 
314
316
  new_obj = GeoObject.from_dimtags(new_dimtags)._take_tools(*objects)
315
317
  new_obj.set_material(main.material)
@@ -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
 
@@ -24,6 +24,7 @@ from typing import Generator, Callable
24
24
  from ..selection import FaceSelection
25
25
  from typing import Literal
26
26
  from functools import reduce
27
+ from loguru import logger
27
28
 
28
29
 
29
30
  def _discretize_curve(xfunc: Callable, yfunc: Callable,
@@ -230,7 +231,22 @@ class GeoPrism(GeoVolume):
230
231
  tagslist = [self._face_tags(name) for name in self._face_pointers.keys() if name not in exclude]
231
232
 
232
233
  tags = list(reduce(lambda a,b: a+b, tagslist))
233
- 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'))
234
250
 
235
251
  class XYPolygon:
236
252
  """This class generalizes a polygon in an un-embedded XY space that can be embedded in 3D space.
@@ -362,10 +378,20 @@ class XYPolygon:
362
378
  self._check()
363
379
 
364
380
  ptags = []
381
+
365
382
  xg, yg, zg = cs.in_global_cs(self.x, self.y, 0*self.x)
366
383
 
367
384
  points = dict()
368
385
  for x,y,z in zip(xg, yg, zg):
386
+ reuse = False
387
+ for key, (px, py, pz) in points.items():
388
+ if ((x-px)**2 + (y-py)**2 + (z-pz)**2)**0.5 < 1e-12:
389
+ ptags.append(key)
390
+ reuse = True
391
+ break
392
+ if reuse:
393
+ logger.warning(f'Reusing {ptags[-1]}')
394
+ continue
369
395
  ptag = gmsh.model.occ.add_point(x,y,z)
370
396
  points[ptag] = (x,y,z)
371
397
  ptags.append(ptag)
@@ -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):
@@ -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")
@@ -280,7 +280,8 @@ class Mesher:
280
280
  for dimtag in dimtags:
281
281
  gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
282
282
 
283
- def set_boundary_size(self, boundary: GeoObject | Selection | Iterable,
283
+ def set_boundary_size(self,
284
+ boundary: GeoObject | Selection | Iterable,
284
285
  size:float,
285
286
  growth_rate: float = 1.4,
286
287
  max_size: float | None = None) -> None:
@@ -105,7 +105,7 @@ class Assembler:
105
105
  def __init__(self, settings: Settings):
106
106
 
107
107
  self.cached_matrices = None
108
- self.settings = settings
108
+ self.settings: Settings = settings
109
109
 
110
110
  def assemble_bma_matrices(self,
111
111
  field: Nedelec2,