emerge 0.4.6__py3-none-any.whl → 0.4.8__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.
- emerge/__init__.py +54 -0
- emerge/__main__.py +5 -0
- emerge/_emerge/__init__.py +42 -0
- emerge/_emerge/bc.py +197 -0
- emerge/_emerge/coord.py +119 -0
- emerge/_emerge/cs.py +523 -0
- emerge/_emerge/dataset.py +36 -0
- emerge/_emerge/elements/__init__.py +19 -0
- emerge/_emerge/elements/femdata.py +212 -0
- emerge/_emerge/elements/index_interp.py +64 -0
- emerge/_emerge/elements/legrange2.py +172 -0
- emerge/_emerge/elements/ned2_interp.py +645 -0
- emerge/_emerge/elements/nedelec2.py +140 -0
- emerge/_emerge/elements/nedleg2.py +217 -0
- emerge/_emerge/geo/__init__.py +24 -0
- emerge/_emerge/geo/horn.py +107 -0
- emerge/_emerge/geo/modeler.py +449 -0
- emerge/_emerge/geo/operations.py +254 -0
- emerge/_emerge/geo/pcb.py +1244 -0
- emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge/_emerge/geo/pmlbox.py +204 -0
- emerge/_emerge/geo/polybased.py +529 -0
- emerge/_emerge/geo/shapes.py +427 -0
- emerge/_emerge/geo/step.py +77 -0
- emerge/_emerge/geo2d.py +86 -0
- emerge/_emerge/geometry.py +510 -0
- emerge/_emerge/howto.py +214 -0
- emerge/_emerge/logsettings.py +5 -0
- emerge/_emerge/material.py +118 -0
- emerge/_emerge/mesh3d.py +730 -0
- emerge/_emerge/mesher.py +339 -0
- emerge/_emerge/mth/common_functions.py +33 -0
- emerge/_emerge/mth/integrals.py +71 -0
- emerge/_emerge/mth/optimized.py +357 -0
- emerge/_emerge/periodic.py +263 -0
- emerge/_emerge/physics/__init__.py +0 -0
- emerge/_emerge/physics/microwave/__init__.py +1 -0
- emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge/_emerge/physics/microwave/periodic.py +82 -0
- emerge/_emerge/physics/microwave/port_functions.py +53 -0
- emerge/_emerge/physics/microwave/sc.py +175 -0
- emerge/_emerge/physics/microwave/simjob.py +147 -0
- emerge/_emerge/physics/microwave/sparam.py +138 -0
- emerge/_emerge/physics/microwave/touchstone.py +140 -0
- emerge/_emerge/plot/__init__.py +0 -0
- emerge/_emerge/plot/display.py +394 -0
- emerge/_emerge/plot/grapher.py +93 -0
- emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge/_emerge/plot/pyvista/__init__.py +1 -0
- emerge/_emerge/plot/pyvista/display.py +931 -0
- emerge/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge/_emerge/plot/simple_plots.py +551 -0
- emerge/_emerge/plot.py +225 -0
- emerge/_emerge/projects/__init__.py +0 -0
- emerge/_emerge/projects/_gen_base.txt +32 -0
- emerge/_emerge/projects/_load_base.txt +24 -0
- emerge/_emerge/projects/generate_project.py +40 -0
- emerge/_emerge/selection.py +596 -0
- emerge/_emerge/simmodel.py +444 -0
- emerge/_emerge/simulation_data.py +411 -0
- emerge/_emerge/solver.py +993 -0
- emerge/_emerge/system.py +54 -0
- emerge/cli.py +19 -0
- emerge/lib.py +57 -0
- emerge/plot.py +1 -0
- emerge/pyvista.py +1 -0
- {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
- emerge-0.4.8.dist-info/RECORD +78 -0
- emerge-0.4.8.dist-info/entry_points.txt +2 -0
- emerge-0.4.6.dist-info/RECORD +0 -4
- emerge-0.4.6.dist-info/entry_points.txt +0 -2
- {emerge-0.4.6.dist-info → emerge-0.4.8.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
|