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 CHANGED
@@ -64,17 +64,8 @@ class BoundaryCondition:
64
64
  return self.dimension.value
65
65
 
66
66
  def __repr__(self) -> str:
67
- if self.dimension is BCDimension.ANY:
68
- return f'{type(self).__name__}{self.tags}'
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. boundary_conditions if isinstance(item, bctype)]
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
@@ -0,0 +1,5 @@
1
+ C0 = 299792458
2
+ Z0 = 376.73031366857
3
+ PI = 3.14159265358979323846
4
+ EPS0 = 8.854187818814e-12
5
+ MU0 = 1/(C0*C0*EPS0)#1.2566370612720e-6
@@ -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*299792458)*(4*np.pi*1e-7))
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.array, material: Material, unit: float):
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
@@ -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} with {self.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.'''
@@ -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 - (ddd) - HH:mm:ss.SSSS} | <green>{elapsed}</green> [ <level>{level}</level> ] "
15
- " <level>{message}</level>"
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> [<level>{level}</level>] "
19
- " <level>{message}</level>"
19
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
20
+ "<level>{message}</level>"
20
21
  )
21
22
  INFO_FORMAT = (
22
- "<green>{elapsed}</green> [<level>{level}</level>] "
23
- " <level>{message}</level>"
23
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
24
+ "<level>{message}</level>"
24
25
  )
25
26
  WARNING_FORMAT = (
26
- "<green>{elapsed}</green> [<level>{level}</level>] "
27
- " <level>{message}</level>"
27
+ "<green>{elapsed}</green> <level>{level:<7}</level>: "
28
+ "<level>{message}</level>"
28
29
  )
29
30
  ERROR_FORMAT = (
30
- "<green>{elapsed}</green> [<level>{level}</level>] "
31
- " <level>{message}</level>"
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
@@ -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
@@ -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
- init_poles = poles = np.concatenate([[p*(-lr + 1j), p*(-lr - 1j)] for p in pole_locs])
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
- bcs: list[BoundaryCondition]) -> tuple[csr_matrix, csr_matrix, np.ndarray, NedelecLegrange2]:
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 (list[BoundaryCondition]): The other boundary conditions
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
- pecs: list[PEC] = [bc for bc in bcs if isinstance(bc,PEC)]
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('Implementing PEC BCs')
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(' Retreiving cached matricies')
238
+ logger.debug('Using cached matricies.')
240
239
  E, B = self.cached_matrices
241
240
  else:
242
241
  # OTHERWISE, COMPUTE
243
- logger.debug(' Assembling matrices')
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
- # Process all PEC Boundary Conditions
263
- logger.debug(' Implementing PEC Boundary Conditions.')
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
- # Robin BCs
293
+
294
+ ############################################################
295
+ # ROBIN BOUNDARY CONDITIONS #
296
+ ############################################################
297
+
286
298
  if len(robin_bcs) > 0:
287
- logger.debug(' Implementing Robin Boundary Conditions.')
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(' Implementing Periodic Boundary Conditions.')
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(' Assembling matrices')
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(' Implementing PEC Boundary Conditions.')
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(' Implementing Robin Boundary Conditions.')
480
+ logger.debug('Implementing Robin Boundary Conditions.')
458
481
 
459
482
  if len(robin_bcs) > 0:
460
- logger.debug(' Implementing Robin Boundary Conditions.')
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(' Implementing Periodic Boundary Conditions.')
508
+ logger.debug('Implementing Periodic Boundary Conditions.')
486
509
 
487
510
  # Periodic BCs
488
511
  Pmats = []