emerge 1.0.6__tar.gz → 1.1.0__tar.gz

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

Potentially problematic release.


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

Files changed (123) hide show
  1. {emerge-1.0.6 → emerge-1.1.0}/.bumpversion.toml +8 -2
  2. {emerge-1.0.6 → emerge-1.1.0}/PKG-INFO +9 -4
  3. {emerge-1.0.6 → emerge-1.1.0}/emerge/__init__.py +14 -3
  4. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/index_interp.py +45 -0
  5. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/pcb.py +132 -59
  6. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/shapes.py +11 -7
  7. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geometry.py +23 -8
  8. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/logsettings.py +26 -2
  9. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/material.py +1 -1
  10. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mesh3d.py +38 -8
  11. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mesher.py +61 -11
  12. emerge-1.1.0/emerge/_emerge/physics/microwave/adaptive_mesh.py +667 -0
  13. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/assembler.py +6 -5
  14. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_3d.py +251 -88
  15. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_bc.py +182 -11
  16. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_data.py +23 -6
  17. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/display.py +31 -18
  18. emerge-1.1.0/emerge/_emerge/settings.py +130 -0
  19. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/simmodel.py +238 -95
  20. emerge-1.1.0/emerge/_emerge/simstate.py +106 -0
  21. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/simulation_data.py +11 -23
  22. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
  23. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/solver.py +2 -2
  24. emerge-1.1.0/examples/demo0_parallel_plate.py +76 -0
  25. {emerge-1.0.6 → emerge-1.1.0}/examples/demo10_sgh.py +2 -2
  26. {emerge-1.0.6 → emerge-1.1.0}/examples/demo11_lumped_element_filter.py +3 -3
  27. {emerge-1.0.6 → emerge-1.1.0}/examples/demo12_mode_alignment.py +1 -1
  28. {emerge-1.0.6 → emerge-1.1.0}/examples/demo13_helix_antenna.py +2 -3
  29. {emerge-1.0.6 → emerge-1.1.0}/examples/demo14_boundary_selection.py +1 -1
  30. emerge-1.1.0/examples/demo15_strip_slotline_transition.py +158 -0
  31. {emerge-1.0.6 → emerge-1.1.0}/examples/demo1_stepped_imp_filter.py +15 -27
  32. {emerge-1.0.6 → emerge-1.1.0}/examples/demo2_combline_filter.py +7 -8
  33. {emerge-1.0.6 → emerge-1.1.0}/examples/demo3_coupled_line_filter.py +10 -7
  34. {emerge-1.0.6 → emerge-1.1.0}/examples/demo4_patch_antenna.py +6 -6
  35. {emerge-1.0.6 → emerge-1.1.0}/examples/demo5_revolve.py +1 -1
  36. {emerge-1.0.6 → emerge-1.1.0}/examples/demo6_striplines_with_vias.py +1 -1
  37. {emerge-1.0.6 → emerge-1.1.0}/examples/demo7_periodic_cells.py +1 -1
  38. {emerge-1.0.6 → emerge-1.1.0}/examples/demo8_waveguide_bpf_synthesis.py +12 -22
  39. {emerge-1.0.6 → emerge-1.1.0}/examples/demo9_dielectric_resonator.py +1 -1
  40. {emerge-1.0.6 → emerge-1.1.0}/pyproject.toml +10 -4
  41. {emerge-1.0.6 → emerge-1.1.0}/uv.lock +175 -86
  42. emerge-1.0.6/emerge/_emerge/settings.py +0 -12
  43. {emerge-1.0.6 → emerge-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  44. {emerge-1.0.6 → emerge-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  45. {emerge-1.0.6 → emerge-1.1.0}/.gitignore +0 -0
  46. {emerge-1.0.6 → emerge-1.1.0}/.nova/Configuration.json +0 -0
  47. {emerge-1.0.6 → emerge-1.1.0}/.python-version +0 -0
  48. {emerge-1.0.6 → emerge-1.1.0}/LICENSE +0 -0
  49. {emerge-1.0.6 → emerge-1.1.0}/README.md +0 -0
  50. {emerge-1.0.6 → emerge-1.1.0}/THIRD_PARTY_LICENSES.md +0 -0
  51. {emerge-1.0.6 → emerge-1.1.0}/UMFPACK_Install_windows.md +0 -0
  52. {emerge-1.0.6 → emerge-1.1.0}/emerge/__main__.py +0 -0
  53. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/__init__.py +0 -0
  54. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/bc.py +0 -0
  55. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/cacherun.py +0 -0
  56. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/const.py +0 -0
  57. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/coord.py +0 -0
  58. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/cs.py +0 -0
  59. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/dataset.py +0 -0
  60. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/__init__.py +0 -0
  61. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/femdata.py +0 -0
  62. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/ned2_interp.py +0 -0
  63. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/nedelec2.py +0 -0
  64. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/elements/nedleg2.py +0 -0
  65. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/emerge_update.py +0 -0
  66. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/__init__.py +0 -0
  67. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/horn.py +0 -0
  68. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/modeler.py +0 -0
  69. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/operations.py +0 -0
  70. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  71. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/dxf.py +0 -0
  72. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  73. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/pmlbox.py +0 -0
  74. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/polybased.py +0 -0
  75. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo/step.py +0 -0
  76. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/geo2d.py +0 -0
  77. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/howto.py +0 -0
  78. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mth/_cache_check.py +0 -0
  79. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mth/common_functions.py +0 -0
  80. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mth/integrals.py +0 -0
  81. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mth/optimized.py +0 -0
  82. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/mth/pairing.py +0 -0
  83. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/periodic.py +0 -0
  84. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/__init__.py +0 -0
  85. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  86. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  87. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  88. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  89. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  90. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  91. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +0 -0
  92. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  93. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  94. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  95. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/sc.py +0 -0
  96. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  97. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  98. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  99. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/__init__.py +0 -0
  100. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/display.py +0 -0
  101. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  102. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  103. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/cmap_maker.py +0 -0
  104. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  105. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot/simple_plots.py +0 -0
  106. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/plot.py +0 -0
  107. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/projects/__init__.py +0 -0
  108. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/projects/_gen_base.txt +0 -0
  109. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/projects/_load_base.txt +0 -0
  110. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/projects/generate_project.py +0 -0
  111. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/selection.py +0 -0
  112. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  113. {emerge-1.0.6 → emerge-1.1.0}/emerge/_emerge/system.py +0 -0
  114. {emerge-1.0.6 → emerge-1.1.0}/emerge/beta/dxf.py +0 -0
  115. {emerge-1.0.6 → emerge-1.1.0}/emerge/cli.py +0 -0
  116. {emerge-1.0.6 → emerge-1.1.0}/emerge/ext.py +0 -0
  117. {emerge-1.0.6 → emerge-1.1.0}/emerge/lib.py +0 -0
  118. {emerge-1.0.6 → emerge-1.1.0}/emerge/materials/__init__.py +0 -0
  119. {emerge-1.0.6 → emerge-1.1.0}/emerge/materials/isola.py +0 -0
  120. {emerge-1.0.6 → emerge-1.1.0}/emerge/materials/rogers.py +0 -0
  121. {emerge-1.0.6 → emerge-1.1.0}/emerge/plot.py +0 -0
  122. {emerge-1.0.6 → emerge-1.1.0}/emerge/pyvista.py +0 -0
  123. {emerge-1.0.6 → emerge-1.1.0}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "1.0.6"
2
+ current_version = "1.1.0"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -29,6 +29,9 @@ filename = "pyproject.toml"
29
29
  [[tool.bumpversion.files]]
30
30
  filename = "src/emerge/__init__.py"
31
31
 
32
+ [[tool.bumpversion.files]]
33
+ filename = "examples/demo0_parallel_plate.py"
34
+
32
35
  [[tool.bumpversion.files]]
33
36
  filename = "examples/demo1_stepped_imp_filter.py"
34
37
 
@@ -69,4 +72,7 @@ filename = "examples/demo12_mode_alignment.py"
69
72
  filename = "examples/demo13_helix_antenna.py"
70
73
 
71
74
  [[tool.bumpversion.files]]
72
- filename = "examples/demo14_boundary_selection.py"
75
+ filename = "examples/demo14_boundary_selection.py"
76
+
77
+ [[tool.bumpversion.files]]
78
+ filename = "examples/demo15_strip_slotline_transition.py"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 1.0.6
3
+ Version: 1.1.0
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
@@ -11,14 +11,19 @@ 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
19
  Provides-Extra: cudss
19
20
  Requires-Dist: cupy-cuda12x; extra == 'cudss'
20
- Requires-Dist: nvidia-cudss-cu12; extra == 'cudss'
21
- Requires-Dist: nvmath-python[cu12]; extra == 'cudss'
21
+ Requires-Dist: nvidia-cudss-cu12==0.5.0.16; extra == 'cudss'
22
+ Requires-Dist: nvmath-python[cu12]==0.5.0; extra == 'cudss'
23
+ Provides-Extra: cudss12
24
+ Requires-Dist: cupy-cuda12x; extra == 'cudss12'
25
+ Requires-Dist: nvidia-cudss-cu12==0.5.0.16; extra == 'cudss12'
26
+ Requires-Dist: nvmath-python[cu12]==0.5.0; extra == 'cudss12'
22
27
  Provides-Extra: dxf
23
28
  Requires-Dist: ezdxf; extra == 'dxf'
24
29
  Provides-Extra: umfpack
@@ -16,13 +16,23 @@ along with this program; if not, see
16
16
  <https://www.gnu.org/licenses/>.
17
17
 
18
18
  """
19
- import os
19
+ ############################################################
20
+ # WARNING SUPPRESSION #
21
+ ############################################################
20
22
 
21
- __version__ = "1.0.6"
23
+ import warnings
24
+ warnings.filterwarnings(
25
+ "ignore",
26
+ category=DeprecationWarning,
27
+ message="builtin type swigvarlink.*"
28
+ )
22
29
 
23
30
  ############################################################
24
31
  # HANDLE ENVIRONMENT VARIABLES #
25
32
  ############################################################
33
+ import os
34
+
35
+ __version__ = "1.1.0"
26
36
 
27
37
  NTHREADS = "1"
28
38
  os.environ["EMERGE_STD_LOGLEVEL"] = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
@@ -36,6 +46,7 @@ os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
36
46
  os.environ["NUMBA_NUM_THREADS"] = os.getenv("NUMBA_NUM_THREADS", default="4")
37
47
  os.environ.setdefault("NUMBA_THREADING_LAYER", "workqueue")
38
48
 
49
+
39
50
  ############################################################
40
51
  # IMPORT MODULES #
41
52
  ############################################################
@@ -48,7 +59,7 @@ logger.debug('Importing modules')
48
59
  LOG_CONTROLLER._set_log_buffer()
49
60
 
50
61
  import gmsh
51
- from ._emerge.simmodel import Simulation
62
+ from ._emerge.simmodel import Simulation, SimulationBeta
52
63
  from ._emerge.material import Material, FreqCoordDependent, FreqDependent, CoordDependent
53
64
  from ._emerge import bc
54
65
  from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
@@ -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
@@ -934,12 +934,14 @@ class StripPath:
934
934
  return self.path[element_nr]
935
935
 
936
936
  class PCBLayer:
937
-
937
+ _DEFNAME: str = 'PCBLayer'
938
938
  def __init__(self,
939
939
  thickness: float,
940
- material: Material):
940
+ material: Material,
941
+ name: str | None = None):
941
942
  self.th: float = thickness
942
943
  self.mat: Material = material
944
+ self.name: str = _NAME_MANAGER(name, self._DEFNAME)
943
945
 
944
946
  ############################################################
945
947
  # PCB DESIGN CLASS #
@@ -976,6 +978,7 @@ class PCB:
976
978
 
977
979
  self.thickness: float = thickness
978
980
  self._stack: list[PCBLayer] = []
981
+
979
982
  if zs is not None:
980
983
  self._zs = zs
981
984
  self.thickness = np.max(zs)-np.min(zs)
@@ -1029,9 +1032,15 @@ class PCB:
1029
1032
  self.calc: PCBCalculator = PCBCalculator(self.thickness, self._zs, self.material, self.unit)
1030
1033
 
1031
1034
  self.name: str = _NAME_MANAGER(name, self._DEFNAME)
1032
-
1035
+
1036
+
1037
+ ############################################################
1038
+ # PROPERTIES #
1039
+ ############################################################
1040
+
1033
1041
  @property
1034
1042
  def trace(self) -> GeoPolygon:
1043
+ ""
1035
1044
  tags = []
1036
1045
  for trace in self.traces:
1037
1046
  tags.extend(trace.tags)
@@ -1040,29 +1049,36 @@ class PCB:
1040
1049
 
1041
1050
  @property
1042
1051
  def all_objects(self) -> list[GeoPolygon]:
1043
- return self.traces + self.ports
1044
-
1045
-
1046
- def z(self, layer: int) -> float:
1047
- """Returns the z-height of the given layer number counter from 1 (bottom) to N (top)
1048
-
1049
- Args:
1050
- layer (int): The layer number (1 to N)
1052
+ """Returns all objects gnerated by the PCB layer.
1051
1053
 
1052
1054
  Returns:
1053
- float: the z-height
1055
+ list[GeoPolygon]: _description_
1054
1056
  """
1055
- if layer <= 0:
1056
- return self._zs[layer]
1057
- return self._zs[layer-1]
1057
+ return self.traces + self.ports
1058
1058
 
1059
1059
  @property
1060
1060
  def top(self) -> float:
1061
+ """ The top conductor later height (z-value in meters)."""
1061
1062
  return self._zs[-1]
1062
1063
 
1063
1064
  @property
1064
1065
  def bottom(self) -> float:
1066
+ """The bottom conductor layer height (z-value in meters)
1067
+
1068
+ """
1065
1069
  return self._zs[0]
1070
+
1071
+
1072
+ ############################################################
1073
+ # PRIVATE FUNCTIONS #
1074
+ ############################################################
1075
+
1076
+ def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
1077
+ geopoly = poly._finalize(self.cs, name=name)
1078
+ geopoly._aux_data['func'] = function
1079
+ geopoly._aux_data['width'] = width
1080
+ geopoly._aux_data['height'] = length
1081
+ self.lumped_elements.append(geopoly)
1066
1082
 
1067
1083
  def _get_z(self, element: RouteElement) -> float :
1068
1084
  """Return the z-height of a given Route Element
@@ -1077,12 +1093,62 @@ class PCB:
1077
1093
  if path._has(element):
1078
1094
  return path.z
1079
1095
  raise RouteException('Requesting z-height of route element that is not contained in a path.')
1096
+
1097
+ def __call__(self, path_nr: int) -> StripPath:
1098
+ if path_nr >= len(self.paths):
1099
+ self.paths.append(StripPath(self))
1100
+ return self.paths[path_nr]
1101
+
1102
+ def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
1103
+ """ Generates a GeoPoly out of a list of (x,y) coordinate tuples.
1104
+
1105
+
1106
+ Args:
1107
+ xys (list[tuple[float, float]]): A list of (x,y) coordinate tuples.
1108
+ z (float, optional): The z-height of the polygon. Defaults to the top layer.
1109
+ name (str, optional): The name of the polygon.
1110
+ """
1111
+ ptags = []
1112
+ for x,y in xys:
1113
+ px, py, pz = self.cs.in_global_cs(x*self.unit, y*self.unit, z*self.unit)
1114
+ ptags.append(gmsh.model.occ.addPoint(px, py, pz))
1115
+
1116
+ ltags = []
1117
+ for t1, t2 in zip(ptags[:-1], ptags[1:]):
1118
+ ltags.append(gmsh.model.occ.addLine(t1, t2))
1119
+ ltags.append(gmsh.model.occ.addLine(ptags[-1], ptags[0]))
1120
+
1121
+ tag_wire = gmsh.model.occ.addWire(ltags)
1122
+ planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
1123
+ poly = GeoPolygon([planetag,], name=name)
1124
+ poly._store('thickness', self.trace_thickness)
1125
+ return poly
1126
+
1127
+
1128
+ ############################################################
1129
+ # USER FUNCTIONS #
1130
+ ############################################################
1131
+
1132
+ def z(self, layer: int) -> float:
1133
+ """Returns the z-height of the given layer number counter from 1 (bottom) to N (top)
1134
+
1135
+ Args:
1136
+ layer (int): The layer number (1 to N)
1137
+
1138
+ Returns:
1139
+ float: the z-height
1140
+ """
1141
+ if layer <= 0:
1142
+ return self._zs[layer]
1143
+ return self._zs[layer-1]
1080
1144
 
1081
1145
  def add_vias(self, *coordinates: tuple[float, float], radius: float,
1082
1146
  z1: float | None = None,
1083
1147
  z2: float | None = None,
1084
1148
  segments: int = 6) -> None:
1085
- """Add a series of vias provided by a list of coordinates.
1149
+ """Add a series of vias provided by a list of coordinates.
1150
+
1151
+ Vias will not be created yet. To generate the actual geometries use the function .generate_vias().
1086
1152
 
1087
1153
  Make sure to define the radius explicitly, otherwise the radius gets interpreted as a coordinate:
1088
1154
 
@@ -1118,10 +1184,7 @@ class PCB:
1118
1184
  return poly
1119
1185
  raise ValueError(f'There is no stripline or coordinate under the name of {name}')
1120
1186
 
1121
- def __call__(self, path_nr: int) -> StripPath:
1122
- if path_nr >= len(self.paths):
1123
- self.paths.append(StripPath(self))
1124
- return self.paths[path_nr]
1187
+
1125
1188
 
1126
1189
  def determine_bounds(self,
1127
1190
  leftmargin: float = 0,
@@ -1207,19 +1270,46 @@ class PCB:
1207
1270
  plane.set_material(self.trace_material)
1208
1271
  return plane # type: ignore
1209
1272
 
1273
+ def radial_stub(self, pos: tuple[float, float], length: float, angle: float, direction: tuple[float, float], Nsections: int = 8, w0: float = 0, z: float = 0, material: Material = None, name: str = None) -> None:
1274
+ x0, y0 = pos
1275
+ dx, dy = direction
1276
+
1277
+ rx, ry = dy, -dx
1278
+
1279
+ points = []
1280
+ if w0==0:
1281
+ points.append(pos)
1282
+ else:
1283
+ points.append((x0-rx*w0/2, y0-ry*w0/2))
1284
+ points.append((x0+rx*w0/2, y0+ry*w0/2))
1285
+
1286
+ angs = np.linspace(-angle/2, angle/2, Nsections)*np.pi/180
1287
+ c0 = x0 + 1j*y0
1288
+ vec = length*dx + 1j*length*dy
1289
+ for a in angs:
1290
+ p = c0 + vec*np.exp(1j*a)
1291
+ points.append((p.real, p.imag))
1292
+
1293
+ xs, ys = zip(*points)
1294
+ self.add_poly(xs, ys, z, material, name)
1295
+
1210
1296
  def generate_pcb(self,
1211
1297
  split_z: bool = True,
1212
- merge: bool = True) -> GeoVolume:
1298
+ merge: bool = True) -> list[GeoVolume] | GeoVolume:
1213
1299
  """Generate the PCB Block object
1214
1300
 
1301
+ Args:
1302
+ split_z (bool, optional): If a PCB consisting of a thickness, material and n_layers should be split in sub domains. Defaults to True
1303
+ merge: (bool, optional): If an output list of multiple volumes should be merged into a single object.
1304
+
1215
1305
  Returns:
1216
- GeoVolume: The PCB Block
1306
+ GeoVolume | List[GeoVolume]: The PCB Block or blocks
1217
1307
  """
1218
1308
  x0, y0, z0 = self.origin*self.unit
1219
1309
 
1220
- Nmats = len(set([layer.mat.name for layer in self._stack]))
1310
+ n_materials = len(set([layer.mat.name for layer in self._stack]))
1221
1311
 
1222
- if split_z and self._zs.shape[0]>2 or Nmats > 1:
1312
+ if split_z and self._zs.shape[0]>2 or n_materials > 1:
1223
1313
 
1224
1314
  boxes: list[GeoVolume] = []
1225
1315
  for i, (z1, z2, layer) in enumerate(zip(self._zs[:-1],self._zs[1:],self._stack)):
@@ -1228,12 +1318,12 @@ class PCB:
1228
1318
  self.length*self.unit,
1229
1319
  h*self.unit,
1230
1320
  position=(x0, y0, z0+z1*self.unit),
1231
- name=f'{self.name}_layer{i}')
1321
+ name=layer.name)
1232
1322
  box.material = layer.mat
1233
1323
  box = change_coordinate_system(box, self.cs)
1234
1324
  box.prio_set(self.dielectric_priority)
1235
1325
  boxes.append(box)
1236
- if merge and Nmats == 1:
1326
+ if merge and n_materials == 1:
1237
1327
  return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
1238
1328
  return boxes # type: ignore
1239
1329
 
@@ -1247,7 +1337,7 @@ class PCB:
1247
1337
  box = change_coordinate_system(box, self.cs)
1248
1338
  return box # type: ignore
1249
1339
 
1250
- def generate_air(self, height: float, name: str = 'PCBAirbox') -> GeoVolume:
1340
+ def generate_air(self, height: float, name: str = 'PCBAirbox', bottom: bool = False) -> GeoVolume:
1251
1341
  """Generate the Air Block object
1252
1342
 
1253
1343
  This requires that the width, depth and origin are deterimed. This
@@ -1256,11 +1346,16 @@ class PCB:
1256
1346
  Returns:
1257
1347
  GeoVolume: The PCB Block
1258
1348
  """
1349
+ dz = 0
1350
+
1259
1351
  x0, y0, z0 = self.origin*self.unit
1352
+ if bottom:
1353
+ dz = z0-self.thickness*self.unit-height*self.unit
1354
+
1260
1355
  box = Box(self.width*self.unit,
1261
1356
  self.length*self.unit,
1262
1357
  height*self.unit,
1263
- position=(x0,y0,z0),
1358
+ position=(x0,y0,z0+dz),
1264
1359
  name=name)
1265
1360
  box = change_coordinate_system(box, self.cs)
1266
1361
  return box # type: ignore
@@ -1334,16 +1429,9 @@ class PCB:
1334
1429
 
1335
1430
  return poly
1336
1431
 
1337
- def _lumped_element(self, poly: XYPolygon, function: Callable, width: float, length: float, name: str | None = 'LumpedElement') -> None:
1338
- geopoly = poly._finalize(self.cs, name=name)
1339
- geopoly._aux_data['func'] = function
1340
- geopoly._aux_data['width'] = width
1341
- geopoly._aux_data['height'] = length
1342
- self.lumped_elements.append(geopoly)
1343
-
1344
1432
  def modal_port(self,
1345
1433
  point: StripLine,
1346
- height: float,
1434
+ height: float | tuple[float, float],
1347
1435
  width_multiplier: float = 5.0,
1348
1436
  width: float | None = None,
1349
1437
  name: str | None = 'ModalPort'
@@ -1362,8 +1450,12 @@ class PCB:
1362
1450
  Returns:
1363
1451
  GeoSurface: The GeoSurface object that can be used for the waveguide.
1364
1452
  """
1365
-
1366
- height = (self.thickness + height)
1453
+ if isinstance(height, tuple):
1454
+ dz, height = height
1455
+ else:
1456
+ dz = 0
1457
+
1458
+ height = (self.thickness + height + dz)
1367
1459
 
1368
1460
  if width is not None:
1369
1461
  W = width
@@ -1373,7 +1465,7 @@ class PCB:
1373
1465
  ds = point.dirright
1374
1466
  x0 = point.x - ds[0]*W/2
1375
1467
  y0 = point.y - ds[1]*W/2
1376
- z0 = - self.thickness
1468
+ z0 = - self.thickness - dz
1377
1469
  ax1 = np.array([ds[0], ds[1], 0])*self.unit*W
1378
1470
  ax2 = np.array([0,0,1])*height*self.unit
1379
1471
 
@@ -1428,28 +1520,9 @@ class PCB:
1428
1520
  """
1429
1521
  if material is None:
1430
1522
  material = self.trace_material
1431
- poly = PCBPoly(xs, ys, z, material,name=name)
1523
+ poly = PCBPoly(xs, ys, z, material, name=name)
1432
1524
 
1433
1525
  self.polies.append(poly)
1434
-
1435
-
1436
- def _gen_poly(self, xys: list[tuple[float, float]], z: float, name: str | None = None) -> GeoPolygon:
1437
- """ Generates a GeoPoly out of a list of (x,y) coordinate tuples"""
1438
- ptags = []
1439
- for x,y in xys:
1440
- px, py, pz = self.cs.in_global_cs(x*self.unit, y*self.unit, z*self.unit)
1441
- ptags.append(gmsh.model.occ.addPoint(px, py, pz))
1442
-
1443
- ltags = []
1444
- for t1, t2 in zip(ptags[:-1], ptags[1:]):
1445
- ltags.append(gmsh.model.occ.addLine(t1, t2))
1446
- ltags.append(gmsh.model.occ.addLine(ptags[-1], ptags[0]))
1447
-
1448
- tag_wire = gmsh.model.occ.addWire(ltags)
1449
- planetag = gmsh.model.occ.addPlaneSurface([tag_wire,])
1450
- poly = GeoPolygon([planetag,], name=name)
1451
- poly._store('thickness', self.trace_thickness)
1452
- return poly
1453
1526
 
1454
1527
  @overload
1455
1528
  def compile_paths(self, merge: Literal[True]) -> GeoSurface: ...
@@ -160,11 +160,14 @@ class Sphere(GeoVolume):
160
160
  x,y,z = position
161
161
  self.tags: list[int] = [gmsh.model.occ.addSphere(x,y,z,radius),]
162
162
 
163
+ gmsh.model.occ.synchronize()
164
+ self._add_face_pointer('outside', tag=self.boundary().tags[0])
165
+
163
166
  @property
164
167
  def outside(self) -> FaceSelection:
165
168
  """The outside boundary of the sphere.
166
169
  """
167
- return self.boundary()
170
+ return self.face('outside')
168
171
 
169
172
  class XYPlate(GeoSurface):
170
173
  """Generates and XY-plane oriented plate
@@ -471,15 +474,16 @@ class HalfSphere(GeoVolume):
471
474
  self._add_face_pointer('bottom',np.array(position), np.array(direction))
472
475
  self._add_face_pointer('face',np.array(position), np.array(direction))
473
476
  self._add_face_pointer('disc',np.array(position), np.array(direction))
474
-
477
+
478
+ gmsh.model.occ.synchronize()
479
+ self._add_face_pointer('outside', tag=self.boundary(exclude='disc').tags[0])
480
+
475
481
  @property
476
482
  def outside(self) -> FaceSelection:
477
- """The outside of the sphere excluding the flat disc face
478
-
479
- Returns:
480
- FaceSelection: _description_
483
+ """The outside boundary of the half sphere.
481
484
  """
482
- return self.boundary(exclude=('disc',))
485
+ return self.face('outside')
486
+
483
487
 
484
488
  @property
485
489
  def disc(self) -> FaceSelection:
@@ -88,15 +88,12 @@ class _GeometryManager:
88
88
  def sign_in(self, modelname: str) -> None:
89
89
  # if modelname not in self.geometry_list:
90
90
  # self.geometry_list[modelname] = []
91
- if modelname is self.geometry_list:
92
- logger.warning(f'{modelname} already exist, Geometries will be reset.')
93
91
  self.geometry_list[modelname] = dict()
94
92
  self.geometry_names[modelname] = set()
95
93
  self.active = modelname
96
94
 
97
95
  def reset(self, modelname: str) -> None:
98
- self.geometry_list[modelname] = dict()
99
- self.geometry_names[modelname] = set()
96
+ self.sign_in(modelname)
100
97
 
101
98
  def lowest_priority(self) -> int:
102
99
  return min([geo._priority for geo in self.all_geometries()])
@@ -123,9 +120,9 @@ class _FacePointer:
123
120
  normals: list[np.ndarray]) -> list[int]:
124
121
  tags = []
125
122
  for (d,t), o, n in zip(dimtags, origins, normals):
126
- normdist = np.abs((o-self.o)@self.n)
123
+ normdist = np.abs((o-self.o) @ self.n)
127
124
  dotnorm = np.abs(n@self.n)
128
- if normdist < 1e-3 and dotnorm > 0.99:
125
+ if normdist < 1e-5 and dotnorm > 0.999:
129
126
  tags.append(t)
130
127
  return tags
131
128
 
@@ -257,7 +254,7 @@ class GeoObject:
257
254
  self._embeddings: list[GeoObject] = []
258
255
  self._face_pointers: dict[str, _FacePointer] = dict()
259
256
  self._tools: dict[int, dict[str, _FacePointer]] = dict()
260
-
257
+ self._hidden: bool = False
261
258
  self._key = _GENERATOR.new()
262
259
  self._aux_data: dict[str, Any] = dict()
263
260
  self._priority: int = 10
@@ -617,7 +614,25 @@ class GeoObject:
617
614
  for name in names:
618
615
  tags.extend(self._face_tags(name, tool))
619
616
  return FaceSelection(tags)
620
-
617
+
618
+ def hide(self) -> GeoObject:
619
+ """Hides the object from views
620
+
621
+ Returns:
622
+ GeoObject: _description_
623
+ """
624
+ self._hidden = True
625
+ return self
626
+
627
+ def unhide(self) -> GeoObject:
628
+ """Unhides the object from views
629
+
630
+ Returns:
631
+ GeoObject: _description_
632
+ """
633
+ self._hidden = False
634
+ return self
635
+
621
636
  @property
622
637
  def dimtags(self) -> list[tuple[int, int]]:
623
638
  return [(self.dim, tag) for tag in self.tags]
@@ -17,7 +17,7 @@
17
17
 
18
18
  from loguru import logger
19
19
  import sys
20
- from typing import Literal
20
+ from typing import Literal, Generator
21
21
  from pathlib import Path
22
22
  import os
23
23
  from collections import deque
@@ -132,4 +132,28 @@ class LogController:
132
132
  self.file_level = loglevel
133
133
  os.environ["EMERGE_FILE_LOGLEVEL"] = loglevel
134
134
 
135
- LOG_CONTROLLER = LogController()
135
+ class DebugCollector:
136
+ """The DebugController is used by EMerge to collect heuristic
137
+ warnings for detections of things that might be causing problems but aren't
138
+ guaranteed to cause them. These logs will be printed at the end of a simulation
139
+ to ensure that users are aware of them if they abort simulations.
140
+
141
+ """
142
+ def __init__(self):
143
+ self.reports: list[str] = []
144
+
145
+ @property
146
+ def any_warnings(self) -> bool:
147
+ return len(self.reports)>0
148
+
149
+ def add_report(self, message: str):
150
+ self.reports.append(message)
151
+
152
+ def all_reports(self) -> Generator[tuple[int, str], None, None]:
153
+
154
+ for i, message in enumerate(self.reports):
155
+ yield i+1, message
156
+
157
+
158
+ LOG_CONTROLLER = LogController()
159
+ DEBUG_COLLECTOR = DebugCollector()
@@ -59,7 +59,7 @@ class MatProperty:
59
59
  self._z: np.ndarray = np.array([], dtype=np.float64)
60
60
 
61
61
  self._fmax = lambda f: value
62
-
62
+
63
63
  def initialize(self, x: np.ndarray, y: np.ndarray, z: np.ndarray, ids: np.ndarray) -> None:
64
64
  self._apply_to = np.concatenate([self._apply_to, ids])
65
65
  self._x = np.concatenate([self._x, x])