emerge 0.4.7__py3-none-any.whl → 0.4.9__py3-none-any.whl

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 (78) hide show
  1. emerge/__init__.py +14 -14
  2. emerge/_emerge/__init__.py +42 -0
  3. emerge/_emerge/bc.py +197 -0
  4. emerge/_emerge/coord.py +119 -0
  5. emerge/_emerge/cs.py +523 -0
  6. emerge/_emerge/dataset.py +36 -0
  7. emerge/_emerge/elements/__init__.py +19 -0
  8. emerge/_emerge/elements/femdata.py +212 -0
  9. emerge/_emerge/elements/index_interp.py +64 -0
  10. emerge/_emerge/elements/legrange2.py +172 -0
  11. emerge/_emerge/elements/ned2_interp.py +645 -0
  12. emerge/_emerge/elements/nedelec2.py +140 -0
  13. emerge/_emerge/elements/nedleg2.py +217 -0
  14. emerge/_emerge/geo/__init__.py +24 -0
  15. emerge/_emerge/geo/horn.py +107 -0
  16. emerge/_emerge/geo/modeler.py +449 -0
  17. emerge/_emerge/geo/operations.py +254 -0
  18. emerge/_emerge/geo/pcb.py +1244 -0
  19. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  20. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  21. emerge/_emerge/geo/pmlbox.py +204 -0
  22. emerge/_emerge/geo/polybased.py +529 -0
  23. emerge/_emerge/geo/shapes.py +427 -0
  24. emerge/_emerge/geo/step.py +77 -0
  25. emerge/_emerge/geo2d.py +86 -0
  26. emerge/_emerge/geometry.py +510 -0
  27. emerge/_emerge/howto.py +214 -0
  28. emerge/_emerge/logsettings.py +5 -0
  29. emerge/_emerge/material.py +118 -0
  30. emerge/_emerge/mesh3d.py +730 -0
  31. emerge/_emerge/mesher.py +339 -0
  32. emerge/_emerge/mth/common_functions.py +33 -0
  33. emerge/_emerge/mth/integrals.py +71 -0
  34. emerge/_emerge/mth/optimized.py +357 -0
  35. emerge/_emerge/periodic.py +263 -0
  36. emerge/_emerge/physics/__init__.py +0 -0
  37. emerge/_emerge/physics/microwave/__init__.py +1 -0
  38. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  39. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  40. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  41. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  42. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  43. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  44. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  45. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  46. emerge/_emerge/physics/microwave/periodic.py +82 -0
  47. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  48. emerge/_emerge/physics/microwave/sc.py +175 -0
  49. emerge/_emerge/physics/microwave/simjob.py +147 -0
  50. emerge/_emerge/physics/microwave/sparam.py +138 -0
  51. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  52. emerge/_emerge/plot/__init__.py +0 -0
  53. emerge/_emerge/plot/display.py +394 -0
  54. emerge/_emerge/plot/grapher.py +93 -0
  55. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  56. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  57. emerge/_emerge/plot/pyvista/display.py +931 -0
  58. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  59. emerge/_emerge/plot/simple_plots.py +551 -0
  60. emerge/_emerge/plot.py +225 -0
  61. emerge/_emerge/projects/__init__.py +0 -0
  62. emerge/_emerge/projects/_gen_base.txt +32 -0
  63. emerge/_emerge/projects/_load_base.txt +24 -0
  64. emerge/_emerge/projects/generate_project.py +40 -0
  65. emerge/_emerge/selection.py +596 -0
  66. emerge/_emerge/simmodel.py +444 -0
  67. emerge/_emerge/simulation_data.py +411 -0
  68. emerge/_emerge/solver.py +993 -0
  69. emerge/_emerge/system.py +54 -0
  70. emerge/cli.py +19 -0
  71. emerge/lib.py +1 -1
  72. emerge/plot.py +1 -1
  73. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/METADATA +7 -6
  74. emerge-0.4.9.dist-info/RECORD +78 -0
  75. emerge-0.4.9.dist-info/entry_points.txt +2 -0
  76. emerge-0.4.7.dist-info/RECORD +0 -9
  77. emerge-0.4.7.dist-info/entry_points.txt +0 -2
  78. {emerge-0.4.7.dist-info → emerge-0.4.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,28 @@
1
+ import numpy as np
2
+ from ...material import Material
3
+
4
+ PI = np.pi
5
+ TAU = 2*PI
6
+ n0 = 376.73
7
+
8
+ def microstrip_z0(W: float, th: float, er: float):
9
+ u = W/th
10
+ fu = 6 + (TAU - 6)*np.exp(-((30.666/u)**(0.7528)))
11
+ Z0 = n0/(TAU*np.sqrt(er))* np.log(fu/u + np.sqrt(1+(2/u)**2))
12
+ return Z0
13
+
14
+
15
+ class PCBCalculator:
16
+
17
+ def __init__(self, thickness: float, layers: np.array, material: Material, unit: float):
18
+ self.th = thickness
19
+ self.layers = layers
20
+ self.mat = material
21
+ self.unit = unit
22
+
23
+
24
+ def z0(self, Z0: float, layer: int = -1, ground_layer: int = 0):
25
+ th = abs(self.layers[layer] - self.layers[ground_layer])*self.unit
26
+ ws = np.geomspace(1e-6,1e-1,101)
27
+ Z0ms = microstrip_z0(ws, th, self.mat.er)
28
+ return np.interp(Z0, Z0ms[::-1], ws[::-1])/self.unit
@@ -0,0 +1,79 @@
1
+ from typing import Any
2
+ from dataclasses import dataclass
3
+ import math
4
+ import re
5
+
6
+ def rotation_angle(a: tuple[float, float], b: tuple[float, float]) -> float:
7
+ """
8
+ Returns the signed angle in degrees you must rotate vector a
9
+ to align with vector b. Positive = CCW, Negative = CW.
10
+
11
+ a, b: (vx, vy) tuples in the plane.
12
+ """
13
+ ax, ay = a
14
+ bx, by = b
15
+
16
+ # compute 2D cross product (scalar) and dot product
17
+ cross = ax * by - ay * bx
18
+ dot = ax * bx + ay * by
19
+
20
+ # atan2(cross, dot) gives angle between -π and π
21
+ angle_rad = math.atan2(cross, dot)
22
+ angle_deg = math.degrees(angle_rad)
23
+ return -angle_deg
24
+
25
+ @dataclass
26
+ class Instruction:
27
+ instr: str
28
+ args: tuple[int]
29
+ kwargs: dict[str,float] = None
30
+
31
+ def __post_init__(self):
32
+ if self.kwargs is None:
33
+ self.kwargs = {}
34
+
35
+ symbols = ['=','>','v','^','<','@','/','\\','T']
36
+ char_class = ''.join(re.escape(c) for c in symbols)
37
+
38
+ pattern = re.compile(rf'([{char_class}])([\d\,\.\-]+)')
39
+
40
+ def parse_macro(pathstring: str, width: int, direction: tuple[float, float]) -> list[Instruction]:
41
+ instructions = pattern.findall(pathstring.replace(' ',''))
42
+
43
+ oi = []
44
+ for com, val in instructions:
45
+ if ',' in val:
46
+ ival, width = [float(x) for x in val.split(',')]
47
+ else:
48
+ ival = float(val)
49
+ if com == '=':
50
+ oi.append(Instruction('straight',(ival,),{'width': width}))
51
+ elif com == '>':
52
+ oi.append(Instruction('turn',(rotation_angle(direction,(1,0)),) ))
53
+ oi.append(Instruction('straight',(ival,),{'width': width}))
54
+ direction = (1,0)
55
+ elif com == '<':
56
+ oi.append(Instruction('turn',(rotation_angle(direction,(-1,0)),) ))
57
+ oi.append(Instruction('straight',(ival,),{'width': width}))
58
+ direction = (-1,0)
59
+ elif com == 'v':
60
+ oi.append(Instruction('turn',(rotation_angle(direction,(0,-1)),) ))
61
+ oi.append(Instruction('straight',(ival,),{'width': width}))
62
+ direction = (0,-1)
63
+ elif com == '^':
64
+ oi.append(Instruction('turn',(rotation_angle(direction,(0,1)),) ))
65
+ oi.append(Instruction('straight',(ival,),{'width': width}))
66
+ direction = (0,1)
67
+ elif com == '\\':
68
+ oi.append(Instruction('turn',(90,) ))
69
+ oi.append(Instruction('straight',(ival,),{'width': width}))
70
+ direction = (direction[1],-direction[0])
71
+ elif com == '/':
72
+ oi.append(Instruction('turn',(-90,) ))
73
+ oi.append(Instruction('straight',(ival,),{'width': width}))
74
+ direction = (-direction[1],direction[0])
75
+ elif com == 'T':
76
+ oi.append(Instruction('taper',(ival,),{'width': width}))
77
+ elif com == '@':
78
+ oi.append(Instruction('turn',(ival,),{'width': width}))
79
+ return oi
@@ -0,0 +1,204 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ from ..geometry import GeoVolume
19
+ from .shapes import Box, Alignment, Plate
20
+ from ..material import Material, AIR
21
+ import numpy as np
22
+ from functools import partial
23
+
24
+
25
+ def _add_pml_layer(center: tuple[float, float, float],
26
+ dims: tuple[float, float, float],
27
+ direction: tuple[float, float, float],
28
+ thickness: float,
29
+ Nlayers: int,
30
+ N_mesh_layers: int,
31
+ exponent: float,
32
+ deltamax: float,
33
+ material: Material) -> GeoVolume:
34
+ px, py, pz = center
35
+ W,D,H = dims
36
+ dx, dy, dz = direction
37
+
38
+ pml_block_size = [W, D, H]
39
+ new_center = [px, py, pz]
40
+
41
+ sxf = lambda x, y, z: np.ones_like(x, dtype=np.complex128)
42
+ syf = lambda x, y, z: np.ones_like(x, dtype=np.complex128)
43
+ szf = lambda x, y, z: np.ones_like(x, dtype=np.complex128)
44
+
45
+ p0x = px + dx*(W/2+thickness/2)
46
+ p0y = py + dy*(D/2+thickness/2)
47
+ p0z = pz + dz*(H/2+thickness/2)
48
+
49
+ tW, tD, tH = W, D, H
50
+
51
+ if dx != 0:
52
+ tW = thickness
53
+ pml_block_size[0] = thickness
54
+ new_center[0] = new_center[0] + (W/2 + thickness/2) * dx
55
+ def sxf(x, y, z):
56
+ return 1 - 1j * (dx*(x-px-(dx*W/2)) / thickness) ** exponent * deltamax
57
+ if dy != 0:
58
+ tD = thickness
59
+ pml_block_size[1] = thickness
60
+ new_center[1] = new_center[1] + (D/2 + thickness/2) * dy
61
+ def syf(x, y, z):
62
+ return 1 - 1j * (dy*(y-py-(dy*D/2)) / thickness) ** exponent * deltamax
63
+ if dz != 0:
64
+ tH = thickness
65
+ pml_block_size[2] = thickness
66
+ new_center[2] = new_center[2] + (H/2 + thickness/2) * dz
67
+ def szf(x, y, z):
68
+ return 1 - 1j * (dz*(z-pz-(dz*H/2)) / thickness) ** exponent * deltamax
69
+
70
+ def ermat(x, y, z):
71
+ ers = np.zeros((3,3,x.shape[0]), dtype=np.complex128)
72
+ ers[0,0,:] = material.er * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
73
+ ers[1,1,:] = material.er * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
74
+ ers[2,2,:] = material.er * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
75
+ return ers
76
+ def urmat(x, y, z):
77
+ urs = np.zeros((3,3,x.shape[0]), dtype=np.complex128)
78
+ urs[0,0,:] = material.ur * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
79
+ urs[1,1,:] = material.ur * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
80
+ urs[2,2,:] = material.ur * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
81
+ return urs
82
+
83
+ pml_box = Box(*pml_block_size, new_center, alignment=Alignment.CENTER)
84
+ pml_box._unset_constraints = True
85
+
86
+ planes = []
87
+ thl = thickness / Nlayers
88
+ planes = []
89
+
90
+ if dx != 0:
91
+ ax1 = np.array([0, tD, 0])
92
+ ax2 = np.array([0, 0, tH])
93
+ for n in range(Nlayers-1):
94
+ plate = Plate(np.array([p0x-dx*thickness/2 + dx*(n+1)*thl, p0y-tD/2, p0z-tH/2]), ax1, ax2)
95
+ planes.append(plate)
96
+ if dy != 0:
97
+ ax1 = np.array([tW, 0, 0])
98
+ ax2 = np.array([0, 0, tH])
99
+ for n in range(Nlayers-1):
100
+ plate = Plate(np.array([p0x-tW/2, p0y-dy*thickness/2 + dy*(n+1)*thl, p0z-tH/2]), ax1, ax2)
101
+ planes.append(plate)
102
+ if dz != 0:
103
+ ax1 = np.array([tW, 0, 0])
104
+ ax2 = np.array([0, tD, 0])
105
+ for n in range(Nlayers-1):
106
+ plate = Plate(np.array([p0x-tW/2, p0y-tD/2, p0z-dz*thickness/2 + dz*(n+1)*thl]), ax1, ax2)
107
+ planes.append(plate)
108
+
109
+ pml_box.material = Material(_neff=np.sqrt(material.er*material.ur), _fer=ermat, _fur=urmat)
110
+ pml_box.max_meshsize = thickness/N_mesh_layers
111
+ pml_box._embeddings = planes
112
+
113
+ return pml_box
114
+
115
+
116
+ def pmlbox(width: float,
117
+ depth: float,
118
+ height: float,
119
+ position: tuple = (0, 0, 0),
120
+ alignment: Alignment = Alignment.CORNER,
121
+ material: Material = AIR,
122
+ thickness: float = 0.1,
123
+ Nlayers: int = 1,
124
+ N_mesh_layers: int = 8,
125
+ exponent: float = 1.5,
126
+ deltamax: float = 8.0,
127
+ top: bool = False,
128
+ bottom: bool = False,
129
+ left: bool = False,
130
+ right: bool = False,
131
+ front: bool = False,
132
+ back: bool = False) -> list[GeoVolume]:
133
+ """Generate a block of uniform material (default air) with optional PML boxes around it
134
+
135
+ This constructor uses coordinate-dependent material properties so only 1 layer is needed for the PML box. As a standin,
136
+ the mesh discretization will be based on the thickness/number of mesh layers. If the PML layer is over-meshsed, try decreasing
137
+ the number of mesh layers.
138
+
139
+
140
+ Args:
141
+ width (float): The width of the box
142
+ depth (float): The depth of the box
143
+ height (float): The height of the box
144
+ position (tuple, optional): The placmeent of the box. Defaults to (0, 0, 0).
145
+ alignment (Alignment, optional): Which point of the box is placed at the given coordinate. Defaults to Alignment.CORNER.
146
+ material (Material, optional): The material of the box. Defaults to AIR.
147
+ thickness (float, optional): The thickness of the PML Layer. Defaults to 0.1.
148
+ Nlayers (int, optional): The number of PML layers (1 is reccomended). Defaults to 1.
149
+ N_mesh_layers (int, optional): The number of mesh layers. Sets the discretization size accordingly. Defaults to 8
150
+ exponent (float, optional): The PML gradient growth function. Defaults to 1.5.
151
+ deltamax (float, optional): A PML matching coefficient. Defaults to 8.0.
152
+ top (bool, optional): Add a top PML layer. Defaults to True.
153
+ bottom (bool, optional): Add a bottom PML layer. Defaults to False.
154
+ left (bool, optional): Add a left PML layer. Defaults to False.
155
+ right (bool, optional): Add a right PML layer. Defaults to False.
156
+ front (bool, optional): Add a front PML layer. Defaults to False.
157
+ back (bool, optional): Add a back PML layer. Defaults to False.
158
+
159
+ Returns:
160
+ list[GeoVolume]: A list of objects [main box, *pml boxes]
161
+ """
162
+ px, py, pz = position
163
+ if alignment == Alignment.CORNER:
164
+ px = px + width / 2
165
+ py = py + depth / 2
166
+ pz = pz + height / 2
167
+
168
+ position = (px, py, pz)
169
+ main_box = Box(width, depth, height, position, alignment=Alignment.CENTER)
170
+ main_box.material = material
171
+
172
+ main_box._unset_constraints = True
173
+ other_boxes = []
174
+
175
+ addpml = partial(_add_pml_layer, center=(px, py, pz), dims=(width, depth, height),
176
+ thickness=thickness, Nlayers=Nlayers, N_mesh_layers=N_mesh_layers,
177
+ exponent=exponent, deltamax=deltamax, material=material)
178
+
179
+ xs = [0,]
180
+ ys = [0,]
181
+ zs = [0,]
182
+ if top:
183
+ zs.append(1)
184
+ if bottom:
185
+ zs.append(-1)
186
+ if left:
187
+ xs.append(-1)
188
+ if right:
189
+ xs.append(1)
190
+ if front:
191
+ ys.append(-1)
192
+ if back:
193
+ ys.append(1)
194
+ for x in xs:
195
+ for y in ys:
196
+ for z in zs:
197
+ if x == 0 and y == 0 and z == 0:
198
+ continue
199
+ box = addpml(direction=(x, y, z))
200
+
201
+ other_boxes.append(box
202
+ )
203
+
204
+ return [main_box] + other_boxes