emerge 0.4.7__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 +14 -14
- 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 +1 -1
- emerge/plot.py +1 -1
- {emerge-0.4.7.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.7.dist-info/RECORD +0 -9
- emerge-0.4.7.dist-info/entry_points.txt +0 -2
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
emerge/_emerge/howto.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
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
|
+
|
|
19
|
+
class _HowtoClass:
|
|
20
|
+
"""The help class does nothing but offer docstring for
|
|
21
|
+
function that have names related to potential activities.
|
|
22
|
+
"""
|
|
23
|
+
def __init__(self):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def start(self):
|
|
27
|
+
"""
|
|
28
|
+
To start a simulation simply create a model object through:
|
|
29
|
+
|
|
30
|
+
>>> model = emerge.Simulation3D('MyProjectName')
|
|
31
|
+
|
|
32
|
+
Optionally, you can use a context manager for a more explicit handling of exiting the GMSH api and storing data after simulations.
|
|
33
|
+
|
|
34
|
+
>>> with emerge.Simulation3D('MyProjectName') as model:
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def make_geometry(self):
|
|
40
|
+
"""
|
|
41
|
+
To make geometries in EMerge, you can use the various Geometry options in the .geo module.
|
|
42
|
+
For example
|
|
43
|
+
|
|
44
|
+
>>> box = emerge.geo.Box(...)
|
|
45
|
+
>>> sphere = emerge.geo.Sphere(...)
|
|
46
|
+
>>> pcb_layouter = emerge.geo.PCBLayout(...)
|
|
47
|
+
>>> plate = emerge.geo.Plate(...)
|
|
48
|
+
>>> cyl = emerge.geo.Cyllinder(...)
|
|
49
|
+
|
|
50
|
+
After making geometries, you should pass all of them to
|
|
51
|
+
the simulation object
|
|
52
|
+
>>> model.define_geometry(box, sphere,...)
|
|
53
|
+
|
|
54
|
+
You can also directly store geometries into the model class by treating
|
|
55
|
+
it as a list (using __getitem__ and __setitem__)
|
|
56
|
+
>>> model['box'] = emerge.geo.Box(...)
|
|
57
|
+
|
|
58
|
+
In this case, all geometries will be automatically included. You shoul still call:
|
|
59
|
+
>>> model.define_geometry()
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def select_face(self):
|
|
66
|
+
"""
|
|
67
|
+
Faces can be selected in multiple ways. First, many native geometries have face definitions:
|
|
68
|
+
>>> front_face = box.face('front')
|
|
69
|
+
|
|
70
|
+
The naming convention is left(-X), right(+X), front(-Y), back(+Y), bottom(-Z), top(+Z)
|
|
71
|
+
All outside faces can be selected using
|
|
72
|
+
>>> outside = object.outside()
|
|
73
|
+
|
|
74
|
+
If objects are the results from operations, you can access the faces from the
|
|
75
|
+
source objects using the optional tool argument
|
|
76
|
+
>>> cutout = emerge.geo.subtract(sphere,box)
|
|
77
|
+
>>> face = cutout.face('front', tool=box)
|
|
78
|
+
|
|
79
|
+
Exclusions or specific isolations can be added with optional arguments.
|
|
80
|
+
There is also a select object in your Simulation3D class that has various convenient selection options
|
|
81
|
+
>>> faces = model.select.face.inlayer()
|
|
82
|
+
>>> faces = model.select.inplane()
|
|
83
|
+
>>> faces = model.select.face.near(x,y,z)
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
def boundary_conditions(self):
|
|
89
|
+
"""
|
|
90
|
+
Boundary conditions can be added by first creating them and
|
|
91
|
+
then passing them to the physics object.
|
|
92
|
+
>>> portbc = emerge.bc.ModalPort(...)
|
|
93
|
+
>>> abc = emerge.bc.AbsorbingBoundary(...)
|
|
94
|
+
>>> pec = emerge.bc.PEC(...)
|
|
95
|
+
|
|
96
|
+
Then you should pass them to the physics object with comma separation
|
|
97
|
+
and/or list comprehension (*bcs)
|
|
98
|
+
>>> model.mw.assign(bc1, bc2, bc3, *bcs)
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
def run_frequency_domain(self):
|
|
103
|
+
"""
|
|
104
|
+
You can run a frequency domain study by simply calling:\
|
|
105
|
+
|
|
106
|
+
>>> results = model.mw.frequency_domain(...)
|
|
107
|
+
|
|
108
|
+
You can distribute your frequency sweep across multiple threads using
|
|
109
|
+
|
|
110
|
+
>>> results = model.mw.frequency_domain(parallel=True, njobs=3)
|
|
111
|
+
|
|
112
|
+
The frequency domain study will return an MWSimData object that contains all data.
|
|
113
|
+
"""
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
def access_data(self):
|
|
117
|
+
"""
|
|
118
|
+
Data from a frequency domain study is contained in the MWSimData object.
|
|
119
|
+
Each simulation iteration is a separate MWDataSet object with all relevant parameters included.
|
|
120
|
+
|
|
121
|
+
You can select an individual dataset based on the iteration number using:
|
|
122
|
+
>>> dataset = results.item(3)
|
|
123
|
+
|
|
124
|
+
This will select the 4th result. You can also select one by a specific value using
|
|
125
|
+
>>> dataset = results(freq=3e9)
|
|
126
|
+
|
|
127
|
+
The numbers must be exact. You can also approximately select a value using:
|
|
128
|
+
>>> dataset = results.find(freq=3.9)
|
|
129
|
+
|
|
130
|
+
Datasets contain various parameters such as the simulation frequency and associated k0:
|
|
131
|
+
>>> k0 = dataset.k0
|
|
132
|
+
>>> S11 = dataset.S(1,1)
|
|
133
|
+
|
|
134
|
+
You can select all results along a specific axis using:
|
|
135
|
+
>>> freq, S11 = results.ax('freq').S(1,1)
|
|
136
|
+
|
|
137
|
+
If you ran a parameter sweep, this will select whatever value it finds.
|
|
138
|
+
You can also select across multiple dimensions
|
|
139
|
+
>>> freq, param, S11 = results.ax('freq','param').S(1,1)
|
|
140
|
+
|
|
141
|
+
A dataset also contains all spatial data. You can probe the E-field or H-field using
|
|
142
|
+
the interpolate method. This will compute the E and H fields and store them into the
|
|
143
|
+
dataset after which they can be accessed
|
|
144
|
+
>>> Ex, Ey, Ez = dataset(freq=1e9).interpolate(xs, ys, zs).E
|
|
145
|
+
>>> Ex = dataset(freq=1e9).interpolate(xs, ys, zs).Ex
|
|
146
|
+
|
|
147
|
+
You can automate the generation of sample coordinates and field values for
|
|
148
|
+
specific plot instructions
|
|
149
|
+
>>> X, Y, Z, Eyreal = dataset(freq=1e9).cutplane(ds=0.002, z=0.005).scalar('Ey','real')
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
def save_and_load(self):
|
|
155
|
+
"""
|
|
156
|
+
You can save your project data by setting save_file to True:
|
|
157
|
+
>>> model = emerge.Simulation3D(..., save_file=True)
|
|
158
|
+
|
|
159
|
+
Whenever you want, you can save all data by calling the .save() method
|
|
160
|
+
|
|
161
|
+
>>> model.save()
|
|
162
|
+
|
|
163
|
+
If you run your simulation in a context manager, the data will be saved
|
|
164
|
+
automatically when the context is done. By default, if your Python script crashes
|
|
165
|
+
or ends, the data will also automatically be saved. Data is saved in a folder.
|
|
166
|
+
|
|
167
|
+
You can load the data from a simulation using:
|
|
168
|
+
|
|
169
|
+
>>> model = emerge.Simulation3D(..., load_file=True)
|
|
170
|
+
|
|
171
|
+
The data from a simulation can be found in:
|
|
172
|
+
|
|
173
|
+
>>> results = model.data
|
|
174
|
+
>>> results = model.mw.freq_data # the same
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
def parameter_sweep(self):
|
|
181
|
+
"""To run a parameter sweep, you can simply use the
|
|
182
|
+
parameter_sweep iterator method of your model:
|
|
183
|
+
|
|
184
|
+
>>>
|
|
185
|
+
param1 = np.linspace(...)
|
|
186
|
+
param2 = np.linspace(...)
|
|
187
|
+
for p1, p2 in model.parameter_sweep(..., param_A=param1, param_B=param2):
|
|
188
|
+
# run simulation
|
|
189
|
+
|
|
190
|
+
The parameters will be automatically included in all simulation runs.
|
|
191
|
+
The name will be the same as the keyword argument in the parameter sweep.
|
|
192
|
+
You can extract the S-parameters for example using
|
|
193
|
+
|
|
194
|
+
>>> freq, params, S11 = result.ax('freq','param_A').S(1,1)
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def compute_farfield(self):
|
|
198
|
+
"""
|
|
199
|
+
A farfield can be computed in any simulation but it only really
|
|
200
|
+
represents something if the model has an absorbing boundary or PML.
|
|
201
|
+
To compute the farfield on a single arc we will use the farfield_2d
|
|
202
|
+
method of the MWDataSet class
|
|
203
|
+
|
|
204
|
+
>>> theta, E, H = data.find(freq=1e9).farfield_2d(refdir, planedir, faces)
|
|
205
|
+
|
|
206
|
+
The first argument should be the reference direction (angle = 0 degrees)
|
|
207
|
+
The second argument is the plane normal. The arc is drawn as a semi-circle (or full)
|
|
208
|
+
where the angle 0deg is in the reference direction.
|
|
209
|
+
The faces argument should be a selection of faces to integrate the farfield over.
|
|
210
|
+
|
|
211
|
+
Currently, EMerge only allows for Robin boundary conditions (ABC) on flat surfaces.
|
|
212
|
+
|
|
213
|
+
"""
|
|
214
|
+
pass
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
import numpy as np
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Material:
|
|
23
|
+
"""The Material class generalizes a material in the EMerge FEM environment.
|
|
24
|
+
|
|
25
|
+
If a scalar value is provided for the relative permittivity or the relative permeability
|
|
26
|
+
it will be used as multiplication entries for the material property diadic as identity matrix.
|
|
27
|
+
|
|
28
|
+
Additionally, a function may be provided that computes a coordinate dependent material property
|
|
29
|
+
for _fer. For example: Material(_fer = lambda x,y,z: ...).
|
|
30
|
+
The x,y and z coordinates are provided as a (N,) np.ndarray. The return array must be of shape (3,3,N)!
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
er: float = 1
|
|
34
|
+
ur: float = 1
|
|
35
|
+
tand: float = 0
|
|
36
|
+
cond: float = 0
|
|
37
|
+
_neff: float = None
|
|
38
|
+
_fer: callable = None
|
|
39
|
+
_fur: callable = None
|
|
40
|
+
color: str = "#BEBEBE"
|
|
41
|
+
_color_rgb: tuple[int,int,int] = None
|
|
42
|
+
opacity: float = 1.0
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
hex_str = self.color.lstrip('#')
|
|
46
|
+
self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def color_rgb(self) -> tuple[float,float,float]:
|
|
50
|
+
return self._color_rgb
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def ermat(self) -> np.ndarray:
|
|
54
|
+
if isinstance(self.er, (float, complex, int, np.float64, np.complex128)):
|
|
55
|
+
return self.er*(1-1j*self.tand)*np.eye(3)
|
|
56
|
+
else:
|
|
57
|
+
return self.er*(1-1j*self.tand)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def urmat(self) -> np.ndarray:
|
|
61
|
+
if isinstance(self.ur, (float, complex, int, np.float64, np.complex128)):
|
|
62
|
+
return self.ur*np.eye(3)
|
|
63
|
+
else:
|
|
64
|
+
return self.ur
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def neff(self) -> complex:
|
|
68
|
+
if self._neff is not None:
|
|
69
|
+
return self._neff
|
|
70
|
+
er = self.ermat[0,0]
|
|
71
|
+
ur = self.urmat[0,0]
|
|
72
|
+
|
|
73
|
+
return np.abs(np.sqrt(er*(1-1j*self.tand)*ur))
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def fer2d(self) -> callable:
|
|
77
|
+
if self._fer is None:
|
|
78
|
+
return lambda x,y: self.er*(1-1j*self.tand)*np.ones_like(x)
|
|
79
|
+
else:
|
|
80
|
+
return self._fer
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def fur2d(self) -> callable:
|
|
84
|
+
if self._fur is None:
|
|
85
|
+
|
|
86
|
+
return lambda x,y: self.ur*np.ones_like(x)
|
|
87
|
+
else:
|
|
88
|
+
return self._fur
|
|
89
|
+
@property
|
|
90
|
+
def fer3d(self) -> callable:
|
|
91
|
+
if self._fer is None:
|
|
92
|
+
return lambda x,y,z: self.er*(1-1j*self.tand)*np.ones_like(x)
|
|
93
|
+
else:
|
|
94
|
+
return self._fer
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def fur3d(self) -> callable:
|
|
98
|
+
if self._fur is None:
|
|
99
|
+
return lambda x,y,z: self.ur*np.ones_like(x)
|
|
100
|
+
else:
|
|
101
|
+
return self._fur
|
|
102
|
+
@property
|
|
103
|
+
def fer3d_mat(self) -> callable:
|
|
104
|
+
if self._fer is None:
|
|
105
|
+
|
|
106
|
+
return lambda x,y,z: np.repeat(self.ermat[:, :, np.newaxis], x.shape[0], axis=2)
|
|
107
|
+
else:
|
|
108
|
+
return self._fer
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def fur3d_mat(self) -> callable:
|
|
112
|
+
if self._fur is None:
|
|
113
|
+
return lambda x,y,z: np.repeat(self.urmat[:, :, np.newaxis], x.shape[0], axis=2)
|
|
114
|
+
else:
|
|
115
|
+
return self._fur
|
|
116
|
+
|
|
117
|
+
AIR = Material(color="#4496f3", opacity=0.05)
|
|
118
|
+
COPPER = Material(cond=5.8e7, color="#62290c")
|