emerge 1.0.7__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.
- {emerge-1.0.7 → emerge-1.1.0}/.bumpversion.toml +8 -2
- {emerge-1.0.7 → emerge-1.1.0}/PKG-INFO +7 -3
- {emerge-1.0.7 → emerge-1.1.0}/emerge/__init__.py +14 -3
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/pcb.py +132 -59
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/shapes.py +11 -7
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geometry.py +23 -8
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/logsettings.py +26 -2
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mesh3d.py +5 -5
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mesher.py +40 -8
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/adaptive_mesh.py +113 -22
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_3d.py +131 -81
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_bc.py +157 -8
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/display.py +26 -16
- emerge-1.1.0/emerge/_emerge/settings.py +130 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/simmodel.py +220 -148
- emerge-1.1.0/emerge/_emerge/simstate.py +106 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/simulation_data.py +11 -23
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/solver.py +1 -1
- emerge-1.1.0/examples/demo0_parallel_plate.py +76 -0
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo10_sgh.py +2 -2
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo11_lumped_element_filter.py +3 -3
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo12_mode_alignment.py +1 -1
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo13_helix_antenna.py +2 -3
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo14_boundary_selection.py +1 -1
- emerge-1.1.0/examples/demo15_strip_slotline_transition.py +158 -0
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo1_stepped_imp_filter.py +14 -26
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo2_combline_filter.py +7 -8
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo3_coupled_line_filter.py +10 -7
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo4_patch_antenna.py +6 -6
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo5_revolve.py +1 -1
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo6_striplines_with_vias.py +1 -1
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo7_periodic_cells.py +1 -1
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo8_waveguide_bpf_synthesis.py +12 -22
- {emerge-1.0.7 → emerge-1.1.0}/examples/demo9_dielectric_resonator.py +1 -1
- {emerge-1.0.7 → emerge-1.1.0}/pyproject.toml +8 -3
- {emerge-1.0.7 → emerge-1.1.0}/uv.lock +61 -32
- emerge-1.0.7/emerge/_emerge/settings.py +0 -12
- {emerge-1.0.7 → emerge-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/.gitignore +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/.nova/Configuration.json +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/.python-version +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/LICENSE +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/README.md +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/THIRD_PARTY_LICENSES.md +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/UMFPACK_Install_windows.md +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/__main__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/bc.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/cacherun.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/const.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/coord.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/cs.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/dataset.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/femdata.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/index_interp.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/ned2_interp.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/nedelec2.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/elements/nedleg2.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/emerge_update.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/horn.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/modeler.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/operations.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/dxf.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/pmlbox.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/polybased.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo/step.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/geo2d.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/howto.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/material.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mth/_cache_check.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mth/common_functions.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mth/integrals.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mth/optimized.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/mth/pairing.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/periodic.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/robin_abc_order2.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/microwave_data.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/periodic.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/sc.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/simjob.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/sparam.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/display.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/cmap_maker.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/pyvista/display_settings.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot/simple_plots.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/plot.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/projects/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/projects/_gen_base.txt +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/projects/_load_base.txt +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/projects/generate_project.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/selection.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/_emerge/system.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/beta/dxf.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/cli.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/ext.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/lib.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/materials/__init__.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/materials/isola.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/materials/rogers.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/plot.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/emerge/pyvista.py +0 -0
- {emerge-1.0.7 → emerge-1.1.0}/src/__init__.py +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[tool.bumpversion]
|
|
2
|
-
current_version = "1.0
|
|
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
|
|
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
|
|
@@ -18,8 +18,12 @@ Requires-Dist: pyvista>=0.45.2
|
|
|
18
18
|
Requires-Dist: scipy>=1.14.0
|
|
19
19
|
Provides-Extra: cudss
|
|
20
20
|
Requires-Dist: cupy-cuda12x; extra == 'cudss'
|
|
21
|
-
Requires-Dist: nvidia-cudss-cu12; extra == 'cudss'
|
|
22
|
-
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'
|
|
23
27
|
Provides-Extra: dxf
|
|
24
28
|
Requires-Dist: ezdxf; extra == 'dxf'
|
|
25
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
|
-
|
|
19
|
+
############################################################
|
|
20
|
+
# WARNING SUPPRESSION #
|
|
21
|
+
############################################################
|
|
20
22
|
|
|
21
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
1055
|
+
list[GeoPolygon]: _description_
|
|
1054
1056
|
"""
|
|
1055
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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=
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
478
|
-
|
|
479
|
-
Returns:
|
|
480
|
-
FaceSelection: _description_
|
|
483
|
+
"""The outside boundary of the half sphere.
|
|
481
484
|
"""
|
|
482
|
-
return self.
|
|
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.
|
|
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-
|
|
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
|
-
|
|
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()
|
|
@@ -25,6 +25,7 @@ from .geometry import GeoVolume
|
|
|
25
25
|
from loguru import logger
|
|
26
26
|
from .bc import Periodic
|
|
27
27
|
from .material import Material
|
|
28
|
+
from .logsettings import DEBUG_COLLECTOR
|
|
28
29
|
|
|
29
30
|
_MISSING_ID: int = -1234
|
|
30
31
|
|
|
@@ -78,9 +79,7 @@ class Mesh3D(Mesh):
|
|
|
78
79
|
are mapped to mesh elements is managed by the FEMBasis class.
|
|
79
80
|
|
|
80
81
|
"""
|
|
81
|
-
def __init__(self
|
|
82
|
-
|
|
83
|
-
self.geometry: Mesher = mesher
|
|
82
|
+
def __init__(self):
|
|
84
83
|
|
|
85
84
|
# All spatial objects
|
|
86
85
|
self.nodes: np.ndarray = np.array([])
|
|
@@ -186,6 +185,8 @@ class Mesh3D(Mesh):
|
|
|
186
185
|
i11, i21, i31 = tuple(sorted((int(i1), int(i2), int(i3))))
|
|
187
186
|
output = self.inv_tris.get(tuple(sorted((int(i1), int(i2), int(i3)))), None)
|
|
188
187
|
if output is None:
|
|
188
|
+
DEBUG_COLLECTOR.add_report(f'Mesh3D: The program is crashed due to a non existing triangle {i11}, {i21}, {i31}. This occurs often if surfaces stick out of the 3D domain.\n' +
|
|
189
|
+
'Only 3D volumes can be meshed. Parts or entire simulations that are two dimensional will cause this problem.')
|
|
189
190
|
raise ValueError(f'There is no triangle with indices {i11}, {i21}, {i31}')
|
|
190
191
|
return output
|
|
191
192
|
|
|
@@ -652,8 +653,6 @@ class Mesh3D(Mesh):
|
|
|
652
653
|
* Only the connectivity of the supplied edges is considered.
|
|
653
654
|
In particular, vertices that never occur in `edges` do **not** create extra
|
|
654
655
|
components.
|
|
655
|
-
* Runtime is *O*(N + V), with N = number of edges, V = number of
|
|
656
|
-
distinct vertices. No external libraries are needed.
|
|
657
656
|
"""
|
|
658
657
|
edges = self.edges[:,edge_ids]
|
|
659
658
|
if edges.ndim != 2 or edges.shape[0] != 2:
|
|
@@ -692,6 +691,7 @@ class Mesh3D(Mesh):
|
|
|
692
691
|
group += list(new_edges)
|
|
693
692
|
ungrouped.difference_update(new_edges)
|
|
694
693
|
|
|
694
|
+
groups = sorted(groups, key = lambda x: sum(self.edge_lengths[np.array(x)]))
|
|
695
695
|
return groups
|
|
696
696
|
|
|
697
697
|
def boundary_surface(self,
|