emerge 0.4.11__tar.gz → 0.5.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 (105) hide show
  1. {emerge-0.4.11 → emerge-0.5.0}/.bumpversion.toml +1 -1
  2. {emerge-0.4.11 → emerge-0.5.0}/PKG-INFO +1 -1
  3. {emerge-0.4.11 → emerge-0.5.0}/emerge/__init__.py +15 -8
  4. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/bc.py +41 -2
  5. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/__init__.py +1 -1
  6. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/pcb.py +49 -11
  7. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/howto.py +2 -2
  8. emerge-0.5.0/emerge/_emerge/logsettings.py +83 -0
  9. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/mesh3d.py +30 -12
  10. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/mth/common_functions.py +28 -1
  11. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/mth/integrals.py +25 -3
  12. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/mth/optimized.py +126 -33
  13. emerge-0.5.0/emerge/_emerge/mth/pairing.py +97 -0
  14. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/periodic.py +22 -0
  15. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/assembly/assembler.py +129 -155
  16. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +35 -3
  17. emerge-0.5.0/emerge/_emerge/physics/microwave/assembly/periodicbc.py +130 -0
  18. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/microwave_3d.py +3 -3
  19. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/microwave_bc.py +5 -4
  20. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/microwave_data.py +2 -2
  21. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/projects/_gen_base.txt +1 -1
  22. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/simmodel.py +137 -126
  23. emerge-0.5.0/emerge/_emerge/solve_interfaces/pardiso_interface.py +468 -0
  24. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/solver.py +102 -31
  25. emerge-0.5.0/emerge/lib.py +292 -0
  26. {emerge-0.4.11 → emerge-0.5.0}/examples/demo10_sgh.py +1 -1
  27. {emerge-0.4.11 → emerge-0.5.0}/examples/demo11_lumped_element_filter.py +3 -3
  28. {emerge-0.4.11 → emerge-0.5.0}/examples/demo1_stepped_imp_filter.py +3 -3
  29. {emerge-0.4.11 → emerge-0.5.0}/examples/demo2_combline_filter.py +1 -1
  30. {emerge-0.4.11 → emerge-0.5.0}/examples/demo3_coupled_line_filter.py +2 -2
  31. {emerge-0.4.11 → emerge-0.5.0}/examples/demo3_patch_antenna.py +2 -2
  32. {emerge-0.4.11 → emerge-0.5.0}/examples/demo4_boundary_selection.py +1 -1
  33. {emerge-0.4.11 → emerge-0.5.0}/examples/demo6_striplines_with_vias.py +2 -2
  34. {emerge-0.4.11 → emerge-0.5.0}/examples/demo7_periodic_cells.py +1 -1
  35. {emerge-0.4.11 → emerge-0.5.0}/examples/demo8_waveguide_bpf_synthesis.py +2 -2
  36. {emerge-0.4.11 → emerge-0.5.0}/examples/demo9_dielectric_resonator.py +1 -1
  37. {emerge-0.4.11 → emerge-0.5.0}/pyproject.toml +1 -1
  38. {emerge-0.4.11 → emerge-0.5.0}/uv.lock +1 -1
  39. emerge-0.4.11/emerge/_emerge/logsettings.py +0 -5
  40. emerge-0.4.11/emerge/_emerge/pardiso/pardiso_solver.py +0 -455
  41. emerge-0.4.11/emerge/lib.py +0 -57
  42. {emerge-0.4.11 → emerge-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  43. {emerge-0.4.11 → emerge-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  44. {emerge-0.4.11 → emerge-0.5.0}/.gitignore +0 -0
  45. {emerge-0.4.11 → emerge-0.5.0}/.python-version +0 -0
  46. {emerge-0.4.11 → emerge-0.5.0}/LICENSE +0 -0
  47. {emerge-0.4.11 → emerge-0.5.0}/README.md +0 -0
  48. {emerge-0.4.11 → emerge-0.5.0}/UMFPACK_Install_windows.md +0 -0
  49. {emerge-0.4.11 → emerge-0.5.0}/emerge/__main__.py +0 -0
  50. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/__init__.py +0 -0
  51. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/coord.py +0 -0
  52. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/cs.py +0 -0
  53. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/dataset.py +0 -0
  54. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/__init__.py +0 -0
  55. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/femdata.py +0 -0
  56. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/index_interp.py +0 -0
  57. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/legrange2.py +0 -0
  58. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/ned2_interp.py +0 -0
  59. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/nedelec2.py +0 -0
  60. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/elements/nedleg2.py +0 -0
  61. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/horn.py +0 -0
  62. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/modeler.py +0 -0
  63. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/operations.py +0 -0
  64. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  65. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  66. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/pmlbox.py +0 -0
  67. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/polybased.py +0 -0
  68. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/shapes.py +0 -0
  69. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo/step.py +0 -0
  70. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geo2d.py +0 -0
  71. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/geometry.py +0 -0
  72. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/material.py +0 -0
  73. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/mesher.py +0 -0
  74. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/__init__.py +0 -0
  75. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  76. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  77. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  78. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  79. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  80. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  81. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/sc.py +0 -0
  82. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  83. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  84. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  85. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/__init__.py +0 -0
  86. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/display.py +0 -0
  87. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/grapher.py +0 -0
  88. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  89. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  90. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/pyvista/display.py +0 -0
  91. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
  92. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot/simple_plots.py +0 -0
  93. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/plot.py +0 -0
  94. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/projects/__init__.py +0 -0
  95. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/projects/_load_base.txt +0 -0
  96. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/projects/generate_project.py +0 -0
  97. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/selection.py +0 -0
  98. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/simulation_data.py +0 -0
  99. {emerge-0.4.11 → emerge-0.5.0}/emerge/_emerge/system.py +0 -0
  100. {emerge-0.4.11 → emerge-0.5.0}/emerge/cli.py +0 -0
  101. {emerge-0.4.11 → emerge-0.5.0}/emerge/plot.py +0 -0
  102. {emerge-0.4.11 → emerge-0.5.0}/emerge/pyvista.py +0 -0
  103. {emerge-0.4.11 → emerge-0.5.0}/examples/demo5_revolve.py +0 -0
  104. {emerge-0.4.11 → emerge-0.5.0}/src/__init__.py +0 -0
  105. {emerge-0.4.11 → emerge-0.5.0}/src/_img/logo.jpeg +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.4.11"
2
+ current_version = "0.5.0"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.4.11
3
+ Version: 0.5.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
@@ -18,22 +18,29 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- NTHREADS = "1"
22
21
 
23
- os.environ["OMP_NUM_THREADS"] = "4"
24
- os.environ["MKL_NUM_THREADS"] = "4"
22
+ ############################################################
23
+ # HANDLE ENVIRONMENT VARIABLES #
24
+ ############################################################
25
+
26
+ NTHREADS = "1"
27
+ os.environ["EMERGE_STD_LOGLEVEL"] = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
28
+ os.environ["EMERGE_FILE_LOGLEVEL"] = os.getenv("EMERGE_FILE_LOGLEVEL", default="DEBUG")
29
+ os.environ["OMP_NUM_THREADS"] = os.getenv("OMP_NUM_THREADS", default="4")
30
+ os.environ["MKL_NUM_THREADS"] = os.getenv("MKL_NUM_THREADS", default="4")
25
31
  os.environ["OPENBLAS_NUM_THREADS"] = NTHREADS
26
32
  os.environ["VECLIB_MAXIMUM_THREADS"] = NTHREADS
27
33
  os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
28
34
 
29
35
 
30
- from loguru import logger
31
- from ._emerge.logsettings import logger_format
32
- import sys
36
+ ############################################################
37
+ # IMPORT MODULES #
38
+ ############################################################
33
39
 
34
- logger.remove()
35
- logger.add(sys.stderr, format=logger_format)
40
+ from ._emerge.logsettings import LOG_CONTROLLER
41
+ from loguru import logger
36
42
 
43
+ LOG_CONTROLLER.set_default()
37
44
  logger.debug('Importing modules')
38
45
 
39
46
  from ._emerge.simmodel import Simulation3D
@@ -31,8 +31,11 @@ class BCDimension(Enum):
31
31
 
32
32
  def _unique(input: list[int]) -> list:
33
33
  """ Returns a sorted list of all unique integers/floats in a list."""
34
- output = sorted(list(set(input)))
35
- return output
34
+ return sorted(list(set(input)))
35
+
36
+ ############################################################
37
+ # BASE BOUNDARY CONDITION CLASS #
38
+ ############################################################
36
39
 
37
40
  class BoundaryCondition:
38
41
  """A generalized class for all boundary condition objects.
@@ -141,13 +144,47 @@ class BoundaryConditionSet:
141
144
  return obj
142
145
  return constr
143
146
 
147
+ def assigned(self, dim: int = 2) -> list[int]:
148
+ """Returns all boundary tags that have a boundary condition assigned to them
149
+
150
+ Args:
151
+ dim (int, optional): The dimension. Defaults to 2.
152
+
153
+ Returns:
154
+ list[int]: The list of tags
155
+ """
156
+ tags = []
157
+ for bc in self.boundary_conditions:
158
+ if bc.dim != dim:
159
+ continue
160
+ tags.extend(bc.tags)
161
+ return tags
162
+
144
163
  def count(self, bctype: type) -> int:
164
+ """Returns the number of a certain boundary condition type
165
+
166
+ Args:
167
+ bctype (type): The boundary condtion type
168
+
169
+ Returns:
170
+ int: The number of occurances
171
+ """
145
172
  return len(self.oftype(bctype))
146
173
 
147
174
  def oftype(self, bctype: type) -> list[BoundaryCondition]:
175
+ """Returns a list of all boundary conditions of a certain type.
176
+
177
+ Args:
178
+ bctype (type): The boundar condition type
179
+
180
+ Returns:
181
+ list[BoundaryCondition]: The list of boundary conditions
182
+ """
148
183
  return [item for item in self. boundary_conditions if isinstance(item, bctype)]
149
184
 
150
185
  def reset(self) -> None:
186
+ """Resets the boundary conditions that are defined
187
+ """
151
188
  self.boundary_conditions = []
152
189
 
153
190
  def assign(self,
@@ -158,6 +195,8 @@ class BoundaryConditionSet:
158
195
  Args:
159
196
  bcs *(BoundaryCondition): A list of boundary condition objects.
160
197
  """
198
+ if bc in self.boundary_conditions:
199
+ return
161
200
  self._initialized = True
162
201
  wordmap = {
163
202
  0: 'point',
@@ -15,7 +15,7 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
- from .pcb import PCBLayouter
18
+ from .pcb import PCB
19
19
  from .pmlbox import pmlbox
20
20
  from .horn import Horn
21
21
  from .shapes import Cyllinder, CoaxCyllinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment
@@ -35,9 +35,26 @@ from dataclasses import dataclass
35
35
  import math
36
36
 
37
37
 
38
- SizeNames = Literal['0402','0603','1005','1608','2012','3216','3225','4532','5025','6332']
38
+ ############################################################
39
+ # EXCEPTIONS #
40
+ ############################################################
41
+
42
+ class RouteException(Exception):
43
+ pass
44
+
45
+ ############################################################
46
+ # CONSTANTS #
47
+ ############################################################
48
+
49
+ SIZE_NAMES = Literal['0402','0603','1005','1608','2012','3216','3225','4532','5025','6332']
39
50
  _SMD_SIZE_DICT = {x: (float(x[:2])*0.05, float(x[2:])*0.1) for x in ['0402','0603','1005','1608','2012','3216','3225','4532','5025','6332']}
40
51
 
52
+
53
+ ############################################################
54
+ # FUNCTIONS #
55
+ ############################################################
56
+
57
+
41
58
  def approx(a,b):
42
59
  return abs(a-b) < 1e-8
43
60
 
@@ -51,8 +68,10 @@ def _rot_mat(angle):
51
68
  ang = -angle * np.pi/180
52
69
  return np.array([[np.cos(ang), -np.sin(ang)], [np.sin(ang), np.cos(ang)]])
53
70
 
54
- class RouteException(Exception):
55
- pass
71
+ ############################################################
72
+ # CLASSES #
73
+ ############################################################
74
+
56
75
 
57
76
  class PCBPoly:
58
77
 
@@ -240,10 +259,14 @@ class StripTurn(RouteElement):
240
259
 
241
260
  return [(xend, yend), (x2, y2), (x1, y1)]
242
261
 
262
+ ############################################################
263
+ # THE STRIP PATH CLASS #
264
+ ############################################################
265
+
243
266
  class StripPath:
244
267
 
245
- def __init__(self, pcb: PCBLayouter):
246
- self.pcb: PCBLayouter = pcb
268
+ def __init__(self, pcb: PCB):
269
+ self.pcb: PCB = pcb
247
270
  self.path: list[RouteElement] = []
248
271
  self.z: float = 0
249
272
 
@@ -423,7 +446,7 @@ class StripPath:
423
446
  self.pcb._checkpoint = self
424
447
  return paths
425
448
 
426
- def lumped_element(self, impedance_function: Callable, size: SizeNames | tuple) -> StripPath:
449
+ def lumped_element(self, impedance_function: Callable, size: SIZE_NAMES | tuple) -> StripPath:
427
450
  """Adds a lumped element to the PCB.
428
451
 
429
452
  The first argument should be the impedance function as function of frequency. For a capacitor this would be:
@@ -730,7 +753,7 @@ class StripPath:
730
753
  return self
731
754
 
732
755
  def macro(self, path: str, width: float = None, start_dir: tuple[float, float] = None) -> StripPath:
733
- """Parse an EMerge macro command string
756
+ r"""Parse an EMerge macro command string
734
757
 
735
758
  The start direction by default is the abslute current heading. If a specified heading is provided
736
759
  the macro language will assume that as the current heading and generate commands accordingly.
@@ -743,7 +766,7 @@ class StripPath:
743
766
  - v X: Turn to down and move X forward
744
767
  - ^ X: Turn to up and move X forward
745
768
  - T X,Y: Taper X forward to width Y
746
- - \ X: Turn relative right 90 degrees and X forward
769
+ - \\ X: Turn relative right 90 degrees and X forward
747
770
  - / X: Turn relative left 90 degrees and X forward
748
771
 
749
772
  (*) All commands X can also be provided as X,Y to change the width
@@ -771,8 +794,12 @@ class StripPath:
771
794
  if element_nr >= len(self.path):
772
795
  self.path.append(RouteElement())
773
796
  return self.path[element_nr]
774
-
775
- class PCBLayouter:
797
+
798
+ ############################################################
799
+ # PCB DESIGN CLASS #
800
+ ############################################################
801
+
802
+ class PCB:
776
803
  def __init__(self,
777
804
  thickness: float,
778
805
  unit: float = 0.001,
@@ -1241,4 +1268,15 @@ class PCBLayouter:
1241
1268
  polys = GeoSurface(tags)
1242
1269
  polys.material = COPPER
1243
1270
  return polys
1244
-
1271
+
1272
+
1273
+ ############################################################
1274
+ # DEPRICATED #
1275
+ ############################################################
1276
+
1277
+ class PCBLayouter(PCB):
1278
+
1279
+ def __init__(self, *args, **kwargs):
1280
+ logger.warning('PCBLayouter will be depricated. Use PCB instead.')
1281
+ super().__init__(*args, **kwargs)
1282
+
@@ -49,14 +49,14 @@ class _HowtoClass:
49
49
 
50
50
  After making geometries, you should pass all of them to
51
51
  the simulation object
52
- >>> model.define_geometry(box, sphere,...)
52
+ >>> model.commit_geometry(box, sphere,...)
53
53
 
54
54
  You can also directly store geometries into the model class by treating
55
55
  it as a list (using __getitem__ and __setitem__)
56
56
  >>> model['box'] = emerge.geo.Box(...)
57
57
 
58
58
  In this case, all geometries will be automatically included. You shoul still call:
59
- >>> model.define_geometry()
59
+ >>> model.commit_geometry()
60
60
 
61
61
  """
62
62
 
@@ -0,0 +1,83 @@
1
+ from loguru import logger
2
+ import sys
3
+ from typing import Literal
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ import os
7
+
8
+
9
+ ############################################################
10
+ # FORMATS #
11
+ ############################################################
12
+
13
+ TRACE_FORMAT = (
14
+ "{time: YY/MM/DD - (ddd) - HH:mm:ss.SSSS} | <green>{elapsed}</green> [ <level>{level}</level> ] "
15
+ " <level>{message}</level>"
16
+ )
17
+ DEBUG_FORMAT = (
18
+ "<green>{elapsed}</green> [<level>{level}</level>] "
19
+ " <level>{message}</level>"
20
+ )
21
+ INFO_FORMAT = (
22
+ "<green>{elapsed}</green> [<level>{level}</level>] "
23
+ " <level>{message}</level>"
24
+ )
25
+ WARNING_FORMAT = (
26
+ "<green>{elapsed}</green> [<level>{level}</level>] "
27
+ " <level>{message}</level>"
28
+ )
29
+ ERROR_FORMAT = (
30
+ "<green>{elapsed}</green> [<level>{level}</level>] "
31
+ " <level>{message}</level>"
32
+ )
33
+ FORMAT_DICT = {
34
+ 'TRACE': TRACE_FORMAT,
35
+ 'DEBUG': DEBUG_FORMAT,
36
+ 'INFO': INFO_FORMAT,
37
+ 'WARNING': WARNING_FORMAT,
38
+ 'ERROR': ERROR_FORMAT,
39
+ }
40
+
41
+ LLTYPE = Literal['TRACE','DEBUG','INFO','WARNING','ERROR']
42
+
43
+
44
+ ############################################################
45
+ # LOG CONTROLLER #
46
+ ############################################################
47
+
48
+ class LogController:
49
+
50
+ def __init__(self):
51
+ logger.remove()
52
+ self.std_handlers: list[int] = []
53
+ self.file_handlers: list[int] = []
54
+ self.level: LLTYPE = 'INFO'
55
+ self.file_level: LLTYPE = 'INFO'
56
+
57
+ def set_default(self):
58
+ value = os.getenv("EMERGE_STD_LOGLEVEL", default="INFO")
59
+ self.set_std_loglevel(value)
60
+
61
+ def add_std_logger(self, loglevel: LLTYPE) -> None:
62
+ handle_id = logger.add(sys.stderr,
63
+ level=loglevel,
64
+ format=FORMAT_DICT.get(loglevel, INFO_FORMAT))
65
+ self.std_handlers.append(handle_id)
66
+
67
+ def set_std_loglevel(self, loglevel: str):
68
+ handler = {"sink": sys.stdout,
69
+ "level": loglevel,
70
+ "format": FORMAT_DICT.get(loglevel, INFO_FORMAT)}
71
+ logger.configure(handlers=[handler])
72
+ self.level = loglevel
73
+ os.environ["EMERGE_STD_LOGLEVEL"] = loglevel
74
+
75
+
76
+ def set_write_file(self, path: Path, loglevel: str = 'TRACE'):
77
+
78
+ handler_id = logger.add(str(path / 'logging.log'), mode='w', level=loglevel, format=FORMAT_DICT.get(loglevel, INFO_FORMAT), colorize=False, backtrace=True, diagnose=True)
79
+ self.file_handlers.append(handler_id)
80
+ self.file_level = loglevel
81
+ os.environ["EMERGE_FILE_LOGLEVEL"] = loglevel
82
+
83
+ LOG_CONTROLLER = LogController()
@@ -27,6 +27,7 @@ from .mth.optimized import outward_normal
27
27
  from loguru import logger
28
28
  from functools import cache
29
29
  from .bc import Periodic
30
+ from .mth.pairing import pair_coordinates
30
31
 
31
32
  @njit(f8(f8[:], f8[:], f8[:]), cache=True, nogil=True)
32
33
  def area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
@@ -87,8 +88,6 @@ class Mesh3D:
87
88
 
88
89
  self.geometry: Mesher = mesher
89
90
 
90
- self.exterior_face_tags: list[int] = None
91
-
92
91
  # All spatial objects
93
92
  self.nodes: np.ndarray = None
94
93
  self.n_i2t: dict = None
@@ -143,6 +142,7 @@ class Mesh3D:
143
142
  ## Memory
144
143
  self.ftag_to_tri: dict[int, list[int]] = dict()
145
144
  self.ftag_to_node: dict[int, list[int]] = dict()
145
+ self.ftag_to_edge: dict[int, list[int]] = dict()
146
146
  self.vtag_to_tet: dict[int, list[int]] = dict()
147
147
 
148
148
  @property
@@ -245,6 +245,17 @@ class Mesh3D:
245
245
  nodes.extend(self.ftag_to_node[facetag])
246
246
 
247
247
  return np.array(sorted(list(set(nodes))))
248
+
249
+ def get_edges(self, face_tags: Union[int, list[int]]) -> np.ndarray:
250
+ '''Returns a numpyarray of all the edges that belong to the given face tags'''
251
+ if isinstance(face_tags, int):
252
+ face_tags = [face_tags,]
253
+
254
+ edges = []
255
+ for facetag in face_tags:
256
+ edges.extend(self.ftag_to_edge[facetag])
257
+
258
+ return np.array(sorted(list(set(edges))))
248
259
 
249
260
 
250
261
  def update(self, periodic_bcs: list[Periodic] = None):
@@ -405,7 +416,8 @@ class Mesh3D:
405
416
  self.ftag_to_node[t] = node_tags
406
417
  node_tags = np.squeeze(np.array(node_tags)).reshape(-1,3).T
407
418
  self.ftag_to_tri[t] = [self.get_tri(node_tags[0,i], node_tags[1,i], node_tags[2,i]) for i in range(node_tags.shape[1])]
408
-
419
+ self.ftag_to_edge[t] = sorted(list(np.unique(self.tri_to_edge[:,self.ftag_to_tri[t]].flatten())))
420
+
409
421
  vol_dimtags = gmsh.model.get_entities(3)
410
422
  for d,t in vol_dimtags:
411
423
  domain_tag, v_tags, node_tags = gmsh.model.mesh.get_elements(3, t)
@@ -457,16 +469,8 @@ class Mesh3D:
457
469
  node_ids_1 = sorted(list(set(node_ids_1)))
458
470
  node_ids_2 = sorted(list(set(node_ids_2)))
459
471
  dv = np.array(bc.dv)
460
- nodemapdict = defaultdict(lambda: [None, None])
461
-
462
- mult = int(10**(-np.round(np.log10(dsmin))+2))
463
472
 
464
- for i1, i2 in zip(node_ids_1, node_ids_2):
465
- nodemapdict[gen_key(self.nodes[:,i1], mult)][0] = i1
466
- nodemapdict[gen_key(self.nodes[:,i2]-dv, mult)][1] = i2
467
-
468
- nodemap = {i1: i2 for i1, i2 in nodemapdict.values()}
469
-
473
+ nodemap = pair_coordinates(self.nodes, node_ids_1, node_ids_2, dv, dsmin/2)
470
474
  node_ids_2_unsorted = [nodemap[i] for i in sorted(node_ids_1)]
471
475
  node_ids_2_sorted = sorted(node_ids_2_unsorted)
472
476
  conv_map = {i1: i2 for i1, i2 in zip(node_ids_2_unsorted, node_ids_2_sorted)}
@@ -565,6 +569,20 @@ class Mesh3D:
565
569
  def boundary_surface(self,
566
570
  face_tags: Union[int, list[int]],
567
571
  origin: tuple[float, float, float] = None) -> SurfaceMesh:
572
+ """Returns a SurfaceMesh class that is a 2D mesh isolated from the 3D mesh
573
+
574
+ The mesh will be based on the given set of face tags.
575
+
576
+ In order to properly allign the normal vectors, an alignment origin can be provided.
577
+ If not provided, the center point of all boundaries will be used.
578
+
579
+ Args:
580
+ face_tags (Union[int, list[int]]): The list of face tags to use
581
+ origin (tuple[float, float, float], optional): The normal vecor alignment origin.. Defaults to None.
582
+
583
+ Returns:
584
+ SurfaceMesh: The resultant surface mesh
585
+ """
568
586
  tri_ids = self.get_triangles(face_tags)
569
587
  if origin is None:
570
588
  nodes = self.nodes[:,self.get_nodes(face_tags)]
@@ -18,16 +18,43 @@
18
18
  import numpy as np
19
19
 
20
20
  def norm(Field: np.ndarray) -> np.ndarray:
21
- ''' Computes the complex norm of a field (3,N)'''
21
+ """ Computes the complex norm of a field (3,N)
22
+
23
+ Args:
24
+ Field (np.ndarray): The input field, shape (3,N)
25
+
26
+ Returns:
27
+ np.ndarray: The complex norm in shape (N,)
28
+ """
22
29
  return np.sqrt(np.abs(Field[0,:])**2 + np.abs(Field[1,:])**2 + np.abs(Field[2,:])**2)
23
30
 
24
31
  def coax_rout(rin: float,
25
32
  eps_r: float = 1,
26
33
  Z0: float = 50) -> float:
34
+ """Computes the outer radius given a dielectric constant, inner radius and characteristic impedance
35
+
36
+ Args:
37
+ rin (float): The inner radius
38
+ eps_r (float, optional): The dielectric permittivity. Defaults to 1.
39
+ Z0 (float, optional): The impedance. Defaults to 50.
40
+
41
+ Returns:
42
+ float: The outer radius
43
+ """
27
44
  return rin*10**(Z0*np.sqrt(eps_r)/138)
28
45
 
29
46
  def coax_rin(rout: float,
30
47
  eps_r: float = 1,
31
48
  Z0: float = 50) -> float:
49
+ """Computes the inner radius given a dielectric constant, outer radius and characteristic impedance
50
+
51
+ Args:
52
+ rin (float): The outer radius
53
+ eps_r (float, optional): The dielectric permittivity. Defaults to 1.
54
+ Z0 (float, optional): The impedance. Defaults to 50.
55
+
56
+ Returns:
57
+ float: The inner radius
58
+ """
32
59
  return rout/10**(Z0*np.sqrt(eps_r)/138)
33
60
 
@@ -17,10 +17,14 @@
17
17
 
18
18
  import numpy as np
19
19
  from typing import Callable
20
- from numba import njit, f8, i8, types, c16
20
+ from numba import njit, f8, i8, c16
21
21
  from .optimized import gaus_quad_tri, generate_int_points_tri, calc_area
22
22
 
23
23
 
24
+ ############################################################
25
+ # NUMBA OPTIMIZED INTEGRALS #
26
+ ############################################################
27
+
24
28
  @njit(c16(f8[:,:], i8[:,:], c16[:], f8[:,:], c16[:,:]), cache=True, nogil=True)
25
29
  def _fast_integral_c(nodes, triangles, constants, DPTs, field_values):
26
30
  tot = np.complex128(0.0)
@@ -48,16 +52,34 @@ def _fast_integral_f(nodes, triangles, constants, DPTs, field_values):
48
52
  tot = tot + constants[it] * field * A
49
53
  return tot
50
54
 
55
+
56
+ ############################################################
57
+ # PYTHON WRAPPER #
58
+ ############################################################
59
+
51
60
  def surface_integral(nodes: np.ndarray,
52
61
  triangles: np.ndarray,
53
62
  function: Callable,
54
63
  constants: np.ndarray = None,
55
- ndpts: int = 4):
64
+ gq_order: int = 4) -> complex:
65
+ """Computes the surface integral of a scalar-field
66
+
67
+ Computes I = Σ ∫∫ C ᐧ f(x,y,z) dA
68
+
69
+ Args:
70
+ nodes (np.ndarray): The integration nodes
71
+ triangles (np.ndarray): The integration triangles
72
+ function (Callable): The scalar field f(x,y,z)
73
+ constants (np.ndarray, optional): The constant C. Defaults to None.
74
+ ndpts (int, optional): Order of gauss quadrature points. Defaults to 4.
56
75
 
76
+ Returns:
77
+ complex: The integral I.
78
+ """
57
79
  if constants is None:
58
80
  constants = np.ones(triangles.shape[1])
59
81
 
60
- DPTs = gaus_quad_tri(ndpts)
82
+ DPTs = gaus_quad_tri(gq_order)
61
83
  xall_flat, yall_flat, zall_flat, shape = generate_int_points_tri(nodes, triangles, DPTs)
62
84
 
63
85
  fvals = function(xall_flat, yall_flat, zall_flat)