emerge 0.5.2__py3-none-any.whl → 0.5.3__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/_emerge/bc.py +3 -12
- emerge/_emerge/const.py +5 -0
- emerge/_emerge/elements/nedleg2.py +2 -2
- emerge/_emerge/geo/pcb.py +110 -13
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
- emerge/_emerge/geometry.py +1 -1
- emerge/_emerge/logsettings.py +12 -13
- emerge/_emerge/material.py +4 -0
- emerge/_emerge/mth/integrals.py +1 -1
- emerge/_emerge/physics/microwave/adaptive_freq.py +1 -5
- emerge/_emerge/physics/microwave/assembly/assembler.py +62 -39
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
- emerge/_emerge/physics/microwave/microwave_3d.py +33 -26
- emerge/_emerge/physics/microwave/microwave_bc.py +97 -27
- emerge/_emerge/physics/microwave/microwave_data.py +3 -5
- emerge/_emerge/physics/microwave/sc.py +26 -26
- emerge/_emerge/physics/microwave/simjob.py +8 -3
- emerge/_emerge/simmodel.py +12 -8
- emerge/_emerge/simulation_data.py +5 -1
- emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
- emerge/_emerge/solver.py +285 -107
- emerge/cli.py +1 -1
- emerge/lib.py +2 -5
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/RECORD +28 -26
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
- {emerge-0.5.2.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/bc.py
CHANGED
|
@@ -64,17 +64,8 @@ class BoundaryCondition:
|
|
|
64
64
|
return self.dimension.value
|
|
65
65
|
|
|
66
66
|
def __repr__(self) -> str:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
elif self.dimension is BCDimension.EDGE:
|
|
70
|
-
return f'{type(self).__name__}{self.tags}'
|
|
71
|
-
elif self.dimension is BCDimension.NODE:
|
|
72
|
-
return f'{type(self).__name__}{self.tags}'
|
|
73
|
-
elif self.dimension is BCDimension.FACE:
|
|
74
|
-
return f'{type(self).__name__}{self.tags}'
|
|
75
|
-
elif self.dimension is BCDimension.DOMAIN:
|
|
76
|
-
return f'{type(self).__name__}{self.tags}'
|
|
77
|
-
|
|
67
|
+
return f'{type(self).__name__}{self.tags}'
|
|
68
|
+
|
|
78
69
|
def __str__(self) -> str:
|
|
79
70
|
return self.__repr__()
|
|
80
71
|
|
|
@@ -183,7 +174,7 @@ class BoundaryConditionSet:
|
|
|
183
174
|
Returns:
|
|
184
175
|
list[BoundaryCondition]: The list of boundary conditions
|
|
185
176
|
"""
|
|
186
|
-
return [item for item in self.
|
|
177
|
+
return [item for item in self.boundary_conditions if isinstance(item, bctype)]
|
|
187
178
|
|
|
188
179
|
def reset(self) -> None:
|
|
189
180
|
"""Resets the boundary conditions that are defined
|
emerge/_emerge/const.py
ADDED
|
@@ -22,7 +22,7 @@ from .femdata import FEMBasis
|
|
|
22
22
|
from .ned2_interp import ned2_tri_interp_full, ned2_tri_interp_curl
|
|
23
23
|
from ..mth.optimized import matinv
|
|
24
24
|
from ..cs import CoordinateSystem
|
|
25
|
-
|
|
25
|
+
from ..const import MU0, C0
|
|
26
26
|
|
|
27
27
|
## TODO: TEMPORARY SOLUTION FIX THIS
|
|
28
28
|
|
|
@@ -158,7 +158,7 @@ class NedelecLegrange2(FEMBasis):
|
|
|
158
158
|
|
|
159
159
|
def interpolate_Hf(self, field: np.ndarray, k0: float, ur: np.ndarray, beta: float) -> FieldFunctionClass:
|
|
160
160
|
'''Generates the Interpolation function as a function object for a given coordiante basis and origin.'''
|
|
161
|
-
constant = 1j / ((k0*
|
|
161
|
+
constant = 1j / ((k0*C0)*MU0)
|
|
162
162
|
urinv = np.zeros_like(ur)
|
|
163
163
|
|
|
164
164
|
for i in range(ur.shape[2]):
|
emerge/_emerge/geo/pcb.py
CHANGED
|
@@ -17,9 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
import numpy as np
|
|
21
|
-
import gmsh
|
|
22
|
-
|
|
23
20
|
from ..cs import CoordinateSystem, GCS, Axis
|
|
24
21
|
from ..geometry import GeoPolygon, GeoVolume, GeoSurface
|
|
25
22
|
from ..material import Material, AIR, COPPER
|
|
@@ -28,11 +25,14 @@ from .polybased import XYPolygon
|
|
|
28
25
|
from .operations import change_coordinate_system
|
|
29
26
|
from .pcb_tools.macro import parse_macro
|
|
30
27
|
from .pcb_tools.calculator import PCBCalculator
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
31
30
|
from loguru import logger
|
|
32
31
|
from typing import Literal, Callable, overload
|
|
33
32
|
from dataclasses import dataclass
|
|
34
|
-
import math
|
|
35
33
|
|
|
34
|
+
import math
|
|
35
|
+
import gmsh
|
|
36
36
|
|
|
37
37
|
############################################################
|
|
38
38
|
# EXCEPTIONS #
|
|
@@ -63,7 +63,15 @@ def normalize(vector: np.ndarray) -> np.ndarray:
|
|
|
63
63
|
return vector
|
|
64
64
|
return vector / norm
|
|
65
65
|
|
|
66
|
-
def _rot_mat(angle):
|
|
66
|
+
def _rot_mat(angle: float) -> np.ndarray:
|
|
67
|
+
"""Returns a 2D rotation matrix given an angle in degrees
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
angle (float): The angle in degrees
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
np.ndarray: The rotation matrix
|
|
74
|
+
"""
|
|
67
75
|
ang = -angle * np.pi/180
|
|
68
76
|
return np.array([[np.cos(ang), -np.sin(ang)], [np.sin(ang), np.cos(ang)]])
|
|
69
77
|
|
|
@@ -265,6 +273,66 @@ class StripTurn(RouteElement):
|
|
|
265
273
|
else:
|
|
266
274
|
raise RouteException(f'Trying to route a StripTurn with an unknown corner type: {self.corner_type}')
|
|
267
275
|
|
|
276
|
+
class StripCurve(StripTurn):
|
|
277
|
+
def __init__(self,
|
|
278
|
+
x: float,
|
|
279
|
+
y: float,
|
|
280
|
+
width: float,
|
|
281
|
+
direction: tuple[float, float],
|
|
282
|
+
angle: float,
|
|
283
|
+
radius: float,
|
|
284
|
+
dang: float = 10.0):
|
|
285
|
+
self.xold: float = x
|
|
286
|
+
self.yold: float = y
|
|
287
|
+
self.width: float = width
|
|
288
|
+
self.old_direction: np.ndarray = normalize(np.array(direction))
|
|
289
|
+
self.direction: np.ndarray = _rot_mat(angle) @ self.old_direction
|
|
290
|
+
self.angle: float = angle
|
|
291
|
+
self.radius: float = radius
|
|
292
|
+
self.dirright: np.ndarray = np.array([self.old_direction[1], -self.old_direction[0]])
|
|
293
|
+
self.dang: float = dang
|
|
294
|
+
|
|
295
|
+
angd = abs(angle*np.pi/180)
|
|
296
|
+
self.start = np.array([x,y])
|
|
297
|
+
self.circ_origin = self.start + radius * np.sign(angle) * self.dirright
|
|
298
|
+
|
|
299
|
+
self._xhat = -self.dirright * np.sign(angle)
|
|
300
|
+
self._yhat = self.old_direction
|
|
301
|
+
|
|
302
|
+
self.end = self.circ_origin + radius*(self._xhat*np.cos(angd)+self._yhat*np.sin(angd))
|
|
303
|
+
self.x, self.y = self.end
|
|
304
|
+
|
|
305
|
+
def __str__(self) -> str:
|
|
306
|
+
return f'StripCurve[{self.x},{self.y},w={self.width},d=({self.direction})]'
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def right(self) -> list[tuple[float, float]]:
|
|
310
|
+
points: list[tuple[float, float]] = []
|
|
311
|
+
Npts = int(np.ceil(abs(self.angle/self.dang)))
|
|
312
|
+
R = self.radius-np.sign(self.angle)*self.width/2
|
|
313
|
+
print(R, self.circ_origin, self._xhat, self._yhat)
|
|
314
|
+
for i in range(Npts):
|
|
315
|
+
ang = abs((i+1)/Npts * self.angle * np.pi/180)
|
|
316
|
+
pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
|
|
317
|
+
points.append((pnew[0], pnew[1]))
|
|
318
|
+
|
|
319
|
+
return points
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def left(self) -> list[tuple[float, float]]:
|
|
323
|
+
points: list[tuple[float, float]] = []
|
|
324
|
+
|
|
325
|
+
Npts = int(np.ceil(abs(self.angle/self.dang)))
|
|
326
|
+
R = self.radius+np.sign(self.angle)*self.width/2
|
|
327
|
+
print(R, self.circ_origin, self._xhat, self._yhat)
|
|
328
|
+
for i in range(Npts):
|
|
329
|
+
ang = abs((i+1)/Npts * self.angle * np.pi/180)
|
|
330
|
+
pnew = self.circ_origin + R*(self._xhat*np.cos(ang)+self._yhat*np.sin(ang))
|
|
331
|
+
points.append((pnew[0], pnew[1]))
|
|
332
|
+
|
|
333
|
+
return points[::-1]
|
|
334
|
+
|
|
335
|
+
|
|
268
336
|
############################################################
|
|
269
337
|
# THE STRIP PATH CLASS #
|
|
270
338
|
############################################################
|
|
@@ -405,7 +473,9 @@ class StripPath:
|
|
|
405
473
|
"""
|
|
406
474
|
x, y = self.end.x, self.end.y
|
|
407
475
|
dx, dy = self.end.direction
|
|
408
|
-
|
|
476
|
+
if abs(angle) <= 20:
|
|
477
|
+
corner_type = 'square'
|
|
478
|
+
logger.warning('Small turn detected, defaulting to rectangular corners because chamfers would add to much detail.')
|
|
409
479
|
if width is not None:
|
|
410
480
|
if width != self.end.width:
|
|
411
481
|
self._add_element(StripLine(x, y, width, (dx, dy)))
|
|
@@ -413,6 +483,37 @@ class StripPath:
|
|
|
413
483
|
width=self.end.width
|
|
414
484
|
self._add_element(StripTurn(x, y, width, (dx, dy), angle, corner_type))
|
|
415
485
|
return self
|
|
486
|
+
|
|
487
|
+
def curve(self, angle: float, radius: float,
|
|
488
|
+
width: float | None = None,
|
|
489
|
+
corner_type: Literal['champher','square'] = 'champher',
|
|
490
|
+
dang: float = 10) -> StripPath:
|
|
491
|
+
"""Adds a bend to the strip path.
|
|
492
|
+
|
|
493
|
+
The angle is specified in degrees. The width of the turn will be the same as the last segment.
|
|
494
|
+
optionally, a different width may be provided.
|
|
495
|
+
By default, all corners will be cut using the "champher" type. Other options are not yet provided.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
angle (float): The turning angle
|
|
499
|
+
width (float, optional): The stripline width. Defaults to None.
|
|
500
|
+
corner_type (str, optional): The corner type. Defaults to 'champher'.
|
|
501
|
+
|
|
502
|
+
Returns:
|
|
503
|
+
StripPath: The current StripPath object
|
|
504
|
+
"""
|
|
505
|
+
if angle == 0:
|
|
506
|
+
logger.trace('Zero angle turn, passing action')
|
|
507
|
+
return self
|
|
508
|
+
x, y = self.end.x, self.end.y
|
|
509
|
+
dx, dy = self.end.direction
|
|
510
|
+
if width is not None:
|
|
511
|
+
if width != self.end.width:
|
|
512
|
+
self._add_element(StripLine(x, y, width, (dx, dy)))
|
|
513
|
+
else:
|
|
514
|
+
width=self.end.width
|
|
515
|
+
self._add_element(StripCurve(x, y, width, (dx, dy), angle, radius, dang=dang))
|
|
516
|
+
return self
|
|
416
517
|
|
|
417
518
|
def store(self, name: str) -> StripPath:
|
|
418
519
|
""" Store the current x,y coordinate labeled in the PCB object.
|
|
@@ -495,7 +596,6 @@ class StripPath:
|
|
|
495
596
|
self.pcb._lumped_element(poly, impedance_function, width, length)
|
|
496
597
|
return self.pcb.new(x+dx*length, y+dy*length, self.end.width, self.end.direction, self.z)
|
|
497
598
|
|
|
498
|
-
|
|
499
599
|
def cut(self) -> StripPath:
|
|
500
600
|
"""Split the current path in N new paths given by a new departure direction
|
|
501
601
|
|
|
@@ -637,7 +737,7 @@ class StripPath:
|
|
|
637
737
|
def to(self, dest: tuple[float, float],
|
|
638
738
|
arrival_dir: tuple[float, float] | None = None,
|
|
639
739
|
arrival_margin: float | None= None,
|
|
640
|
-
angle_step: float = 90):
|
|
740
|
+
angle_step: float = 90) -> StripPath:
|
|
641
741
|
"""
|
|
642
742
|
Extend the path from current end point to dest (x, y).
|
|
643
743
|
Optionally ensure arrival in arrival_dir after a straight segment of arrival_margin.
|
|
@@ -743,9 +843,8 @@ class StripPath:
|
|
|
743
843
|
back_ang = math.degrees(math.atan2(cross2, dot2))
|
|
744
844
|
|
|
745
845
|
backoff = math.tan(abs(back_ang)*np.pi/360)*self.end.width/2
|
|
746
|
-
self.straight(s - backoff)
|
|
747
|
-
|
|
748
846
|
|
|
847
|
+
self.straight(s - backoff)
|
|
749
848
|
self.turn(-back_ang)
|
|
750
849
|
|
|
751
850
|
x0 = self.end.x
|
|
@@ -753,7 +852,6 @@ class StripPath:
|
|
|
753
852
|
D = math.hypot(tx-x0, ty-y0)
|
|
754
853
|
# 4) Final straight into destination by arrival_margin + t
|
|
755
854
|
self.straight(D)
|
|
756
|
-
|
|
757
855
|
|
|
758
856
|
return self
|
|
759
857
|
|
|
@@ -801,7 +899,7 @@ class StripPath:
|
|
|
801
899
|
return self.path[element_nr]
|
|
802
900
|
|
|
803
901
|
############################################################
|
|
804
|
-
# PCB DESIGN CLASS
|
|
902
|
+
# PCB DESIGN CLASS #
|
|
805
903
|
############################################################
|
|
806
904
|
|
|
807
905
|
class PCB:
|
|
@@ -1270,7 +1368,6 @@ class PCB:
|
|
|
1270
1368
|
polys.material = COPPER
|
|
1271
1369
|
return polys
|
|
1272
1370
|
|
|
1273
|
-
|
|
1274
1371
|
############################################################
|
|
1275
1372
|
# DEPRICATED #
|
|
1276
1373
|
############################################################
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from ...material import Material
|
|
3
|
+
from ...const import Z0 as n0
|
|
3
4
|
|
|
4
5
|
PI = np.pi
|
|
5
6
|
TAU = 2*PI
|
|
6
|
-
n0 = 376.73
|
|
7
7
|
|
|
8
8
|
def microstrip_z0(W: float, th: float, er: float):
|
|
9
9
|
u = W/th
|
|
@@ -14,7 +14,7 @@ def microstrip_z0(W: float, th: float, er: float):
|
|
|
14
14
|
|
|
15
15
|
class PCBCalculator:
|
|
16
16
|
|
|
17
|
-
def __init__(self, thickness: float, layers: np.
|
|
17
|
+
def __init__(self, thickness: float, layers: np.ndarray, material: Material, unit: float):
|
|
18
18
|
self.th = thickness
|
|
19
19
|
self.layers = layers
|
|
20
20
|
self.mat = material
|
emerge/_emerge/geometry.py
CHANGED
|
@@ -299,7 +299,7 @@ class GeoObject:
|
|
|
299
299
|
for tag in self.tags:
|
|
300
300
|
newtags.extend(tagmap.get(tag, [tag,]))
|
|
301
301
|
self.tags = newtags
|
|
302
|
-
logger.debug(f'Replaced {self.old_tags}
|
|
302
|
+
logger.debug(f'{self} Replaced {self.old_tags} -> {self.tags}')
|
|
303
303
|
|
|
304
304
|
def update_tags(self, tag_mapping: dict[int,dict]) -> GeoObject:
|
|
305
305
|
''' Update the tag definition of a GeoObject after fragementation.'''
|
emerge/_emerge/logsettings.py
CHANGED
|
@@ -4,31 +4,32 @@ from typing import Literal
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
import os
|
|
7
|
-
|
|
7
|
+
import gmsh
|
|
8
8
|
|
|
9
9
|
############################################################
|
|
10
10
|
# FORMATS #
|
|
11
11
|
############################################################
|
|
12
12
|
|
|
13
13
|
TRACE_FORMAT = (
|
|
14
|
-
"{time: YY/MM/DD
|
|
15
|
-
"
|
|
14
|
+
"{time:ddd YY/MM/DD HH:mm:ss.SSSS} {level:<7} {thread.id:<15} {line:>4}: "
|
|
15
|
+
"{message}"
|
|
16
16
|
)
|
|
17
|
+
|
|
17
18
|
DEBUG_FORMAT = (
|
|
18
|
-
"<green>{elapsed}</green>
|
|
19
|
-
"
|
|
19
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
20
|
+
"<level>{message}</level>"
|
|
20
21
|
)
|
|
21
22
|
INFO_FORMAT = (
|
|
22
|
-
"<green>{elapsed}</green>
|
|
23
|
-
"
|
|
23
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
24
|
+
"<level>{message}</level>"
|
|
24
25
|
)
|
|
25
26
|
WARNING_FORMAT = (
|
|
26
|
-
"<green>{elapsed}</green>
|
|
27
|
-
"
|
|
27
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
28
|
+
"<level>{message}</level>"
|
|
28
29
|
)
|
|
29
30
|
ERROR_FORMAT = (
|
|
30
|
-
"<green>{elapsed}</green>
|
|
31
|
-
"
|
|
31
|
+
"<green>{elapsed}</green> <level>{level:<7}</level>: "
|
|
32
|
+
"<level>{message}</level>"
|
|
32
33
|
)
|
|
33
34
|
FORMAT_DICT = {
|
|
34
35
|
'TRACE': TRACE_FORMAT,
|
|
@@ -71,10 +72,8 @@ class LogController:
|
|
|
71
72
|
logger.configure(handlers=[handler]) # type: ignore
|
|
72
73
|
self.level = loglevel
|
|
73
74
|
os.environ["EMERGE_STD_LOGLEVEL"] = loglevel
|
|
74
|
-
|
|
75
75
|
|
|
76
76
|
def set_write_file(self, path: Path, loglevel: str = 'TRACE'):
|
|
77
|
-
|
|
78
77
|
handler_id = logger.add(str(path / 'logging.log'), mode='w', level=loglevel, format=FORMAT_DICT.get(loglevel, INFO_FORMAT), colorize=False, backtrace=True, diagnose=True)
|
|
79
78
|
self.file_handlers.append(handler_id)
|
|
80
79
|
self.file_level = loglevel
|
emerge/_emerge/material.py
CHANGED
|
@@ -45,6 +45,10 @@ class Material:
|
|
|
45
45
|
hex_str = self.color.lstrip('#')
|
|
46
46
|
self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
|
|
47
47
|
|
|
48
|
+
@property
|
|
49
|
+
def sigma(self) -> float:
|
|
50
|
+
return self.cond
|
|
51
|
+
|
|
48
52
|
@property
|
|
49
53
|
def color_rgb(self) -> tuple[float,float,float]:
|
|
50
54
|
return self._color_rgb
|
emerge/_emerge/mth/integrals.py
CHANGED
|
@@ -60,7 +60,7 @@ def _fast_integral_f(nodes, triangles, constants, DPTs, field_values):
|
|
|
60
60
|
def surface_integral(nodes: np.ndarray,
|
|
61
61
|
triangles: np.ndarray,
|
|
62
62
|
function: Callable,
|
|
63
|
-
constants: np.ndarray = None,
|
|
63
|
+
constants: np.ndarray | None = None,
|
|
64
64
|
gq_order: int = 4) -> complex:
|
|
65
65
|
"""Computes the surface integral of a scalar-field
|
|
66
66
|
|
|
@@ -108,10 +108,6 @@ def vectfit_step(f: np.ndarray, s: np.ndarray, poles: np.ndarray) -> np.ndarray:
|
|
|
108
108
|
b = np.concatenate((b.real, b.imag))
|
|
109
109
|
x, residuals, rnk, s = np.linalg.lstsq(A, b, rcond=-1)
|
|
110
110
|
|
|
111
|
-
residues = x[:N]
|
|
112
|
-
d = x[N]
|
|
113
|
-
h = x[N+1]
|
|
114
|
-
|
|
115
111
|
# We only want the "tilde" part in (A.4)
|
|
116
112
|
x = x[-N:]
|
|
117
113
|
|
|
@@ -197,7 +193,7 @@ def vectfit_auto(f: np.ndarray,
|
|
|
197
193
|
w = s.imag
|
|
198
194
|
pole_locs = np.linspace(w[0], w[-1], n_poles+2)[1:-1]
|
|
199
195
|
lr = loss_ratio
|
|
200
|
-
|
|
196
|
+
poles = np.concatenate([[p*(-lr + 1j), p*(-lr - 1j)] for p in pole_locs])
|
|
201
197
|
|
|
202
198
|
if inc_real:
|
|
203
199
|
poles = np.concatenate((poles, [1]))
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
|
-
from ..microwave_bc import PEC, BoundaryCondition, RectangularWaveguide, RobinBC, PortBC, Periodic
|
|
19
|
+
from ..microwave_bc import PEC, BoundaryCondition, RectangularWaveguide, RobinBC, PortBC, Periodic, MWBoundaryConditionSet
|
|
20
20
|
from ....elements.nedelec2 import Nedelec2
|
|
21
21
|
from ....elements.nedleg2 import NedelecLegrange2
|
|
22
22
|
from ....mth.optimized import gaus_quad_tri
|
|
@@ -26,14 +26,7 @@ from scipy.sparse import csr_matrix
|
|
|
26
26
|
from loguru import logger
|
|
27
27
|
from ..simjob import SimJob
|
|
28
28
|
from .periodicbc import gen_periodic_matrix
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
############################################################
|
|
32
|
-
# CONSTANTS #
|
|
33
|
-
############################################################
|
|
34
|
-
|
|
35
|
-
C0 = 299792458
|
|
36
|
-
EPS0 = 8.854187818814e-12
|
|
29
|
+
from ....const import MU0, EPS0, C0
|
|
37
30
|
|
|
38
31
|
|
|
39
32
|
############################################################
|
|
@@ -124,7 +117,7 @@ class Assembler:
|
|
|
124
117
|
sig: np.ndarray,
|
|
125
118
|
k0: float,
|
|
126
119
|
port: PortBC,
|
|
127
|
-
|
|
120
|
+
bc_set: MWBoundaryConditionSet) -> tuple[csr_matrix, csr_matrix, np.ndarray, NedelecLegrange2]:
|
|
128
121
|
"""Computes the boundary mode analysis matrices
|
|
129
122
|
|
|
130
123
|
Args:
|
|
@@ -134,32 +127,37 @@ class Assembler:
|
|
|
134
127
|
sig (np.ndarray): The conductivity scalar of shape (N,)
|
|
135
128
|
k0 (float): The simulation phase constant
|
|
136
129
|
port (PortBC): The port boundary condition object
|
|
137
|
-
bcs (
|
|
130
|
+
bcs (MWBoundaryConditionSet): The other boundary conditions
|
|
138
131
|
|
|
139
132
|
Returns:
|
|
140
133
|
tuple[np.ndarray, np.ndarray, np.ndarray, NedelecLegrange2]: The E, B, solve ids and Mixed order field object.
|
|
141
134
|
"""
|
|
142
135
|
from .generalized_eigen import generelized_eigenvalue_matrix
|
|
143
|
-
|
|
136
|
+
logger.debug('Assembling Boundary Mode Matrices')
|
|
137
|
+
|
|
138
|
+
bcs = bc_set.boundary_conditions
|
|
144
139
|
mesh = field.mesh
|
|
145
140
|
tri_ids = mesh.get_triangles(port.tags)
|
|
141
|
+
logger.trace(f'.boundary face has {len(tri_ids)} triangles.')
|
|
146
142
|
|
|
147
143
|
origin = tuple([c-n for c,n in zip(port.cs.origin, port.cs.gzhat)])
|
|
148
|
-
|
|
144
|
+
logger.trace(f'.boundary origin {origin}')
|
|
145
|
+
|
|
149
146
|
boundary_surface = mesh.boundary_surface(port.tags, origin)
|
|
150
147
|
nedlegfield = NedelecLegrange2(boundary_surface, port.cs)
|
|
151
148
|
|
|
152
149
|
ermesh = er[:,:,tri_ids]
|
|
153
150
|
urmesh = ur[:,:,tri_ids]
|
|
154
151
|
sigmesh = sig[tri_ids]
|
|
155
|
-
|
|
156
152
|
ermesh = ermesh - 1j * sigmesh/(k0*C0*EPS0)
|
|
157
153
|
|
|
154
|
+
logger.trace(f'.assembling matrices for {nedlegfield} at k0={k0:.2f}')
|
|
158
155
|
E, B = generelized_eigenvalue_matrix(nedlegfield, ermesh, urmesh, port.cs._basis, k0)
|
|
159
156
|
|
|
160
|
-
|
|
157
|
+
# 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)]
|
|
161
159
|
if len(pecs) > 0:
|
|
162
|
-
logger.debug('
|
|
160
|
+
logger.debug(f'.total of equiv. {len(pecs)} PEC BCs implemented')
|
|
163
161
|
|
|
164
162
|
pec_ids = []
|
|
165
163
|
|
|
@@ -172,8 +170,8 @@ class Assembler:
|
|
|
172
170
|
pec_edges = []
|
|
173
171
|
pec_vertices = []
|
|
174
172
|
for pec in pecs:
|
|
173
|
+
logger.trace(f'.implementing {pec}')
|
|
175
174
|
face_tags = pec.tags
|
|
176
|
-
|
|
177
175
|
tri_ids = mesh.get_triangles(face_tags)
|
|
178
176
|
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
179
177
|
for ii in edge_ids:
|
|
@@ -186,7 +184,6 @@ class Assembler:
|
|
|
186
184
|
pec_vertices.append(eids[3]-nedlegfield.n_xy)
|
|
187
185
|
pec_vertices.append(eids[4]-nedlegfield.n_xy)
|
|
188
186
|
|
|
189
|
-
|
|
190
187
|
for ii in tri_ids:
|
|
191
188
|
i2 = nedlegfield.mesh.from_source_tri(ii)
|
|
192
189
|
if i2 is None:
|
|
@@ -196,6 +193,8 @@ class Assembler:
|
|
|
196
193
|
|
|
197
194
|
# Process all port boundary Conditions
|
|
198
195
|
pec_ids_set: set[int] = set(pec_ids)
|
|
196
|
+
|
|
197
|
+
logger.trace(f'.total of {len(pec_ids_set)} pec DoF to remove.')
|
|
199
198
|
solve_ids = [i for i in range(nedlegfield.n_field) if i not in pec_ids_set]
|
|
200
199
|
|
|
201
200
|
return E, B, np.array(solve_ids), nedlegfield
|
|
@@ -236,11 +235,11 @@ class Assembler:
|
|
|
236
235
|
|
|
237
236
|
if cache_matrices and not is_frequency_dependent and self.cached_matrices is not None:
|
|
238
237
|
# IF CACHED AND AVAILABLE PULL E AND B FROM CACHE
|
|
239
|
-
logger.debug('
|
|
238
|
+
logger.debug('Using cached matricies.')
|
|
240
239
|
E, B = self.cached_matrices
|
|
241
240
|
else:
|
|
242
241
|
# OTHERWISE, COMPUTE
|
|
243
|
-
logger.debug('
|
|
242
|
+
logger.debug('Assembling matrices')
|
|
244
243
|
E, B = tet_mass_stiffness_matrices(field, er, ur)
|
|
245
244
|
self.cached_matrices = (E, B)
|
|
246
245
|
|
|
@@ -259,15 +258,24 @@ class Assembler:
|
|
|
259
258
|
b = np.zeros((E.shape[0],)).astype(np.complex128)
|
|
260
259
|
port_vectors = {port.port_number: np.zeros((E.shape[0],)).astype(np.complex128) for port in port_bcs}
|
|
261
260
|
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
|
|
262
|
+
############################################################
|
|
263
|
+
# PEC BOUNDARY CONDITIONS #
|
|
264
|
+
############################################################
|
|
265
|
+
|
|
266
|
+
logger.debug('Implementing PEC Boundary Conditions.')
|
|
264
267
|
pec_ids: list[int] = []
|
|
265
268
|
# Conductivity above al imit, consider it all PEC
|
|
269
|
+
ipec = 0
|
|
266
270
|
for itet in range(field.n_tets):
|
|
267
271
|
if sig[itet] > self.conductivity_limit:
|
|
272
|
+
ipec+=1
|
|
268
273
|
pec_ids.extend(field.tet_to_field[:,itet])
|
|
269
|
-
|
|
274
|
+
if ipec>0:
|
|
275
|
+
logger.trace(f'Extended PEC with {ipec} tets with a conductivity > {self.conductivity_limit}.')
|
|
276
|
+
|
|
270
277
|
for pec in pec_bcs:
|
|
278
|
+
logger.trace(f'Implementing: {pec}')
|
|
271
279
|
if len(pec.tags)==0:
|
|
272
280
|
continue
|
|
273
281
|
face_tags = pec.tags
|
|
@@ -282,14 +290,18 @@ class Assembler:
|
|
|
282
290
|
tids = field.tri_to_field[:, ii]
|
|
283
291
|
pec_ids.extend(list(tids))
|
|
284
292
|
|
|
285
|
-
|
|
293
|
+
|
|
294
|
+
############################################################
|
|
295
|
+
# ROBIN BOUNDARY CONDITIONS #
|
|
296
|
+
############################################################
|
|
297
|
+
|
|
286
298
|
if len(robin_bcs) > 0:
|
|
287
|
-
logger.debug('
|
|
299
|
+
logger.debug('Implementing Robin Boundary Conditions.')
|
|
288
300
|
|
|
289
301
|
gauss_points = gaus_quad_tri(4)
|
|
290
302
|
Bempty = field.empty_tri_matrix()
|
|
291
303
|
for bc in robin_bcs:
|
|
292
|
-
|
|
304
|
+
logger.trace(f'.Implementing {bc}')
|
|
293
305
|
for tag in bc.tags:
|
|
294
306
|
face_tags = [tag,]
|
|
295
307
|
|
|
@@ -298,6 +310,7 @@ class Assembler:
|
|
|
298
310
|
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
299
311
|
|
|
300
312
|
gamma = bc.get_gamma(K0)
|
|
313
|
+
logger.trace(f'..robin bc γ={gamma:.3f}')
|
|
301
314
|
|
|
302
315
|
def Ufunc(x,y):
|
|
303
316
|
return bc.get_Uinc(x,y,K0)
|
|
@@ -306,38 +319,42 @@ class Assembler:
|
|
|
306
319
|
if ibasis is None:
|
|
307
320
|
basis = plane_basis_from_points(mesh.nodes[:,nodes]) + 1e-16
|
|
308
321
|
ibasis = np.linalg.pinv(basis)
|
|
322
|
+
logger.trace(f'..Using computed basis: {ibasis.flatten()}')
|
|
309
323
|
if bc._include_force:
|
|
310
|
-
|
|
311
324
|
Bempty, b_p = assemble_robin_bc_excited(field, Bempty, tri_ids, Ufunc, gamma, ibasis, bc.cs.origin, gauss_points) # type: ignore
|
|
312
|
-
|
|
313
325
|
port_vectors[bc.port_number] += b_p # type: ignore
|
|
314
|
-
|
|
326
|
+
logger.trace(f'..included force vector term with norm {np.linalg.norm(b_p):.3f}')
|
|
315
327
|
else:
|
|
316
328
|
Bempty = assemble_robin_bc(field, Bempty, tri_ids, gamma) # type: ignore
|
|
317
329
|
B_p = field.generate_csr(Bempty)
|
|
318
330
|
K = K + B_p
|
|
319
331
|
|
|
320
332
|
if len(periodic_bcs) > 0:
|
|
321
|
-
logger.debug('
|
|
333
|
+
logger.debug('Implementing Periodic Boundary Conditions.')
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
############################################################
|
|
337
|
+
# PERIODIC BOUNDARY CONDITIONS #
|
|
338
|
+
############################################################
|
|
322
339
|
|
|
323
|
-
|
|
324
|
-
# Periodic BCs
|
|
325
340
|
Pmats = []
|
|
326
341
|
remove: set[int] = set()
|
|
327
342
|
has_periodic = False
|
|
328
343
|
|
|
329
344
|
for pbc in periodic_bcs:
|
|
345
|
+
logger.trace(f'.Implementing {pbc}')
|
|
330
346
|
has_periodic = True
|
|
331
347
|
tri_ids_1 = mesh.get_triangles(pbc.face1.tags)
|
|
332
348
|
edge_ids_1 = mesh.get_edges(pbc.face1.tags)
|
|
333
349
|
tri_ids_2 = mesh.get_triangles(pbc.face2.tags)
|
|
334
350
|
edge_ids_2 = mesh.get_edges(pbc.face2.tags)
|
|
335
351
|
dv = np.array(pbc.dv)
|
|
352
|
+
logger.trace(f'..displacement vector {dv}')
|
|
336
353
|
linked_tris = pair_coordinates(mesh.tri_centers, tri_ids_1, tri_ids_2, dv, 1e-9)
|
|
337
354
|
linked_edges = pair_coordinates(mesh.edge_centers, edge_ids_1, edge_ids_2, dv, 1e-9)
|
|
338
355
|
dv = np.array(pbc.dv)
|
|
339
356
|
phi = pbc.phi(K0)
|
|
340
|
-
|
|
357
|
+
logger.trace(f'..ϕ={phi} rad/m')
|
|
341
358
|
Pmat, rows = gen_periodic_matrix(tri_ids_1,
|
|
342
359
|
edge_ids_1,
|
|
343
360
|
field.tri_to_field,
|
|
@@ -348,8 +365,9 @@ class Assembler:
|
|
|
348
365
|
phi)
|
|
349
366
|
remove.update(rows)
|
|
350
367
|
Pmats.append(Pmat)
|
|
351
|
-
|
|
368
|
+
|
|
352
369
|
if Pmats:
|
|
370
|
+
logger.trace(f'.periodic bc removes {len(remove)} boundary DoF')
|
|
353
371
|
Pmat = Pmats[0]
|
|
354
372
|
for P2 in Pmats[1:]:
|
|
355
373
|
Pmat = Pmat @ P2
|
|
@@ -360,6 +378,11 @@ class Assembler:
|
|
|
360
378
|
else:
|
|
361
379
|
Pmat = None
|
|
362
380
|
|
|
381
|
+
|
|
382
|
+
############################################################
|
|
383
|
+
# FINALIZE #
|
|
384
|
+
############################################################
|
|
385
|
+
|
|
363
386
|
pec_ids_set = set(pec_ids)
|
|
364
387
|
solve_ids = np.array([i for i in range(E.shape[0]) if i not in pec_ids_set])
|
|
365
388
|
|
|
@@ -416,7 +439,7 @@ class Assembler:
|
|
|
416
439
|
|
|
417
440
|
er = er - 1j*sig/(w0*EPS0)*np.repeat(np.eye(3)[:, :, np.newaxis], er.shape[2], axis=2)
|
|
418
441
|
|
|
419
|
-
logger.debug('
|
|
442
|
+
logger.debug('Assembling matrices')
|
|
420
443
|
E, B = tet_mass_stiffness_matrices(field, er, ur)
|
|
421
444
|
self.cached_matrices = (E, B)
|
|
422
445
|
|
|
@@ -429,7 +452,7 @@ class Assembler:
|
|
|
429
452
|
# Process all PEC Boundary Conditions
|
|
430
453
|
pec_ids: list = []
|
|
431
454
|
|
|
432
|
-
logger.debug('
|
|
455
|
+
logger.debug('Implementing PEC Boundary Conditions.')
|
|
433
456
|
|
|
434
457
|
# Conductivity above a limit, consider it all PEC
|
|
435
458
|
for itet in range(field.n_tets):
|
|
@@ -454,10 +477,10 @@ class Assembler:
|
|
|
454
477
|
|
|
455
478
|
# Robin BCs
|
|
456
479
|
if len(robin_bcs) > 0:
|
|
457
|
-
logger.debug('
|
|
480
|
+
logger.debug('Implementing Robin Boundary Conditions.')
|
|
458
481
|
|
|
459
482
|
if len(robin_bcs) > 0:
|
|
460
|
-
logger.debug('
|
|
483
|
+
logger.debug('Implementing Robin Boundary Conditions.')
|
|
461
484
|
|
|
462
485
|
gauss_points = gaus_quad_tri(4)
|
|
463
486
|
Bempty = field.empty_tri_matrix()
|
|
@@ -482,7 +505,7 @@ class Assembler:
|
|
|
482
505
|
B = B + B_p
|
|
483
506
|
|
|
484
507
|
if len(periodic) > 0:
|
|
485
|
-
logger.debug('
|
|
508
|
+
logger.debug('Implementing Periodic Boundary Conditions.')
|
|
486
509
|
|
|
487
510
|
# Periodic BCs
|
|
488
511
|
Pmats = []
|