emerge 0.4.11__py3-none-any.whl → 0.5.1__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.

@@ -22,12 +22,11 @@ from typing import Callable, Literal
22
22
  from ...selection import Selection, FaceSelection
23
23
  from ...cs import CoordinateSystem, Axis, GCS
24
24
  from ...coord import Line
25
- from ...geometry import GeoSurface, GeoObject, GeoPolygon
25
+ from ...geometry import GeoSurface, GeoObject
26
26
  from dataclasses import dataclass
27
27
  from collections import defaultdict
28
28
  from ...bc import BoundaryCondition, BoundaryConditionSet, Periodic
29
- from ...geo import XYPolygon, XYPlate
30
- from ...periodic import PeriodicCell, HexCell, RectCell, Alignment
29
+ from ...periodic import PeriodicCell, HexCell, RectCell
31
30
 
32
31
  class MWBoundaryConditionSet(BoundaryConditionSet):
33
32
 
@@ -309,7 +308,7 @@ class FloquetPort(PortBC):
309
308
  self.port_number: int= port_number
310
309
  self.active: bool = True
311
310
  self.power: float = power
312
- self.type: str = 'TE'
311
+ self.type: str = 'TEM'
313
312
  self._field_amplitude: np.ndarray = None
314
313
  self.mode: tuple[int,int] = (1,0)
315
314
  self.cs: CoordinateSystem = cs
@@ -920,3 +919,5 @@ class LumpedElement(RobinBC):
920
919
  complex: The γ-constant
921
920
  """
922
921
  return 1j*k0*376.730313412/self.surfZ(k0)
922
+
923
+
@@ -888,11 +888,11 @@ class MWField:
888
888
  else:
889
889
  tags = faces.tags
890
890
 
891
- surface = self.basis.mesh.boundary_surface(tags, None)
891
+ center = np.mean(self.mesh.nodes, axis=1).squeeze()
892
+ surface = self.basis.mesh.boundary_surface(tags, center)
892
893
  field = self.interpolate(*surface.exyz)
893
894
  vertices = surface.nodes
894
895
  triangles = surface.tris
895
- print(surface._origin, surface._alignment_origin)
896
896
  origin = surface._origin
897
897
  E = field.E
898
898
  H = field.H
@@ -94,7 +94,7 @@ def sparam_mode_power(nodes: np.ndarray,
94
94
  Ex2, Ey2, Ez2 = np.conj(modef(x,y,z))
95
95
  return (Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2)/(2*bc.Zmode(k0))
96
96
 
97
- norm = surface_integral(nodes, tri_vertices, inproduct2, const, ndpts=ndpts)
97
+ norm = surface_integral(nodes, tri_vertices, inproduct2, const, gq_order=ndpts)
98
98
 
99
99
  return norm
100
100
 
@@ -132,7 +132,7 @@ def sparam_field_power(nodes: np.ndarray,
132
132
  Ex2, Ey2, Ez2 = np.conj(modef(x,y,z))
133
133
  return (Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2) / (2*bc.Zmode(k0))
134
134
 
135
- mode_dot_field = surface_integral(nodes, tri_vertices, inproduct1, const, ndpts=ndpts)
135
+ mode_dot_field = surface_integral(nodes, tri_vertices, inproduct1, const, gq_order=ndpts)
136
136
 
137
137
  svec = mode_dot_field
138
138
  return svec
@@ -16,7 +16,7 @@ wgl = 50*mm
16
16
  with em.Simulation3D("myfile", save_file=True) as m:
17
17
  m['box'] = em.geo.Box(wga,wgl,wgb,(0,0,0))
18
18
 
19
- m.define_geometry()
19
+ m.commit_geometry()
20
20
 
21
21
  m.mw.set_frequency_range(8e9,9e9,11)
22
22
 
@@ -22,8 +22,7 @@ from .geo.modeler import Modeler
22
22
  from .physics.microwave.microwave_3d import Microwave3D
23
23
  from .mesh3d import Mesh3D
24
24
  from .selection import Selector, FaceSelection, Selection
25
- from .logsettings import logger_format
26
- from .plot.display import BaseDisplay
25
+ from .logsettings import LOG_CONTROLLER
27
26
  from .plot.pyvista import PVDisplay
28
27
  from .dataset import SimulationDataset
29
28
  from .periodic import PeriodicCell
@@ -40,6 +39,11 @@ from pathlib import Path
40
39
  from atexit import register
41
40
  import signal
42
41
 
42
+
43
+ ############################################################
44
+ # EXCEPTION DEFINITIONS #
45
+ ############################################################
46
+
43
47
  _GMSH_ERROR_TEXT = """
44
48
  --------------------------
45
49
  Known problems/solutions:
@@ -48,10 +52,15 @@ Known problems/solutions:
48
52
  --------------------------
49
53
  """
50
54
 
55
+
51
56
  class SimulationError(Exception):
52
57
  pass
53
58
 
54
59
 
60
+ ############################################################
61
+ # BASE 3D SIMULATION MODEL #
62
+ ############################################################
63
+
55
64
  class Simulation3D:
56
65
 
57
66
  def __init__(self,
@@ -74,6 +83,7 @@ class Simulation3D:
74
83
  logfile (bool, optional): If a file should be created that contains the entire log of the simulation. Defaults to False.
75
84
  path_suffix (str, optional): The suffix that will be added to the results directory. Defaults to ".EMResults".
76
85
  """
86
+
77
87
  caller_file = Path(inspect.stack()[1].filename).resolve()
78
88
  base_path = caller_file.parent
79
89
 
@@ -93,8 +103,9 @@ class Simulation3D:
93
103
  self._cell: PeriodicCell = None
94
104
 
95
105
  self.display = PVDisplay(self.mesh)
106
+
96
107
  if logfile:
97
- self.set_logfile(logfile)
108
+ self.set_logfile()
98
109
 
99
110
  self.save_file: bool = save_file
100
111
  self.load_file: bool = load_file
@@ -107,6 +118,11 @@ class Simulation3D:
107
118
  self._initialize_simulation()
108
119
 
109
120
  self._update_data()
121
+
122
+
123
+ ############################################################
124
+ # PRIVATE FUNCTIONS #
125
+ ############################################################
110
126
 
111
127
  def __setitem__(self, name: str, value: Any) -> None:
112
128
  """Store data in the current data container"""
@@ -116,6 +132,82 @@ class Simulation3D:
116
132
  """Get the data from the current data container"""
117
133
  return self.data.sim[name]
118
134
 
135
+ def __enter__(self) -> Simulation3D:
136
+ """This method is depricated with the new atexit system. It still exists for backwards compatibility.
137
+
138
+ Returns:
139
+ Simulation3D: the Simulation3D object
140
+ """
141
+ return self
142
+
143
+ def __exit__(self, type, value, tb):
144
+ """This method no longer does something. It only serves as backwards compatibility."""
145
+ self._exit_gmsh()
146
+ return False
147
+
148
+ def _install_signal_handlers(self):
149
+ # on SIGINT (Ctrl-C) or SIGTERM, call our exit routine
150
+ for sig in (signal.SIGINT, signal.SIGTERM):
151
+ signal.signal(sig, self._handle_signal)
152
+
153
+ def _handle_signal(self, signum, frame):
154
+ """
155
+ Signal handler: do our cleanup, then re-raise
156
+ the default handler so that exit code / traceback
157
+ is as expected.
158
+ """
159
+ try:
160
+ # run your atexit-style cleanup
161
+ self._exit_gmsh()
162
+ except Exception:
163
+ # log but don’t block shutdown
164
+ logger.exception("Error during signal cleanup")
165
+ finally:
166
+ # restore default handler and re‐send the signal
167
+ signal.signal(signum, signal.SIG_DFL)
168
+ os.kill(os.getpid(), signum)
169
+
170
+ def _initialize_simulation(self):
171
+ """Initializes the Simulation data and GMSH API with proper shutdown routines.
172
+ """
173
+ _GEOMANAGER.sign_in(self.modelname)
174
+
175
+ # If GMSH is not yet initialized (Two simulation in a file)
176
+ if gmsh.isInitialized() == 0:
177
+ logger.debug('Initializing GMSH')
178
+ gmsh.initialize()
179
+
180
+ # Set an exit handler for Ctrl+C cases
181
+ self._install_signal_handlers()
182
+
183
+ # Restier the Exit GMSH function on proper program abortion
184
+ register(self._exit_gmsh)
185
+
186
+ # Create a new GMSH model or load it
187
+ if not self.load_file:
188
+ gmsh.model.add(self.modelname)
189
+ self.data: SimulationDataset = SimulationDataset()
190
+ else:
191
+ self.load()
192
+
193
+ # Set the Simulation state to active
194
+ self.__active = True
195
+ return self
196
+
197
+ def _exit_gmsh(self):
198
+ # If the simulation object state is still active (GMSH is running)
199
+ if not self.__active:
200
+ return
201
+ logger.debug('Exiting program')
202
+ # Save the file first
203
+ if self.save_file:
204
+ self.save()
205
+ # Finalize GMSH
206
+ gmsh.finalize()
207
+ logger.debug('GMSH Shut down successful')
208
+ # set the state to active
209
+ self.__active = False
210
+
119
211
  def _update_data(self) -> None:
120
212
  """Writes the stored physics data to each phyics class insatnce"""
121
213
  self.mw.data = self.data.mw
@@ -128,18 +220,22 @@ class Simulation3D:
128
220
  """Returns all boundary condition objects stored in the simulation file"""
129
221
  return [obj for obj in self.sim.default.values() if isinstance(obj, BoundaryCondition)]
130
222
 
131
- @property
132
- def passed_geometries(self) -> list[GeoObject]:
133
- """"""
134
- return self.data.sim['geometries']
135
-
136
- def set_mesh(self, mesh: Mesh3D) -> None:
223
+ def _set_mesh(self, mesh: Mesh3D) -> None:
137
224
  """Set the current model mesh to a given mesh."""
138
225
  self.mesh = mesh
139
226
  self.mw.mesh = mesh
140
227
  self.mesher.mesh = mesh
141
228
  self.display._mesh = mesh
142
229
 
230
+ ############################################################
231
+ # PUBLIC FUNCTIONS #
232
+ ############################################################
233
+
234
+ @property
235
+ def passed_geometries(self) -> list[GeoObject]:
236
+ """"""
237
+ return self.data.sim['geometries']
238
+
143
239
  def save(self) -> None:
144
240
  """Saves the current model in the provided project directory."""
145
241
  # Ensure directory exists
@@ -186,11 +282,8 @@ class Simulation3D:
186
282
  # Load data
187
283
  datapack = joblib.load(str(data_path))
188
284
  self.data = datapack['simdata']
189
- self.set_mesh(datapack['mesh'])
285
+ self._set_mesh(datapack['mesh'])
190
286
  logger.info(f"Loaded simulation data from: {data_path}")
191
-
192
- def load_data(self, key: str) -> Any:
193
- return self.save_data[key]
194
287
 
195
288
  def set_loglevel(self, loglevel: Literal['DEBUG','INFO','WARNING','ERROR']) -> None:
196
289
  """Set the loglevel for loguru.
@@ -198,12 +291,11 @@ class Simulation3D:
198
291
  Args:
199
292
  loglevel ('DEBUG','INFO','WARNING','ERROR'): The loglevel
200
293
  """
201
- handler = {"sink": sys.stdout, "level": loglevel, "format": logger_format}
202
- logger.configure(handlers=[handler])
294
+ LOG_CONTROLLER.set_std_loglevel(loglevel)
203
295
 
204
296
  def set_logfile(self) -> None:
205
297
  """Adds a file output for the logger."""
206
- logger.add(str(self.modelpath / 'logging.log'), mode='w', level='DEBUG', format=logger_format, colorize=False, backtrace=True, diagnose=True)
298
+ LOG_CONTROLLER.set_write_file(self.modelpath)
207
299
 
208
300
  def view(self,
209
301
  selections: list[Selection] = None,
@@ -240,15 +332,20 @@ class Simulation3D:
240
332
  'sure that this method is properly implemented.')
241
333
 
242
334
  def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] = None):
243
- """Sets the periodic cell information based on the PeriodicCell class object"""
335
+ """Set the given periodic cell object as the simulations peridicity.
336
+
337
+ Args:
338
+ cell (PeriodicCell): The PeriodicCell class
339
+ excluded_faces (list[FaceSelection], optional): Faces to exclude from the periodic boundary condition. Defaults to None.
340
+ """
244
341
  self.mw.bc._cell = cell
245
342
  self._cell = cell
246
343
 
247
- def define_geometry(self, *geometries: list[GeoObject]) -> None:
248
- """Provide the physics engine with the geometries that are contained and ought to be included
249
- in the simulation. Please make sure to include all geometries. Its currently unclear how the
250
- system behaves if only a part of all geometries are included.
344
+ def commit_geometry(self, *geometries: list[GeoObject]) -> None:
345
+ """Finalizes and locks the current geometry state of the simulation.
251
346
 
347
+ The geometries may be provided (legacy behavior) but are automatically managed underwater.
348
+
252
349
  """
253
350
  if not geometries:
254
351
  geometries = _GEOMANAGER.all_geometries()
@@ -258,15 +355,10 @@ class Simulation3D:
258
355
  self.mesher.submit_objects(geometries)
259
356
  self._defined_geometries = True
260
357
  self.display._facetags = [dt[1] for dt in gmsh.model.get_entities(2)]
261
- # Set the cell periodicity in GMSH
262
- if self._cell is not None:
263
- self.mesher.set_periodic_cell(self._cell)
264
-
265
- self.mw._initialize_bcs()
266
-
267
- def generate_mesh(self):
358
+
359
+ def generate_mesh(self) -> None:
268
360
  """Generate the mesh.
269
- This can only be done after define_geometry(...) is called and if frequencies are defined.
361
+ This can only be done after commit_geometry(...) is called and if frequencies are defined.
270
362
 
271
363
  Args:
272
364
  name (str, optional): The mesh file name. Defaults to "meshname.msh".
@@ -275,8 +367,14 @@ class Simulation3D:
275
367
  ValueError: ValueError if no frequencies are defined.
276
368
  """
277
369
  if not self._defined_geometries:
278
- self.define_geometry()
370
+ self.commit_geometry()
279
371
 
372
+ # Set the cell periodicity in GMSH
373
+ if self._cell is not None:
374
+ self.mesher.set_periodic_cell(self._cell)
375
+
376
+ self.mw._initialize_bcs()
377
+
280
378
  # Check if frequencies are defined: TODO: Replace with a more generic check
281
379
  if self.mw.frequencies is None:
282
380
  raise ValueError('No frequencies defined for the simulation. Please set frequencies before generating the mesh.')
@@ -296,25 +394,7 @@ class Simulation3D:
296
394
  self.mesh.update(self.mesher._get_periodic_bcs())
297
395
  self.mesh.exterior_face_tags = self.mesher.domain_boundary_face_tags
298
396
  gmsh.model.occ.synchronize()
299
- self.set_mesh(self.mesh)
300
-
301
- def get_boundary(self, face: FaceSelection = None, tags: list[int] = None) -> tuple[np.ndarray, np.ndarray]:
302
- ''' Return boundary data.
303
-
304
- Parameters
305
- ----------
306
- obj: GeoObject
307
- tags: list[int]
308
-
309
- Returns:
310
- ----------
311
- nodes: np.ndarray
312
- triangles: np.ndarray
313
- '''
314
- if tags is None:
315
- tags = face.tags
316
- tri_ids = self.mesh.get_triangles(tags)
317
- return self.mesh.nodes, self.mesh.tris[:,tri_ids]
397
+ self._set_mesh(self.mesh)
318
398
 
319
399
  def parameter_sweep(self, clear_mesh: bool = True, **parameters: np.ndarray) -> Generator[tuple[float,...], None, None]:
320
400
  """Executes a parameteric sweep iteration.
@@ -349,7 +429,7 @@ class Simulation3D:
349
429
  gmsh.clear()
350
430
  mesh = Mesh3D(self.mesher)
351
431
  _GEOMANAGER.reset(self.modelname)
352
- self.set_mesh(mesh)
432
+ self._set_mesh(mesh)
353
433
  self.mw.reset()
354
434
 
355
435
  params = {key: dim[i_iter] for key,dim in zip(paramlist, dims_flat)}
@@ -363,81 +443,12 @@ class Simulation3D:
363
443
  yield (dim[i_iter] for dim in dims_flat)
364
444
  self.mw.cache_matrices = True
365
445
 
366
- def __enter__(self) -> Simulation3D:
367
- """This method is depricated with the new atexit system. It still exists for backwards compatibility.
368
-
369
- Returns:
370
- Simulation3D: the Simulation3D object
371
- """
372
- return self
446
+ ############################################################
447
+ # DEPRICATED FUNCTIONS #
448
+ ############################################################
373
449
 
374
- def __exit__(self, type, value, tb):
375
- """This method no longer does something. It only serves as backwards compatibility."""
376
- self._exit_gmsh()
377
- return False
378
-
379
- def _install_signal_handlers(self):
380
- # on SIGINT (Ctrl-C) or SIGTERM, call our exit routine
381
- for sig in (signal.SIGINT, signal.SIGTERM):
382
- signal.signal(sig, self._handle_signal)
383
-
384
- def _handle_signal(self, signum, frame):
385
- """
386
- Signal handler: do our cleanup, then re-raise
387
- the default handler so that exit code / traceback
388
- is as expected.
450
+ def define_geometry(self, *args):
451
+ """DEPRICATED VERSION: Use .commit_geometry()
389
452
  """
390
- try:
391
- # run your atexit-style cleanup
392
- self._exit_gmsh()
393
- except Exception:
394
- # log but don’t block shutdown
395
- logger.exception("Error during signal cleanup")
396
- finally:
397
- # restore default handler and re‐send the signal
398
- signal.signal(signum, signal.SIG_DFL)
399
- os.kill(os.getpid(), signum)
400
-
401
-
402
- def _initialize_simulation(self):
403
- """Initializes the Simulation data and GMSH API with proper shutdown routines.
404
- """
405
- _GEOMANAGER.sign_in(self.modelname)
406
-
407
- # If GMSH is not yet initialized (Two simulation in a file)
408
- if gmsh.isInitialized() == 0:
409
- logger.debug('Initializing GMSH')
410
- gmsh.initialize()
411
-
412
- # Set an exit handler for Ctrl+C cases
413
- self._install_signal_handlers()
414
-
415
- # Restier the Exit GMSH function on proper program abortion
416
- register(self._exit_gmsh)
417
-
418
- # Create a new GMSH model or load it
419
- if not self.load_file:
420
- gmsh.model.add(self.modelname)
421
- self.data: SimulationDataset = SimulationDataset()
422
- else:
423
- self.load()
424
-
425
- # Set the Simulation state to active
426
- self.__active = True
427
- return self
428
-
429
- def _exit_gmsh(self):
430
- # If the simulation object state is still active (GMSH is running)
431
- if not self.__active:
432
- return
433
- logger.debug('Exiting program')
434
- # Save the file first
435
- if self.save_file:
436
- self.save()
437
- # Finalize GMSH
438
- gmsh.finalize()
439
- logger.debug('GMSH Shut down successful')
440
- # set the state to active
441
- self.__active = False
442
-
443
-
453
+ logger.warning('define_geometry() will be derpicated. Use commit_geometry() instead.')
454
+ self.commit_geometry(*args)