emerge 0.6.11__py3-none-any.whl → 1.0.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.

@@ -26,7 +26,9 @@ from .logsettings import LOG_CONTROLLER
26
26
  from .plot.pyvista import PVDisplay
27
27
  from .dataset import SimulationDataset
28
28
  from .periodic import PeriodicCell
29
- from .bc import BoundaryCondition
29
+ from .cacherun import get_build_section, get_run_section
30
+ from .settings import DEFAULT_SETTINGS, Settings
31
+ from .solver import EMSolver, Solver
30
32
  from typing import Literal, Generator, Any
31
33
  from loguru import logger
32
34
  import numpy as np
@@ -94,21 +96,26 @@ class Simulation:
94
96
 
95
97
  self.mesh: Mesh3D = Mesh3D(self.mesher)
96
98
  self.select: Selector = Selector()
99
+
100
+ self.settings: Settings = DEFAULT_SETTINGS
97
101
 
102
+ ## Display
103
+ self.display: PVDisplay = PVDisplay(self.mesh)
104
+
105
+ ## Dataset
106
+ self.data: SimulationDataset = SimulationDataset()
107
+
98
108
  ## STATES
99
109
  self.__active: bool = False
100
110
  self._defined_geometries: bool = False
101
111
  self._cell: PeriodicCell | None = None
102
-
103
- self.display: PVDisplay = PVDisplay(self.mesh)
104
-
105
112
  self.save_file: bool = save_file
106
113
  self.load_file: bool = load_file
107
-
108
- self.data: SimulationDataset = SimulationDataset()
109
-
114
+ self._cache_run: bool = False
115
+ self._file_lines: str = ''
116
+
110
117
  ## Physics
111
- self.mw: Microwave3D = Microwave3D(self.mesher, self.data.mw)
118
+ self.mw: Microwave3D = Microwave3D(self.mesher, self.settings, self.data.mw)
112
119
 
113
120
  self._initialize_simulation()
114
121
 
@@ -216,14 +223,6 @@ class Simulation:
216
223
  def _update_data(self) -> None:
217
224
  """Writes the stored physics data to each phyics class insatnce"""
218
225
  self.mw.data = self.data.mw
219
-
220
- def all_geometries(self) -> list[GeoObject]:
221
- """Returns all geometries stored in the simulation file."""
222
- return [obj for obj in self.data.sim.default.values() if isinstance(obj, GeoObject)]
223
-
224
- def all_bcs(self) -> list[BoundaryCondition]:
225
- """Returns all boundary condition objects stored in the simulation file"""
226
- return [obj for obj in self.data.sim.default.values() if isinstance(obj, BoundaryCondition)]
227
226
 
228
227
  def _set_mesh(self, mesh: Mesh3D) -> None:
229
228
  """Set the current model mesh to a given mesh."""
@@ -235,34 +234,141 @@ class Simulation:
235
234
  # PUBLIC FUNCTIONS #
236
235
  ############################################################
237
236
 
238
- def check_version(self, version: str) -> None:
239
- """Compares the provided version number with the version number of EMerge that is running the script.
237
+ def cache_build(self) -> bool:
238
+ """Checks if all the lines inside this if statement block are the same as those
239
+ stored from a previous run. If so, then it returns false. Else it returns True.
240
240
 
241
- You may remove any call to check_version to suppress VersionErrors and warnings.
241
+ Can be used to capture an entire model simulation.
242
242
 
243
- Args:
244
- version (str): The EMerge version you intend to write this code for.
243
+ Example:
244
+
245
+ >>> if model.cache_build():
246
+ >>> box = em.geo.Box(...)
247
+ >>> # Other lines
248
+ >>> model.mw.run_sweep()
249
+ >>> data = model.data.mw
245
250
 
246
- Raises:
247
- VersionError: A potential version error if incompatibility is possible
251
+ Returns:
252
+ bool: If the code is not the same
253
+ """
254
+
255
+ self.save_file = True
256
+ self._cache_run = True
257
+ filestr = get_build_section()
258
+ self._file_lines = filestr
259
+ cachepath = self.modelpath / 'pylines.txt'
260
+
261
+ # If there is not pylines file, simulate (can't have been run).
262
+ if not cachepath.exists():
263
+ logger.info('No cached data detected, running file')
264
+ return True
265
+
266
+ with open(cachepath, 'r') as file:
267
+ lines = file.read()
268
+
269
+ if lines==filestr:
270
+ logger.info('Cached data detected! Loading data!')
271
+ self.load()
272
+ return False
273
+ logger.info('Different cached data detected, rebuilding file.')
274
+ return True
275
+
276
+ def cache_run(self) -> bool:
277
+ """Checks if all the lines before this call are the same as the lines
278
+ stored from a previous run. If so, then it returns false. Else it returns True.
279
+
280
+ Can be used to capture a run_sweep() call.
281
+
282
+ Example:
283
+
284
+ >>> if model.cache_run():
285
+ >>> model.mw.run_sweep()
286
+ >>> data = model.data.mw
287
+
288
+ Returns:
289
+ bool: If the code is not the same
290
+ """
291
+ self.save_file = True
292
+ self._cache_run = True
293
+ filestr = get_run_section()
294
+ self._file_lines = filestr
295
+ cachepath = self.modelpath / 'pylines.txt'
296
+
297
+ # If there is not pylines file, simulate (can't have been run).
298
+ if not cachepath.exists():
299
+ logger.info('No cached data detected, running simulation!')
300
+ return True
301
+
302
+ with open(cachepath, 'r') as file:
303
+ lines = file.read()
304
+
305
+ if lines==filestr:
306
+ logger.info('Cached data detected! Loading data!')
307
+ self.load()
308
+ return False
309
+ logger.info('Different cached data detected, rerunning simulation.')
310
+ return True
311
+
312
+ def check_version(self, target_version: str, *, log: bool = False) -> None:
313
+ """
314
+ Ensure the script targets an EMerge version compatible with the current runtime.
315
+
316
+ Parameters
317
+ ----------
318
+ target_version : str
319
+ The EMerge version this script was written for (e.g. "1.4.0").
320
+ log : bool, optional
321
+ If True and a `logger` is available, emit a single WARNING with the same
322
+ message as the exception. Defaults to False.
323
+
324
+ Raises
325
+ ------
326
+ VersionError
327
+ If the script's target version differs from the running EMerge version.
248
328
  """
249
- vM, vm, vp = [float(x) for x in version.split('.')]
250
- cM, cm, cp = [float(x) for x in __version__.split('.')]
251
- if vM != cM:
252
- raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}. \n You can upgrade your version of emerge with: pip --upgrade emerge")
253
- if vm != cm:
254
- raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}. \n You can upgrade your version of emerge with: pip --upgrade emerge")
255
- if vp != cp:
256
- logger.warning("You are running a script designed for a different version of EMerge.")
257
- logger.warning(f"The script version: {version}")
258
- logger.warning(f"EMerge version: {__version__}")
259
- logger.warning("Usually EMerge works without a problem but Errors may occur.")
260
- logger.warning("You can upgrade your version of emerge with: pip --upgrade emerge")
261
- logger.warning("You may suppress this error by removing the call to .check_version().")
262
- logger.warning("Press Ctrl+C to abort.")
263
- ans = input('Press enter to proceed or [Q] to quit:')
264
- if ans.lower().strip()=='q':
265
- quit()
329
+ try:
330
+ from packaging.version import Version as _V
331
+ v_script = _V(target_version)
332
+ v_runtime = _V(__version__)
333
+ newer = v_script > v_runtime
334
+ older = v_script < v_runtime
335
+ except Exception:
336
+ def _parse(v: str):
337
+ try:
338
+ return tuple(int(p) for p in v.split("."))
339
+ except Exception:
340
+ # Last-resort: compare as strings to avoid crashing the check itself
341
+ return tuple(v.split("."))
342
+ v_script = _parse(target_version)
343
+ v_runtime = _parse(__version__)
344
+ newer = v_script > v_runtime
345
+ older = v_script < v_runtime
346
+
347
+ if not newer and not older:
348
+ return # exact match
349
+
350
+ if newer:
351
+ msg = (
352
+ f"Script targets EMerge {target_version}, but runtime is {__version__}. "
353
+ "The script may rely on features added after your installed version. "
354
+ "Recommended: upgrade EMerge (`pip install --upgrade emerge`). "
355
+ "If you know the script is compatible, you may remove this check."
356
+ )
357
+ else: # older
358
+ msg = (
359
+ f"Script targets EMerge {target_version}, but runtime is {__version__}. "
360
+ "APIs may have changed since the targeted version. "
361
+ "Recommended: update the script for the current EMerge, or run a matching older release. "
362
+ "If you know the script is compatible, you may remove this check."
363
+ )
364
+
365
+ if log:
366
+ try:
367
+ logger.warning(msg)
368
+ except Exception:
369
+ pass
370
+
371
+ raise VersionError(msg)
266
372
 
267
373
  def save(self) -> None:
268
374
  """Saves the current model in the provided project directory."""
@@ -289,6 +395,12 @@ class Simulation:
289
395
  data_path = self.modelpath / 'simdata.emerge'
290
396
  with open(str(data_path), "wb") as f_out:
291
397
  cloudpickle.dump(dataset, f_out)
398
+
399
+ if self._cache_run:
400
+ cachepath = self.modelpath / 'pylines.txt'
401
+ with open(str(cachepath), 'w') as f_out:
402
+ f_out.write(self._file_lines)
403
+
292
404
  logger.info(f"Saved simulation data to: {data_path}")
293
405
 
294
406
  def load(self) -> None:
@@ -334,7 +446,8 @@ class Simulation:
334
446
  use_gmsh: bool = False,
335
447
  plot_mesh: bool = False,
336
448
  volume_mesh: bool = True,
337
- opacity: float | None = None) -> None:
449
+ opacity: float | None = None,
450
+ labels: bool = False) -> None:
338
451
  """View the current geometry in either the BaseDisplay object (PVDisplay only) or
339
452
  the GMSH viewer.
340
453
 
@@ -344,21 +457,21 @@ class Simulation:
344
457
  plot_mesh (bool, optional): If the mesh should be plot instead of the object. Defaults to False.
345
458
  volume_mesh (bool, optional): If the internal mesh should be plot instead of only the surface boundary mesh. Defaults to True
346
459
  opacity (float | None, optional): The object/mesh opacity. Defaults to None.
347
-
460
+ labels: (bool, optional): If geometry name labels should be shown. Defaults to False.
348
461
  """
349
462
  if not (self.display is not None and self.mesh.defined) or use_gmsh:
350
463
  gmsh.model.occ.synchronize()
351
464
  gmsh.fltk.run()
352
465
  return
353
466
  for geo in _GEOMANAGER.all_geometries():
354
- self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh)
467
+ self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh, label=labels)
355
468
  if selections:
356
- [self.display.add_object(sel, color='red', opacity=0.3) for sel in selections]
469
+ [self.display.add_object(sel, color='red', opacity=0.3, label=labels) for sel in selections]
357
470
  self.display.show()
358
471
 
359
472
  return None
360
473
 
361
- def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] | None = None):
474
+ def set_periodic_cell(self, cell: PeriodicCell, included_faces: FaceSelection | None = None):
362
475
  """Set the given periodic cell object as the simulations peridicity.
363
476
 
364
477
  Args:
@@ -367,6 +480,7 @@ class Simulation:
367
480
  """
368
481
  self.mw.bc._cell = cell
369
482
  self._cell = cell
483
+ self._cell.included_faces = included_faces
370
484
 
371
485
  def commit_geometry(self, *geometries: GeoObject | list[GeoObject]) -> None:
372
486
  """Finalizes and locks the current geometry state of the simulation.
@@ -380,11 +494,19 @@ class Simulation:
380
494
  else:
381
495
  geometries_parsed = unpack_lists(geometries + tuple([item for item in self.data.sim.default.values() if isinstance(item, GeoObject)]))
382
496
 
383
- self.data.sim['geometries'] = geometries_parsed
497
+ self.data.sim['geos'] = {geo.name: geo for geo in geometries_parsed}
384
498
  self.mesher.submit_objects(geometries_parsed)
385
499
  self._defined_geometries = True
386
500
  self.display._facetags = [dt[1] for dt in gmsh.model.get_entities(2)]
387
-
501
+
502
+ def all_geos(self) -> list[GeoObject]:
503
+ """Returns all geometries in a list
504
+
505
+ Returns:
506
+ list[GeoObject]: A list of all GeoObjects
507
+ """
508
+ return _GEOMANAGER.all_geometries()
509
+
388
510
  def generate_mesh(self) -> None:
389
511
  """Generate the mesh.
390
512
  This can only be done after commit_geometry(...) is called and if frequencies are defined.
@@ -402,7 +524,7 @@ class Simulation:
402
524
  if self._cell is not None:
403
525
  self.mesher.set_periodic_cell(self._cell)
404
526
 
405
- self.mw._initialize_bcs()
527
+ self.mw._initialize_bcs(_GEOMANAGER.get_surfaces())
406
528
 
407
529
  # Check if frequencies are defined: TODO: Replace with a more generic check
408
530
  if self.mw.frequencies is None:
@@ -424,9 +546,11 @@ class Simulation:
424
546
  logger.error('GMSH Mesh error detected.')
425
547
  print(_GMSH_ERROR_TEXT)
426
548
  raise
549
+
427
550
  self.mesh.update(self.mesher._get_periodic_bcs())
428
551
  self.mesh.exterior_face_tags = self.mesher.domain_boundary_face_tags
429
552
  gmsh.model.occ.synchronize()
553
+
430
554
  self._set_mesh(self.mesh)
431
555
 
432
556
  def parameter_sweep(self, clear_mesh: bool = True, **parameters: np.ndarray) -> Generator[tuple[float,...], None, None]:
@@ -490,6 +614,16 @@ class Simulation:
490
614
  filename (str): The filename
491
615
  """
492
616
  gmsh.write(filename)
617
+
618
+ def set_solver(self, solver: EMSolver | Solver):
619
+ """Set a given Solver class instance as the main solver.
620
+ Solvers will be checked on validity for the given problem.
621
+
622
+ Args:
623
+ solver (EMSolver | Solver): The solver objects
624
+ """
625
+ self.mw.solveroutine.set_solver(solver)
626
+
493
627
  ############################################################
494
628
  # DEPRICATED FUNCTIONS #
495
629
  ############################################################
emerge/_emerge/solver.py CHANGED
@@ -15,8 +15,8 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
-
19
18
  from __future__ import annotations
19
+
20
20
  from scipy.sparse import csr_matrix # type: ignore
21
21
  from scipy.sparse.csgraph import reverse_cuthill_mckee # type: ignore
22
22
  from scipy.sparse.linalg import bicgstab, gmres, gcrotmk, eigs, splu # type: ignore
@@ -286,6 +286,7 @@ class Solver:
286
286
  """
287
287
  real_only: bool = False
288
288
  req_sorter: bool = False
289
+ released_gil: bool = False
289
290
 
290
291
  def __init__(self):
291
292
  self.own_preconditioner: bool = False
@@ -479,7 +480,8 @@ class SolverSuperLU(Solver):
479
480
  """ Implements Scipi's direct SuperLU solver."""
480
481
  req_sorter: bool = False
481
482
  real_only: bool = False
482
-
483
+ released_gil: bool = True
484
+
483
485
  def __init__(self):
484
486
  super().__init__()
485
487
  self.atol = 1e-5
@@ -502,6 +504,7 @@ class SolverSuperLU(Solver):
502
504
 
503
505
  def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
504
506
  logger.info(f'[ID={id}] Calling SuperLU Solver.')
507
+
505
508
  self.single = True
506
509
  if not reuse_factorization:
507
510
  logger.trace('Computing LU-Decomposition')
@@ -903,6 +906,10 @@ class SolveRoutine:
903
906
  bool: If the solver is legal
904
907
  """
905
908
  if any(isinstance(solver, solvertype) for solvertype in self.disabled_solver):
909
+ logger.warning(f'The selected solver {solver} cannot be used as it is disabled.')
910
+ return False
911
+ if self.parallel=='MT' and not solver.released_gil:
912
+ logger.warning(f'The selected solver {solver} cannot be used in MultiThreading as it does not release the GIL')
906
913
  return False
907
914
  return True
908
915
 
emerge/beta/dxf.py ADDED
@@ -0,0 +1 @@
1
+ from .._emerge.geo.pcb_tools.dxf import import_dxf
emerge/lib.py CHANGED
@@ -12,8 +12,10 @@
12
12
  ║ Verify critical values independently before use. ║
13
13
  ╚══════════════════════════════════════════════════════════════════════╝
14
14
  """
15
- from ._emerge.material import Material, AIR, COPPER
15
+ from ._emerge.material import Material, AIR, COPPER, PEC
16
16
  from ._emerge.const import C0, Z0, PI, EPS0, MU0
17
+ from .materials import isola
18
+ from .materials import rogers
17
19
 
18
20
  EISO: float = (Z0/(2*PI))**0.5
19
21
  EOMNI = (3*Z0/(4*PI))**0.5
@@ -280,6 +282,7 @@ DIEL_XT_Duroid_8100 = Material(er=3.54, tand=0.0049, color="#21912b
280
282
  DIEL_XT_Duroid_81000_004IN_Thick = Material(er=3.32, tand=0.0038, color="#21912b", opacity=0.3, name="Rogers XT/duroid 81000 0.004in")
281
283
  DIEL_TEFLON = Material(er=2.1, tand=0.0003, color='#eeeeee', opacity=0.3, name="Teflon")
282
284
 
285
+ DIEL_IS420_approx = Material(er=4.5, tand=0.018, color="#CCDE30", opacity=0.3, name="ISOLA420 approximate")
283
286
 
284
287
  # Legacy FR Materials
285
288
  DIEL_FR1 = Material(er=4.8, tand=0.025, color="#3c9747", opacity=0.3, name="FR-1 (Paper Phenolic)")
@@ -0,0 +1 @@
1
+ from . import isola