emerge 0.6.10__tar.gz → 1.0.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 (112) hide show
  1. {emerge-0.6.10 → emerge-1.0.0}/.bumpversion.toml +1 -1
  2. {emerge-0.6.10 → emerge-1.0.0}/PKG-INFO +4 -8
  3. {emerge-0.6.10 → emerge-1.0.0}/README.md +3 -7
  4. {emerge-0.6.10 → emerge-1.0.0}/emerge/__init__.py +1 -1
  5. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/bc.py +9 -4
  6. emerge-1.0.0/emerge/_emerge/cacherun.py +79 -0
  7. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/pcb.py +17 -7
  8. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geometry.py +32 -5
  9. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/material.py +2 -1
  10. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/assembler.py +12 -12
  11. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/microwave_3d.py +42 -15
  12. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/microwave_bc.py +6 -6
  13. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/matplotlib/mpldisplay.py +1 -1
  14. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/pyvista/display.py +25 -18
  15. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/pyvista/display_settings.py +2 -1
  16. emerge-1.0.0/emerge/_emerge/settings.py +12 -0
  17. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/simmodel.py +118 -18
  18. {emerge-0.6.10 → emerge-1.0.0}/emerge/lib.py +1 -1
  19. {emerge-0.6.10 → emerge-1.0.0}/examples/demo10_sgh.py +12 -8
  20. {emerge-0.6.10 → emerge-1.0.0}/examples/demo11_lumped_element_filter.py +2 -6
  21. {emerge-0.6.10 → emerge-1.0.0}/examples/demo12_mode_alignment.py +3 -3
  22. {emerge-0.6.10 → emerge-1.0.0}/examples/demo13_helix_antenna.py +1 -1
  23. {emerge-0.6.10 → emerge-1.0.0}/examples/demo14_boundary_selection.py +1 -1
  24. {emerge-0.6.10 → emerge-1.0.0}/examples/demo1_stepped_imp_filter.py +2 -3
  25. {emerge-0.6.10 → emerge-1.0.0}/examples/demo2_combline_filter.py +1 -1
  26. {emerge-0.6.10 → emerge-1.0.0}/examples/demo3_coupled_line_filter.py +20 -20
  27. {emerge-0.6.10 → emerge-1.0.0}/examples/demo4_patch_antenna.py +4 -6
  28. emerge-1.0.0/examples/demo5_revolve.py +98 -0
  29. {emerge-0.6.10 → emerge-1.0.0}/examples/demo6_striplines_with_vias.py +3 -5
  30. {emerge-0.6.10 → emerge-1.0.0}/examples/demo7_periodic_cells.py +3 -3
  31. {emerge-0.6.10 → emerge-1.0.0}/examples/demo8_waveguide_bpf_synthesis.py +1 -1
  32. {emerge-0.6.10 → emerge-1.0.0}/examples/demo9_dielectric_resonator.py +9 -10
  33. {emerge-0.6.10 → emerge-1.0.0}/pyproject.toml +1 -1
  34. {emerge-0.6.10 → emerge-1.0.0}/uv.lock +1 -1
  35. emerge-0.6.10/.opt +0 -714
  36. emerge-0.6.10/examples/demo5_revolve.py +0 -15
  37. {emerge-0.6.10 → emerge-1.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  38. {emerge-0.6.10 → emerge-1.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  39. {emerge-0.6.10 → emerge-1.0.0}/.gitignore +0 -0
  40. {emerge-0.6.10 → emerge-1.0.0}/.python-version +0 -0
  41. {emerge-0.6.10 → emerge-1.0.0}/LICENSE +0 -0
  42. {emerge-0.6.10 → emerge-1.0.0}/THIRD_PARTY_LICENSES.md +0 -0
  43. {emerge-0.6.10 → emerge-1.0.0}/UMFPACK_Install_windows.md +0 -0
  44. {emerge-0.6.10 → emerge-1.0.0}/UMFPACK_installer_windows.py +0 -0
  45. {emerge-0.6.10 → emerge-1.0.0}/emerge/__main__.py +0 -0
  46. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/__init__.py +0 -0
  47. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/_cache_check.py +0 -0
  48. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/const.py +0 -0
  49. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/coord.py +0 -0
  50. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/cs.py +0 -0
  51. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/dataset.py +0 -0
  52. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/__init__.py +0 -0
  53. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/femdata.py +0 -0
  54. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/index_interp.py +0 -0
  55. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/ned2_interp.py +0 -0
  56. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/nedelec2.py +0 -0
  57. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/elements/nedleg2.py +0 -0
  58. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/__init__.py +0 -0
  59. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/horn.py +0 -0
  60. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/modeler.py +0 -0
  61. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/operations.py +0 -0
  62. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  63. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  64. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/pmlbox.py +0 -0
  65. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/polybased.py +0 -0
  66. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/shapes.py +0 -0
  67. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo/step.py +0 -0
  68. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/geo2d.py +0 -0
  69. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/howto.py +0 -0
  70. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/logsettings.py +0 -0
  71. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mesh3d.py +0 -0
  72. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mesher.py +0 -0
  73. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mth/common_functions.py +0 -0
  74. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mth/integrals.py +0 -0
  75. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mth/optimized.py +0 -0
  76. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/mth/pairing.py +0 -0
  77. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/periodic.py +0 -0
  78. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/__init__.py +0 -0
  79. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  80. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  81. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  82. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  83. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  84. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  85. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  86. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/microwave_data.py +0 -0
  87. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  88. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  89. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/sc.py +0 -0
  90. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  91. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  92. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  93. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/__init__.py +0 -0
  94. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/display.py +0 -0
  95. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  96. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot/simple_plots.py +0 -0
  97. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/plot.py +0 -0
  98. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/projects/__init__.py +0 -0
  99. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/projects/_gen_base.txt +0 -0
  100. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/projects/_load_base.txt +0 -0
  101. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/projects/generate_project.py +0 -0
  102. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/selection.py +0 -0
  103. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/simulation_data.py +0 -0
  104. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
  105. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  106. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/solver.py +0 -0
  107. {emerge-0.6.10 → emerge-1.0.0}/emerge/_emerge/system.py +0 -0
  108. {emerge-0.6.10 → emerge-1.0.0}/emerge/cli.py +0 -0
  109. {emerge-0.6.10 → emerge-1.0.0}/emerge/ext.py +0 -0
  110. {emerge-0.6.10 → emerge-1.0.0}/emerge/plot.py +0 -0
  111. {emerge-0.6.10 → emerge-1.0.0}/emerge/pyvista.py +0 -0
  112. {emerge-0.6.10 → emerge-1.0.0}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.6.10"
2
+ current_version = "1.0.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.6.10
3
+ Version: 1.0.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
@@ -82,17 +82,13 @@ To run this FEM library you need the following libraries
82
82
  - numba
83
83
  - matplotlib (for the matplotlib base display)
84
84
  - pyvista (for the PyVista base display)
85
- - numba-progress
85
+ - cloudpickle
86
86
  - mkl (x86 devices only)
87
87
 
88
88
  Optional:
89
89
  - scikit-umfpack
90
90
  - cudss
91
91
 
92
- ## NOTICE
92
+ ## Resources / Manual
93
93
 
94
- First time runs will be very slow because Numba needs to generate local C-compiled functions of the assembler and other mathematical functions. These compilations are chached so this should only take time once.
95
-
96
- ## Third Party License Notice
97
-
98
- “This package depends on Intel® Math Kernel Library (MKL), which is licensed separately under the Intel Simplified Software License (October 2022). Installing with pip will fetch the MKL wheel and prompt you to accept that licence.”
94
+ You can find the latest versions of the manual on: **https://www.emerge-software.com/resources/**
@@ -56,17 +56,13 @@ To run this FEM library you need the following libraries
56
56
  - numba
57
57
  - matplotlib (for the matplotlib base display)
58
58
  - pyvista (for the PyVista base display)
59
- - numba-progress
59
+ - cloudpickle
60
60
  - mkl (x86 devices only)
61
61
 
62
62
  Optional:
63
63
  - scikit-umfpack
64
64
  - cudss
65
65
 
66
- ## NOTICE
66
+ ## Resources / Manual
67
67
 
68
- First time runs will be very slow because Numba needs to generate local C-compiled functions of the assembler and other mathematical functions. These compilations are chached so this should only take time once.
69
-
70
- ## Third Party License Notice
71
-
72
- “This package depends on Intel® Math Kernel Library (MKL), which is licensed separately under the Intel Simplified Software License (October 2022). Installing with pip will fetch the MKL wheel and prompt you to accept that licence.”
68
+ You can find the latest versions of the manual on: **https://www.emerge-software.com/resources/**
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "0.6.10"
21
+ __version__ = "1.0.0"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -53,7 +53,7 @@ class BoundaryCondition:
53
53
 
54
54
 
55
55
  if isinstance(assignment, GeoObject):
56
- assignment = assignment.select
56
+ assignment = assignment.selection
57
57
 
58
58
  self.selection: Selection = assignment
59
59
  self.tags: list[int] = self.selection.tags
@@ -83,7 +83,7 @@ class BoundaryCondition:
83
83
  raise ValueError(f'All tags must have the same dimension, instead its {tags}')
84
84
  dimension = tags[0][0]
85
85
  if self.dimension is BCDimension.ANY:
86
- logger.info(f'Assigning dimension {BCDimension(dimension)} to {self}')
86
+ logger.info(f'Assigning {self} to dimension{BCDimension(dimension)}')
87
87
  self.dimension = BCDimension(dimension)
88
88
  elif self.dimension != BCDimension(dimension):
89
89
  raise ValueError(f'Current boundary condition has dimension {self.dimension}, but tags have dimension {BCDimension(dimension)}')
@@ -131,6 +131,13 @@ class BoundaryConditionSet:
131
131
  self.boundary_conditions: list[BoundaryCondition] = []
132
132
  self._initialized: bool = False
133
133
 
134
+ def cleanup(self) -> None:
135
+ """ Removes non assigned boundary conditions"""
136
+ logger.trace("Cleaning up boundary conditions.")
137
+ toremove = [bc for bc in self.boundary_conditions if len(bc.tags)==0]
138
+ logger.trace(f"Removing: {toremove}")
139
+ self.boundary_conditions = [bc for bc in self.boundary_conditions if len(bc.tags)>0]
140
+
134
141
  def _construct_bc(self, constructor: type) -> type:
135
142
  def constr(*args, **kwargs):
136
143
  obj = constructor(*args, **kwargs)
@@ -201,8 +208,6 @@ class BoundaryConditionSet:
201
208
 
202
209
  bc.add_tags(bc.selection.dimtags)
203
210
 
204
- logger.info('Excluding other possible boundary conditions')
205
-
206
211
  for existing_bc in self.boundary_conditions:
207
212
  excluded = existing_bc.exclude_bc(bc)
208
213
  if excluded:
@@ -0,0 +1,79 @@
1
+ # inside your module, e.g. utils.py
2
+ import os
3
+ import sys
4
+
5
+ import ast
6
+ import textwrap
7
+ from pathlib import Path
8
+
9
+
10
+ def _is_plain_check_run(test: ast.AST) -> bool:
11
+ """True only for a bare call to check_run() as the entire if-test."""
12
+ if not isinstance(test, ast.Call) or test.args or test.keywords:
13
+ return False
14
+ f = test.func
15
+ return (
16
+ isinstance(f, ast.Attribute) and f.attr == "cache_run"
17
+ ) or (
18
+ isinstance(f, ast.Name) and f.id == "cache_run"
19
+ )
20
+
21
+ def get_run_block_str(source: str) -> str:
22
+ """
23
+ Return all source text before the first `if check_run():` / `if *.check_run():`.
24
+ Returns None if no matching if-statement exists.
25
+ """
26
+ tree = ast.parse(source)
27
+ candidates = [n for n in ast.walk(tree) if isinstance(n, ast.If) and _is_plain_check_run(n.test)]
28
+ if not candidates:
29
+ return None
30
+ first = min(candidates, key=lambda n: n.lineno)
31
+ lines = source.splitlines(keepends=True)
32
+ return "".join(lines[: first.lineno - 1])
33
+
34
+
35
+ def get_build_block_str(source: str) -> str | None:
36
+ """Return the code string inside the first `if *.checkrun(...):` block, or None."""
37
+ tree = ast.parse(source)
38
+ for node in ast.walk(tree):
39
+ if isinstance(node, ast.If) and isinstance(node.test, ast.Call):
40
+ f = node.test.func
41
+ called = (isinstance(f, ast.Attribute) and f.attr == "cache_build") or \
42
+ (isinstance(f, ast.Name) and f.id == "cache_build")
43
+ if called and node.body:
44
+ start = node.body[0].lineno - 1 # 0-based start line
45
+ end = node.body[-1].end_lineno # 1-based end line (inclusive)
46
+ lines = source.splitlines()
47
+ block = "\n".join(lines[start:end])
48
+ return textwrap.dedent(block)
49
+ return None
50
+
51
+ def entry_script_path():
52
+ main = sys.modules.get("__main__")
53
+ # Most normal runs: python path/to/app.py
54
+ if main and hasattr(main, "__file__"):
55
+ return os.path.abspath(main.__file__)
56
+ # Fallbacks (e.g. python -m pkg.mod)
57
+ if sys.argv and sys.argv[0] not in ("", "-c"):
58
+ return os.path.abspath(sys.argv[0])
59
+ # Interactive sessions, notebooks, or embedded interpreters
60
+ return None
61
+
62
+
63
+ def get_build_section() -> str:
64
+ """ Returns the string section inside the check_build() if statement"""
65
+ name = entry_script_path()
66
+
67
+ lines = get_build_block_str(Path(name).read_text())
68
+ return lines
69
+
70
+ def get_run_section() -> str:
71
+ """Return sthe string section before the check_run() if statement
72
+
73
+ Returns:
74
+ str: _description_
75
+ """
76
+ name = entry_script_path()
77
+
78
+ lines = get_run_block_str(Path(name).read_text())
79
+ return lines
@@ -19,7 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  from ..cs import CoordinateSystem, GCS, Axis
21
21
  from ..geometry import GeoPolygon, GeoVolume, GeoSurface
22
- from ..material import Material, AIR, COPPER
22
+ from ..material import Material, AIR, COPPER, PEC
23
23
  from .shapes import Box, Plate, Cylinder
24
24
  from .polybased import XYPolygon
25
25
  from .operations import change_coordinate_system, unite
@@ -86,7 +86,7 @@ class PCBPoly:
86
86
  xs: list[float],
87
87
  ys: list[float],
88
88
  z: float = 0,
89
- material: Material = COPPER):
89
+ material: Material = PEC):
90
90
  self.xs: list[float] = xs
91
91
  self.ys: list[float] = ys
92
92
  self.z: float = z
@@ -906,12 +906,14 @@ class PCB:
906
906
  unit: float = 0.001,
907
907
  cs: CoordinateSystem | None = None,
908
908
  material: Material = AIR,
909
+ trace_material: Material = PEC,
909
910
  layers: int = 2,
910
911
  ):
911
912
 
912
913
  self.thickness: float = thickness
913
914
  self._zs: np.ndarray = np.linspace(-self.thickness, 0, layers)
914
915
  self.material: Material = material
916
+ self.trace_material: Material = trace_material
915
917
  self.width: float | None = None
916
918
  self.length: float | None = None
917
919
  self.origin: np.ndarray = np.array([0.,0.,0.])
@@ -926,6 +928,9 @@ class PCB:
926
928
  self.cs: CoordinateSystem = cs
927
929
  if self.cs is None:
928
930
  self.cs = GCS
931
+
932
+ self.dielectric_priority: int = 11
933
+ self.via_priority: int = 12
929
934
 
930
935
  self.traces: list[GeoPolygon] = []
931
936
  self.ports: list[GeoPolygon] = []
@@ -1110,7 +1115,7 @@ class PCB:
1110
1115
 
1111
1116
  plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
1112
1117
  plane = change_coordinate_system(plane, self.cs) # type: ignore
1113
- plane.set_material(COPPER)
1118
+ plane.set_material(self.trace_material)
1114
1119
  return plane # type: ignore
1115
1120
 
1116
1121
  def generate_pcb(self,
@@ -1140,9 +1145,10 @@ class PCB:
1140
1145
  position=(x0, y0, z0+z1*self.unit))
1141
1146
  box.material = self.material
1142
1147
  box = change_coordinate_system(box, self.cs)
1148
+ box.prio_set(self.dielectric_priority)
1143
1149
  boxes.append(box)
1144
1150
  if merge:
1145
- return GeoVolume.merged(boxes) # type: ignore
1151
+ return GeoVolume.merged(boxes).prio_set(self.dielectric_priority) # type: ignore
1146
1152
  return boxes # type: ignore
1147
1153
 
1148
1154
  box = Box(self.width*self.unit,
@@ -1150,6 +1156,7 @@ class PCB:
1150
1156
  self.thickness*self.unit,
1151
1157
  position=(x0,y0,z0-self.thickness*self.unit))
1152
1158
  box.material = self.material
1159
+ box.prio_set(self.dielectric_priority)
1153
1160
  box = change_coordinate_system(box, self.cs)
1154
1161
  return box # type: ignore
1155
1162
 
@@ -1304,7 +1311,8 @@ class PCB:
1304
1311
  xg, yg, zg = self.cs.in_global_cs(x0, y0, z0)
1305
1312
  cs = CoordinateSystem(self.cs.xax, self.cs.yax, self.cs.zax, np.array([xg, yg, zg]))
1306
1313
  cyl = Cylinder(via.radius*self.unit, (via.z2-via.z1)*self.unit, cs, via.segments)
1307
- cyl.material = COPPER
1314
+ cyl.material = self.trace_material
1315
+ cyl.prio_set(self.via_priority)
1308
1316
  vias.append(cyl)
1309
1317
  if merge:
1310
1318
 
@@ -1315,7 +1323,7 @@ class PCB:
1315
1323
  xs: list[float],
1316
1324
  ys: list[float],
1317
1325
  z: float = 0,
1318
- material: Material = COPPER) -> None:
1326
+ material: Material = None) -> None:
1319
1327
  """Add a custom polygon to the PCB
1320
1328
 
1321
1329
  Args:
@@ -1324,6 +1332,8 @@ class PCB:
1324
1332
  z (float, optional): The z-height. Defaults to 0.
1325
1333
  material (Material, optional): The material. Defaults to COPPER.
1326
1334
  """
1335
+ if material is None:
1336
+ material = self.trace_material
1327
1337
  self.polies.append(PCBPoly(xs, ys, z, material))
1328
1338
 
1329
1339
  def _gen_poly(self, xys: list[tuple[float, float]], z: float) -> GeoPolygon:
@@ -1385,7 +1395,7 @@ class PCB:
1385
1395
  ally.append(y)
1386
1396
 
1387
1397
  poly = self._gen_poly(xys2, z)
1388
- poly.material = COPPER
1398
+ poly.material = self.trace_material
1389
1399
  polys.append(poly)
1390
1400
 
1391
1401
  for pcbpoly in self.polies:
@@ -50,6 +50,9 @@ class _GeometryManager:
50
50
  self.geometry_list: dict[str, list[GeoObject]] = dict()
51
51
  self.active: str = ''
52
52
 
53
+ def get_surfaces(self) -> list[GeoSurface]:
54
+ return [geo for geo in self.all_geometries() if geo.dim==2]
55
+
53
56
  def all_geometries(self, model: str | None = None) -> list[GeoObject]:
54
57
  if model is None:
55
58
  model = self.active
@@ -250,7 +253,7 @@ class GeoObject:
250
253
  return self.material._metal
251
254
 
252
255
  @property
253
- def select(self) -> Selection:
256
+ def selection(self) -> Selection:
254
257
  '''Returns a corresponding Face/Domain or Edge Selection object'''
255
258
  if self.dim==1:
256
259
  return EdgeSelection(self.tags)
@@ -391,6 +394,30 @@ class GeoObject:
391
394
  self._priority = level
392
395
  return self
393
396
 
397
+ def above(self, other: GeoObject) -> GeoObject:
398
+ """Puts the priority of this object one higher than the other, then returns this object
399
+
400
+ Args:
401
+ other (GeoObject): The other object to put below this object
402
+
403
+ Returns:
404
+ GeoObject: This object
405
+ """
406
+ self._priority = other._priority + 1
407
+ return self
408
+
409
+ def below(self, other: GeoObject) -> GeoObject:
410
+ """Puts the priority of this object one lower than the other, then returns this object
411
+
412
+ Args:
413
+ other (GeoObject): The other object to put above this object
414
+
415
+ Returns:
416
+ GeoObject: This object
417
+ """
418
+ self._priority = other._priority -1
419
+ return self
420
+
394
421
  def prio_up(self) -> GeoObject:
395
422
  """Increases the material selection priority by 1
396
423
 
@@ -517,14 +544,14 @@ class GeoVolume(GeoObject):
517
544
  self.tags = [tag,]
518
545
 
519
546
  @property
520
- def select(self) -> DomainSelection:
547
+ def selection(self) -> DomainSelection:
521
548
  return DomainSelection(self.tags)
522
549
 
523
550
  class GeoPoint(GeoObject):
524
551
  dim = 0
525
552
 
526
553
  @property
527
- def select(self) -> PointSelection:
554
+ def selection(self) -> PointSelection:
528
555
  return PointSelection(self.tags)
529
556
 
530
557
  def __init__(self, tag: int | list[int]):
@@ -540,7 +567,7 @@ class GeoEdge(GeoObject):
540
567
  dim = 1
541
568
 
542
569
  @property
543
- def select(self) -> EdgeSelection:
570
+ def selection(self) -> EdgeSelection:
544
571
  return EdgeSelection(self.tags)
545
572
 
546
573
  def __init__(self, tag: int | list[int]):
@@ -558,7 +585,7 @@ class GeoSurface(GeoObject):
558
585
  dim = 2
559
586
 
560
587
  @property
561
- def select(self) -> FaceSelection:
588
+ def selection(self) -> FaceSelection:
562
589
  return FaceSelection(self.tags)
563
590
 
564
591
  def __init__(self, tag: int | list[int]):
@@ -381,4 +381,5 @@ class Material:
381
381
  return self._color_rgb
382
382
 
383
383
  AIR = Material(color="#4496f3", opacity=0.05, name='Air')
384
- COPPER = Material(cond=5.8e7, color="#62290c", _metal=True, name='Copper')
384
+ COPPER = Material(cond=5.8e7, color="#62290c", _metal=True, name='Copper')
385
+ PEC = Material(color="#ff78aa", opacity=1.0, cond=1e30, _metal=True, name="PEC")
@@ -22,6 +22,7 @@ from ....elements.nedleg2 import NedelecLegrange2
22
22
  from ....mth.optimized import gaus_quad_tri
23
23
  from ....mth.pairing import pair_coordinates
24
24
  from ....material import Material
25
+ from ....settings import Settings
25
26
  from scipy.sparse import csr_matrix
26
27
  from loguru import logger
27
28
  from ..simjob import SimJob
@@ -102,13 +103,10 @@ class Assembler:
102
103
 
103
104
  It stores some cached properties to accellerate preformance.
104
105
  """
105
- def __init__(self):
106
+ def __init__(self, settings: Settings):
106
107
 
107
108
  self.cached_matrices = None
108
- self.conductivity_limit = 1e7
109
- # Currently not used.
110
- #self._Pmat_cache: dict[tuple[int,int], csr_matrix] = dict()
111
- #self._remove_cache: list[int] = []
109
+ self.settings = settings
112
110
 
113
111
  def assemble_bma_matrices(self,
114
112
  field: Nedelec2,
@@ -155,20 +153,22 @@ class Assembler:
155
153
  E, B = generelized_eigenvalue_matrix(nedlegfield, ermesh, urmesh, port.cs._basis, k0)
156
154
 
157
155
  # TODO: Simplified to all "conductors" loosely defined. Must change to implementing line robin boundary conditions.
158
- pecs: list[BoundaryCondition] = bc_set.get_conductors()#[bc for bc in bcs if isinstance(bc,PEC)]
156
+ pecs: list[BoundaryCondition] = bc_set.get_conductors()
159
157
  if len(pecs) > 0:
160
- logger.debug(f'.total of equiv. {len(pecs)} PEC BCs implemented')
158
+ logger.debug(f'.total of equiv. {len(pecs)} PEC BCs implemented for BMA')
161
159
 
162
160
  pec_ids = []
163
161
 
164
162
  # Process all concutors. Everything above the conductivity limit is considered pec.
165
163
  for it in range(boundary_surface.n_tris):
166
- if sigmesh[it] > self.conductivity_limit:
164
+ if sigmesh[it] > self.settings.mw_3d_peclim:
167
165
  pec_ids.extend(list(nedlegfield.tri_to_field[:,it]))
168
166
 
169
167
  # Process all PEC Boundary Conditions
170
168
  for pec in pecs:
171
169
  logger.trace(f'.implementing {pec}')
170
+ if len(pec.tags)==0:
171
+ continue
172
172
  face_tags = pec.tags
173
173
  tri_ids = mesh.get_triangles(face_tags)
174
174
  edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
@@ -235,7 +235,7 @@ class Assembler:
235
235
 
236
236
  er = er*(1-1j*tand) - 1j*cond/(W0*EPS0)
237
237
 
238
- is_frequency_dependent = is_frequency_dependent or np.any((cond > 0) & (cond < self.conductivity_limit)) # type: ignore
238
+ is_frequency_dependent = is_frequency_dependent or np.any((cond > 0) & (cond < self.settings.mw_3d_peclim)) # type: ignore
239
239
 
240
240
  if cache_matrices and not is_frequency_dependent and self.cached_matrices is not None:
241
241
  # IF CACHED AND AVAILABLE PULL E AND B FROM CACHE
@@ -273,11 +273,11 @@ class Assembler:
273
273
  # Conductivity above al imit, consider it all PEC
274
274
  ipec = 0
275
275
  for itet in range(field.n_tets):
276
- if cond[0,0,itet] > self.conductivity_limit:
276
+ if cond[0,0,itet] > self.settings.mw_3d_peclim:
277
277
  ipec+=1
278
278
  pec_ids.extend(field.tet_to_field[:,itet])
279
279
  if ipec>0:
280
- logger.trace(f'Extended PEC with {ipec} tets with a conductivity > {self.conductivity_limit}.')
280
+ logger.trace(f'Extended PEC with {ipec} tets with a conductivity > {self.settings.mw_3d_peclim}.')
281
281
 
282
282
  for pec in pec_bcs:
283
283
  logger.trace(f'Implementing: {pec}')
@@ -466,7 +466,7 @@ class Assembler:
466
466
 
467
467
  # Conductivity above a limit, consider it all PEC
468
468
  for itet in range(field.n_tets):
469
- if cond[0,0,itet] > self.conductivity_limit:
469
+ if cond[0,0,itet] > self.settings.mw_3d_peclim:
470
470
  pec_ids.extend(field.tet_to_field[:,itet])
471
471
 
472
472
  # PEC Boundary conditions
@@ -19,12 +19,14 @@ from ...mesher import Mesher
19
19
  from ...material import Material
20
20
  from ...mesh3d import Mesh3D
21
21
  from ...coord import Line
22
+ from ...geometry import GeoSurface
22
23
  from ...elements.femdata import FEMBasis
23
24
  from ...elements.nedelec2 import Nedelec2
24
25
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
25
26
  from ...system import called_from_main_function
26
27
  from ...selection import FaceSelection
27
28
  from ...mth.optimized import compute_distances
29
+ from ...settings import Settings
28
30
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
29
31
  from .microwave_data import MWData
30
32
  from .assembly.assembler import Assembler
@@ -122,16 +124,16 @@ class Microwave3D:
122
124
  formulation.
123
125
 
124
126
  """
125
- def __init__(self, mesher: Mesher, mwdata: MWData, order: int = 2):
127
+ def __init__(self, mesher: Mesher, settings: Settings, mwdata: MWData, order: int = 2):
126
128
  self.frequencies: list[float] = []
127
129
  self.current_frequency = 0
128
130
  self.order: int = order
129
131
  self.resolution: float = 1
130
-
132
+ self._settings: Settings = settings
131
133
  self.mesher: Mesher = mesher
132
134
  self.mesh: Mesh3D = Mesh3D(self.mesher)
133
135
 
134
- self.assembler: Assembler = Assembler()
136
+ self.assembler: Assembler = Assembler(self._settings)
135
137
  self.bc: MWBoundaryConditionSet = MWBoundaryConditionSet(None)
136
138
  self.basis: Nedelec2 | None = None
137
139
  self.solveroutine: SolveRoutine = DEFAULT_ROUTINE
@@ -190,14 +192,28 @@ class Microwave3D:
190
192
  return sorted(self.bc.oftype(PortBC), key=lambda x: x.number) # type: ignore
191
193
 
192
194
 
193
- def _initialize_bcs(self) -> None:
195
+ def _initialize_bcs(self, surfaces: list[GeoSurface]) -> None:
194
196
  """Initializes the boundary conditions to set PEC as all exterior boundaries.
195
197
  """
196
198
  logger.debug('Initializing boundary conditions.')
197
199
 
198
200
  tags = self.mesher.domain_boundary_face_tags
201
+
202
+ # Assigning surface impedance boundary condition
203
+ if self._settings.mw_2dbc:
204
+ for surf in surfaces:
205
+ if surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_peclim:
206
+ logger.debug(f'Assinging PEC to {surf}')
207
+ self.bc.PEC(surf)
208
+ elif surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_lim:
209
+ logger.debug(f'Assigning SurfaceImpedance to {surf}')
210
+ self.bc.SurfaceImpedance(surf, surf.material)
211
+
212
+
199
213
  tags = [tag for tag in tags if tag not in self.bc.assigned(2)]
214
+
200
215
  self.bc.PEC(FaceSelection(tags))
216
+
201
217
  logger.info(f'Adding PEC boundary condition with tags {tags}.')
202
218
  if self.mesher.periodic_cell is not None:
203
219
  self.mesher.periodic_cell.generate_bcs()
@@ -247,6 +263,12 @@ class Microwave3D:
247
263
  resolution (float): The desired wavelength fraction.
248
264
 
249
265
  """
266
+ if resolution > 0.5:
267
+ logger.warning('Resolutions greater than 0.5 cannot yield accurate results, capping resolution to 0.4')
268
+ resolution = 0.4
269
+ elif resolution > 0.334:
270
+ logger.warning('A resolution greater than 0.33 may cause accuracy issues.')
271
+
250
272
  self.resolution = resolution
251
273
 
252
274
  def set_conductivity_limit(self, condutivity: float) -> None:
@@ -292,6 +314,7 @@ class Microwave3D:
292
314
  ''' Initializes auxilliary required boundary condition information before running simulations.
293
315
  '''
294
316
  logger.debug('Initializing boundary conditions')
317
+ self.bc.cleanup()
295
318
  for port in self.bc.oftype(LumpedPort):
296
319
  self.define_lumped_port_integration_points(port)
297
320
 
@@ -311,22 +334,25 @@ class Microwave3D:
311
334
 
312
335
  if points.size==0:
313
336
  raise SimulationError(f'The lumped port {port} has no nodes associated with it')
337
+
314
338
  xs = self.mesh.nodes[0,points]
315
339
  ys = self.mesh.nodes[1,points]
316
340
  zs = self.mesh.nodes[2,points]
317
341
 
318
342
  dotprod = xs*field_axis[0] + ys*field_axis[1] + zs*field_axis[2]
319
343
 
320
- start_id = points[np.argwhere(dotprod == np.min(dotprod))]
344
+ start_id = np.argwhere(dotprod == np.min(dotprod)).flatten()
345
+
346
+ xs = xs[start_id]
347
+ ys = ys[start_id]
348
+ zs = zs[start_id]
321
349
 
322
- start = _pick_central(self.mesh.nodes[:,start_id.flatten()])
323
- logger.info(f'Starting node = {_dimstring(start)}')
324
- end = start + port.Vdirection.np*port.height
325
-
326
-
327
- port.vintline = Line.from_points(start, end, 21)
328
350
 
329
- logger.info(f'Ending node = {_dimstring(end)}')
351
+ for x,y,z in zip(xs, ys, zs):
352
+ start = np.array([x,y,z])
353
+ end = start + port.Vdirection.np*port.height
354
+ port.vintline.append(Line.from_points(start, end, 21))
355
+ logger.trace(f'Port[{port.port_number}] integration line {start} -> {end}.')
330
356
 
331
357
  port.v_integration = True
332
358
 
@@ -569,10 +595,10 @@ class Microwave3D:
569
595
  elif TEM:
570
596
  G1, G2 = self._find_tem_conductors(port, sigtri=cond)
571
597
  cs, dls = self._compute_integration_line(G1,G2)
572
- mode.modetype='TEM'
598
+ mode.modetype = 'TEM'
573
599
  Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
574
600
  voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
575
- mode.Z0 = voltage**2/(2*P)
601
+ mode.Z0 = abs(voltage**2/(2*P))
576
602
  logger.debug(f'Port Z0 = {mode.Z0}')
577
603
 
578
604
  mode.set_power(P*port._qmode(k0)**2)
@@ -1003,7 +1029,8 @@ class Microwave3D:
1003
1029
  if bc.Z0 is None:
1004
1030
  raise SimulationError('Trying to compute the impedance of a boundary condition with no characteristic impedance.')
1005
1031
 
1006
- V = bc.vintline.line_integral(fieldfunction)
1032
+ Voltages = [line.line_integral(fieldfunction) for line in bc.vintline]
1033
+ V = sum(Voltages)/len(Voltages)
1007
1034
 
1008
1035
  if bc.active:
1009
1036
  if bc.voltage is None:
@@ -73,11 +73,11 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
73
73
  """
74
74
  bcs = self.oftype(PEC)
75
75
  for bc in self.oftype(SurfaceImpedance):
76
- if bc.sigma > 1e3:
76
+ if bc.sigma > 10.0:
77
77
  bcs.append(bc)
78
78
 
79
79
  return bcs
80
-
80
+
81
81
  def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort','SurfaceImpedance']) -> FaceSelection:
82
82
  tags = []
83
83
  for bc in self.boundary_conditions:
@@ -545,7 +545,7 @@ class ModalPort(PortBC):
545
545
  self.modes[k0] = new_modes
546
546
  return
547
547
  for k0, modes in self.modes.items():
548
- self.modes[k0] = sorted(modes, key=lambda m: m.energy, reverse=True)
548
+ self.modes[k0] = sorted(modes, key=lambda m: m.beta, reverse=True)
549
549
 
550
550
  def get_mode(self, k0: float, i=None) -> PortMode:
551
551
  """Returns a given mode solution in the form of a PortMode object.
@@ -834,7 +834,7 @@ class LumpedPort(PortBC):
834
834
  # logger.info('Constructing coordinate system from normal port')
835
835
  # self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
836
836
  self.cs = GCS
837
- self.vintline: Line | None = None
837
+ self.vintline: list[Line] = []
838
838
  self.v_integration = True
839
839
 
840
840
  @property
@@ -1031,7 +1031,7 @@ class SurfaceImpedance(RobinBC):
1031
1031
  self.sigma: float = 0.0
1032
1032
 
1033
1033
  if material is not None:
1034
- self.sigma = material.cond
1034
+ self.sigma = material.cond.scalar(1e9)
1035
1035
  self._mur = material.ur
1036
1036
  self._epsr = material.er
1037
1037
 
@@ -1064,7 +1064,7 @@ class SurfaceImpedance(RobinBC):
1064
1064
 
1065
1065
  w0 = k0*C0
1066
1066
  f0 = w0/(2*np.pi)
1067
- sigma = self.sigma.scalar(f0)
1067
+ sigma = self.sigma
1068
1068
  mur = self._material.ur.scalar(f0)
1069
1069
  er = self._material.er.scalar(f0)
1070
1070
 
@@ -145,7 +145,7 @@ def _norm(x, y, z):
145
145
 
146
146
  def _select(obj: GeoObject | Selection) -> Selection:
147
147
  if isinstance(obj, GeoObject):
148
- return obj.select
148
+ return obj.selection
149
149
  return obj
150
150
 
151
151
  def _merge(lst: list[GeoObject | Selection]) -> Selection: