emerge 1.0.7__py3-none-any.whl → 1.1.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.

Files changed (33) hide show
  1. emerge/__init__.py +15 -3
  2. emerge/_emerge/const.py +2 -1
  3. emerge/_emerge/elements/ned2_interp.py +122 -42
  4. emerge/_emerge/geo/__init__.py +1 -1
  5. emerge/_emerge/geo/operations.py +20 -0
  6. emerge/_emerge/geo/pcb.py +162 -71
  7. emerge/_emerge/geo/shapes.py +12 -7
  8. emerge/_emerge/geo/step.py +177 -41
  9. emerge/_emerge/geometry.py +189 -27
  10. emerge/_emerge/logsettings.py +26 -2
  11. emerge/_emerge/material.py +2 -0
  12. emerge/_emerge/mesh3d.py +6 -8
  13. emerge/_emerge/mesher.py +67 -11
  14. emerge/_emerge/mth/common_functions.py +1 -1
  15. emerge/_emerge/mth/optimized.py +2 -2
  16. emerge/_emerge/physics/microwave/adaptive_mesh.py +549 -116
  17. emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
  18. emerge/_emerge/physics/microwave/microwave_3d.py +133 -83
  19. emerge/_emerge/physics/microwave/microwave_bc.py +158 -8
  20. emerge/_emerge/physics/microwave/microwave_data.py +94 -5
  21. emerge/_emerge/plot/pyvista/display.py +36 -23
  22. emerge/_emerge/selection.py +17 -2
  23. emerge/_emerge/settings.py +124 -6
  24. emerge/_emerge/simmodel.py +273 -150
  25. emerge/_emerge/simstate.py +106 -0
  26. emerge/_emerge/simulation_data.py +11 -23
  27. emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
  28. emerge/_emerge/solver.py +4 -4
  29. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/METADATA +7 -3
  30. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/RECORD +33 -32
  31. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
  32. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
  33. {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -16,19 +16,20 @@
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
18
  from __future__ import annotations
19
- from .mesher import Mesher, unpack_lists
20
- from .geometry import GeoObject, _GEOMANAGER
19
+ from .mesher import Mesher
20
+ from .geometry import GeoObject
21
21
  from .geo.modeler import Modeler
22
22
  from .physics.microwave.microwave_3d import Microwave3D
23
23
  from .mesh3d import Mesh3D
24
- from .selection import Selector, FaceSelection, Selection
25
- from .logsettings import LOG_CONTROLLER
26
- from .plot.pyvista import PVDisplay
24
+ from .selection import Selector, Selection
27
25
  from .dataset import SimulationDataset
26
+ from .logsettings import LOG_CONTROLLER, DEBUG_COLLECTOR
27
+ from .plot.pyvista import PVDisplay
28
28
  from .periodic import PeriodicCell
29
29
  from .cacherun import get_build_section, get_run_section
30
30
  from .settings import DEFAULT_SETTINGS, Settings
31
31
  from .solver import EMSolver, Solver
32
+ from .simstate import SimState
32
33
  from typing import Literal, Generator, Any
33
34
  from loguru import logger
34
35
  import numpy as np
@@ -40,7 +41,7 @@ import joblib
40
41
  from atexit import register
41
42
  import signal
42
43
  from .. import __version__
43
-
44
+ from .mesher import Algorithm3D
44
45
  ############################################################
45
46
  # EXCEPTION DEFINITIONS #
46
47
  ############################################################
@@ -63,6 +64,7 @@ class VersionError(Exception):
63
64
  # BASE 3D SIMULATION MODEL #
64
65
  ############################################################
65
66
 
67
+
66
68
  class Simulation:
67
69
 
68
70
  def __init__(self,
@@ -94,16 +96,12 @@ class Simulation:
94
96
  self.mesher: Mesher = Mesher()
95
97
  self.modeler: Modeler = Modeler()
96
98
 
97
- self.mesh: Mesh3D = Mesh3D(self.mesher)
99
+ self.state: SimState = SimState()
98
100
  self.select: Selector = Selector()
99
-
100
101
  self.settings: Settings = DEFAULT_SETTINGS
101
102
 
102
103
  ## Display
103
- self.display: PVDisplay = PVDisplay(self.mesh)
104
-
105
- ## Dataset
106
- self.data: SimulationDataset = SimulationDataset()
104
+ self.display: PVDisplay = PVDisplay(self.state)
107
105
 
108
106
  ## STATES
109
107
  self.__active: bool = False
@@ -115,7 +113,7 @@ class Simulation:
115
113
  self._file_lines: str = ''
116
114
 
117
115
  ## Physics
118
- self.mw: Microwave3D = Microwave3D(self.mesher, self.settings, self.data.mw)
116
+ self.mw: Microwave3D = Microwave3D(self.state, self.mesher, self.settings)
119
117
 
120
118
  self._initialize_simulation()
121
119
 
@@ -125,16 +123,25 @@ class Simulation:
125
123
  self.set_write_log()
126
124
 
127
125
  LOG_CONTROLLER._flush_log_buffer()
128
-
129
126
  LOG_CONTROLLER._sys_info()
130
-
131
- self._update_data()
132
-
127
+
128
+ self.__post_init__()
133
129
 
134
130
  ############################################################
135
131
  # PRIVATE FUNCTIONS #
136
132
  ############################################################
137
133
 
134
+ @property
135
+ def data(self) -> SimulationDataset:
136
+ return self.state.data
137
+
138
+ @property
139
+ def mesh(self) -> Mesh3D:
140
+ return self.state.mesh
141
+
142
+ def __post_init__(self):
143
+ pass
144
+
138
145
  def __setitem__(self, name: str, value: Any) -> None:
139
146
  """Store data in the current data container"""
140
147
  self.data.sim[name] = value
@@ -181,13 +188,13 @@ class Simulation:
181
188
  def _initialize_simulation(self):
182
189
  """Initializes the Simulation data and GMSH API with proper shutdown routines.
183
190
  """
184
- _GEOMANAGER.sign_in(self.modelname)
191
+ self.state.init(self.modelname)
185
192
 
186
193
  # If GMSH is not yet initialized (Two simulation in a file)
187
194
  if gmsh.isInitialized() == 0:
188
195
  logger.debug('Initializing GMSH')
189
- gmsh.initialize()
190
196
 
197
+ gmsh.initialize()
191
198
  # Set an exit handler for Ctrl+C cases
192
199
  self._install_signal_handlers()
193
200
 
@@ -200,7 +207,6 @@ class Simulation:
200
207
  # Create a new GMSH model or load it
201
208
  if not self.load_file:
202
209
  gmsh.model.add(self.modelname)
203
- self.data: SimulationDataset = SimulationDataset()
204
210
  else:
205
211
  self.load()
206
212
 
@@ -213,6 +219,11 @@ class Simulation:
213
219
  if not self.__active:
214
220
  return
215
221
  logger.debug('Exiting program')
222
+
223
+ if DEBUG_COLLECTOR.any_warnings:
224
+ logger.warning('EMerge simulation warnings:')
225
+ for i, report in DEBUG_COLLECTOR.all_reports():
226
+ logger.warning(f'{i}: {report}')
216
227
  # Save the file first
217
228
  if self.save_file:
218
229
  self.save()
@@ -223,25 +234,8 @@ class Simulation:
223
234
  logger.debug('GMSH Shut down successful')
224
235
  # set the state to active
225
236
  self.__active = False
226
-
227
- def _update_data(self) -> None:
228
- """Writes the stored physics data to each phyics class insatnce"""
229
- self.mw.data = self.data.mw
230
-
231
- def _set_mesh(self, mesh: Mesh3D) -> None:
232
- """Set the current model mesh to a given mesh."""
233
- logger.trace(f'Setting {mesh} as model mesh')
234
- self.mesh = mesh
235
- self.mw.mesh = mesh
236
- self.display._mesh = mesh
237
+
237
238
 
238
- def _save_geometries(self) -> None:
239
- """Saves the current geometry state to the simulatin dataset
240
- """
241
- logger.trace('Storing geometries in data.sim')
242
- self.data.sim['geos'] = {geo.name: geo for geo in _GEOMANAGER.all_geometries()}
243
- self.data.sim['mesh'] = self.mesh
244
- self.data.sim.entries.append(self.data.sim.stock)
245
239
  ############################################################
246
240
  # PUBLIC FUNCTIONS #
247
241
  ############################################################
@@ -382,6 +376,11 @@ class Simulation:
382
376
 
383
377
  raise VersionError(msg)
384
378
 
379
+ def activate(self, _indx: int | None = None, **variables) -> Simulation:
380
+ """Searches for the permutaions of parameter sweep variables and sets the current geometry to the provided set."""
381
+ self.state.activate(_indx, **variables)
382
+ return self
383
+
385
384
  def save(self) -> None:
386
385
  """Saves the current model in the provided project directory."""
387
386
  # Ensure directory exists
@@ -403,7 +402,7 @@ class Simulation:
403
402
  logger.info(f"Saved mesh to: {mesh_path}")
404
403
 
405
404
  # Pack and save data
406
- dataset = dict(simdata=self.data, mesh=self.mesh)
405
+ dataset = self.state.get_dataset()
407
406
  data_path = self.modelpath / 'simdata.emerge'
408
407
 
409
408
  joblib.dump(dataset, str(data_path))
@@ -424,17 +423,16 @@ class Simulation:
424
423
  if not mesh_path.exists() or not data_path.exists():
425
424
  raise FileNotFoundError("Missing required mesh or data file.")
426
425
 
427
- # Load mesh
426
+ # Load GMSH Mesh (Ideally Id remove)
428
427
  gmsh.open(str(brep_path))
429
428
  gmsh.merge(str(mesh_path))
430
429
  gmsh.model.geo.synchronize()
431
430
  gmsh.model.occ.synchronize()
432
- logger.info(f"Loaded mesh from: {mesh_path}")
433
431
 
432
+ logger.info(f"Loaded mesh from: {mesh_path}")
434
433
  datapack = joblib.load(str(data_path))
435
-
436
- self.data = datapack['simdata']
437
- self.activate(0)
434
+ self.state.load_dataset(datapack)
435
+ self.state.activate(0)
438
436
 
439
437
  logger.info(f"Loaded simulation data from: {data_path}")
440
438
 
@@ -446,7 +444,7 @@ class Simulation:
446
444
  """
447
445
  logger.trace(f'Setting loglevel to {loglevel}')
448
446
  LOG_CONTROLLER.set_std_loglevel(loglevel)
449
- if loglevel not in ('TRACE','DEBUG'):
447
+ if loglevel not in ('TRACE'):
450
448
  gmsh.option.setNumber("General.Terminal", 0)
451
449
 
452
450
  def set_write_log(self) -> None:
@@ -460,7 +458,8 @@ class Simulation:
460
458
  plot_mesh: bool = False,
461
459
  volume_mesh: bool = True,
462
460
  opacity: float | None = None,
463
- labels: bool = False) -> None:
461
+ labels: bool = False,
462
+ face_labels: bool = False) -> None:
464
463
  """View the current geometry in either the BaseDisplay object (PVDisplay only) or
465
464
  the GMSH viewer.
466
465
 
@@ -476,10 +475,15 @@ class Simulation:
476
475
  gmsh.model.occ.synchronize()
477
476
  gmsh.fltk.run()
478
477
  return
479
- for geo in _GEOMANAGER.all_geometries():
478
+ for geo in self.state.current_geo_state:
480
479
  self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh, label=labels)
480
+
481
+ if face_labels and geo.dim==3:
482
+ for face_name in geo._face_pointers.keys():
483
+ self.display.add_object(geo.face(face_name), color='yellow', opacity=0.1, label=face_name)
481
484
  if selections:
482
- [self.display.add_object(sel, color='red', opacity=0.6, label=labels) for sel in selections]
485
+ [self.display.add_object(sel, color='red', opacity=0.6, label=sel.name) for sel in selections]
486
+
483
487
  self.display.show()
484
488
 
485
489
  return None
@@ -512,17 +516,13 @@ class Simulation:
512
516
  The geometries may be provided (legacy behavior) but are automatically managed in the background.
513
517
 
514
518
  """
515
- geometries_parsed: Any = None
516
519
  logger.trace('Committing final geometry.')
517
- if not geometries:
518
- geometries_parsed = _GEOMANAGER.all_geometries()
519
- else:
520
- geometries_parsed = unpack_lists(geometries + tuple([item for item in self.data.sim.default.values() if isinstance(item, GeoObject)]))
521
- logger.trace(f'Parsed geometries = {geometries_parsed}')
520
+ self.state.store_geometry_data()
522
521
 
523
- self._save_geometries()
522
+ logger.trace(f'Parsed geometries = {self.state.geos}')
523
+
524
+ self.mesher.submit_objects(self.state.geos)
524
525
 
525
- self.mesher.submit_objects(geometries_parsed)
526
526
  self._defined_geometries = True
527
527
  self.display._facetags = [dt[1] for dt in gmsh.model.get_entities(2)]
528
528
 
@@ -532,20 +532,7 @@ class Simulation:
532
532
  Returns:
533
533
  list[GeoObject]: A list of all GeoObjects
534
534
  """
535
- return _GEOMANAGER.all_geometries()
536
-
537
- def activate(self, _indx: int | None = None, **variables) -> Simulation:
538
- """Searches for the permutaions of parameter sweep variables and sets the current geometry to the provided set."""
539
- if _indx is not None:
540
- dataset = self.data.sim.index(_indx)
541
- else:
542
- dataset = self.data.sim.find(**variables)
543
-
544
- variables = ', '.join([f'{key}={value}' for key,value in dataset.vars.items()])
545
- logger.info(f'Activated entry with variables: {variables}')
546
- _GEOMANAGER.set_geometries(dataset['geos'])
547
- self._set_mesh(dataset['mesh'])
548
- return self
535
+ return self.state.current_geo_state
549
536
 
550
537
  def generate_mesh(self, regenerate: bool = False) -> None:
551
538
  """Generate the mesh.
@@ -557,8 +544,9 @@ class Simulation:
557
544
  Raises:
558
545
  ValueError: ValueError if no frequencies are defined.
559
546
  """
547
+ logger.info('Starting mesh generation phase.')
560
548
  if not regenerate:
561
- logger.trace('Starting mesh generation phase.')
549
+
562
550
  if not self._defined_geometries:
563
551
  self.commit_geometry()
564
552
 
@@ -567,7 +555,7 @@ class Simulation:
567
555
  if self._cell is not None:
568
556
  self.mesher.set_periodic_cell(self._cell)
569
557
 
570
- self.mw._initialize_bcs(_GEOMANAGER.get_surfaces())
558
+ self.mw._initialize_bcs(self.state.manager.get_surfaces())
571
559
 
572
560
  # Check if frequencies are defined: TODO: Replace with a more generic check
573
561
  if self.mw.frequencies is None:
@@ -576,8 +564,18 @@ class Simulation:
576
564
  gmsh.model.occ.synchronize()
577
565
 
578
566
  # Set the mesh size
579
- self.mesher._configure_mesh_size(self.mw.get_discretizer(), self.mw.resolution)
567
+ self.mesher._configure_mesh_size(self.mw.get_discretizer(), self.mw.resolution) # This makes no sense to do this here
580
568
 
569
+ # Validity check
570
+ x1, y1, z1, x2, y2, z2 = gmsh.model.getBoundingBox(-1, -1)
571
+ bb_volume = (x2-x1)*(y2-y1)*(z2-z1)
572
+ wl = 299792458/self.mw.frequencies[-1]
573
+ Nelem = int(5 * bb_volume / (wl**3))
574
+ if Nelem > 100_000 and DEFAULT_SETTINGS.size_check:
575
+ DEBUG_COLLECTOR.add_report(f'An estimated {Nelem} tetrahedra are required for the bounding box of the geometry. This may imply a simulation domain that is very large.' +
576
+ 'To disable this message. Set the .size_check parameter in model.settings to False.')
577
+
578
+ raise SimulationError('Simulation requires too many elements.')
581
579
  logger.trace(' (2) Calling GMSH mesher')
582
580
  try:
583
581
  gmsh.logger.start()
@@ -595,16 +593,7 @@ class Simulation:
595
593
  self.mesh._pre_update(self.mesher._get_periodic_bcs())
596
594
  self.mesh.exterior_face_tags = self.mesher.domain_boundary_face_tags
597
595
  gmsh.model.occ.synchronize()
598
- self._set_mesh(self.mesh)
599
596
  logger.trace(' (3) Mesh routine complete')
600
-
601
- def _reset_mesh(self):
602
- #gmsh.clear()
603
- gmsh.model.mesh.clear()
604
- mesh = Mesh3D(self.mesher)
605
-
606
- self.mw.reset(False)
607
- self._set_mesh(mesh)
608
597
 
609
598
  def parameter_sweep(self, clear_mesh: bool = True, **parameters: np.ndarray) -> Generator[tuple[float,...], None, None]:
610
599
  """Executes a parameteric sweep iteration.
@@ -641,14 +630,13 @@ class Simulation:
641
630
  if clear_mesh and i_iter > 0:
642
631
  logger.info('Cleaning up mesh.')
643
632
  gmsh.clear()
644
- mesh = Mesh3D(self.mesher)
645
- _GEOMANAGER.reset(self.modelname)
646
- self._set_mesh(mesh)
633
+ self.state.reset_geostate(self.modelname)
647
634
  self.mw.reset()
635
+
648
636
 
649
637
  params = {key: dim[i_iter] for key,dim in zip(paramlist, dims_flat)}
650
- self.mw._params = params
651
- self.data.sim.new(**params)
638
+
639
+ self.state.set_parameters(params)
652
640
 
653
641
  logger.info(f'Iterating: {params}')
654
642
  if len(dims_flat)==1:
@@ -656,75 +644,16 @@ class Simulation:
656
644
  else:
657
645
  yield (dim[i_iter] for dim in dims_flat) # type: ignore
658
646
 
647
+
659
648
  if not clear_mesh:
660
- self._save_geometries()
649
+ self.state.store_geometry_data()
661
650
 
662
651
  if not clear_mesh:
663
- self._save_geometries()
652
+ self.state.store_geometry_data()
664
653
 
665
654
  self.mw.cache_matrices = True
666
-
667
-
668
- def _beta_adaptive_mesh_refinement(self,
669
- max_steps: int = 6,
670
- convergence: float = 0.02,
671
- refinement_percentage: float = 0.1,
672
- refinement_ratio: float = 0.3,
673
- growth_rate: float = 3) -> None:
674
- """Beta implementation of Adaptive Mesh Refinement
675
-
676
- Args:
677
- max_steps (int, optional): _description_. Defaults to 6.
678
- convergence (float, optional): _description_. Defaults to 0.02.
679
- refinement_percentage (float, optional): _description_. Defaults to 0.1.
680
- refinement_ratio (float, optional): _description_. Defaults to 0.3.
681
- growth_rate (float, optional): _description_. Defaults to 3.
682
- """
683
- from .physics.microwave.adaptive_mesh import select_refinement_indices, reduce_point_set, compute_convergence
684
-
685
- max_freq = np.max(self.mw.frequencies)
686
655
 
687
- regenerate = False
688
656
 
689
- Smats = []
690
-
691
- for step in range(max_steps):
692
- amr_params = dict(iter_step=step)
693
- self.mw._params = amr_params
694
- self.data.sim.new(**amr_params)
695
-
696
-
697
- data = self.mw._run_adaptive_mesh(step, max_freq)
698
-
699
- field = data.field[-1]
700
-
701
- Smat_new = data.scalar[-1].Sp
702
- Smats.append(Smat_new)
703
- if step > 0:
704
- S0 = Smats[-2]
705
- S1 = Smats[-1]
706
- conv = compute_convergence(S0, S1)
707
- logger.info(f'Convergence = {conv}')
708
- if conv < convergence:
709
- logger.info('Mesh refinement passed!')
710
- break
711
- error, lengths = field._solution_quality()
712
-
713
- idx = select_refinement_indices(error, refinement_percentage)
714
-
715
- idx = idx[reduce_point_set(self.mw.mesh.centers[:,idx], growth_rate, lengths[idx], refinement_ratio)]
716
- centers = self.mw.mesh.centers
717
-
718
- self.mesher._reset_amr_points()
719
- self._reset_mesh()
720
- logger.debug(f'Adding {len(idx)} refinement points.')
721
- for i in idx:
722
- coord = centers[:,i]
723
- size = lengths[i]
724
- self.mesher.add_refinement_point(coord, refinement_ratio, size, growth_rate)
725
-
726
- self.generate_mesh(True)
727
- self.view(plot_mesh=True)
728
657
  def export(self, filename: str):
729
658
  """Exports the model or mesh depending on the extension.
730
659
 
@@ -760,4 +689,198 @@ class Simulation:
760
689
  """
761
690
  logger.warning('define_geometry() will be derpicated. Use commit_geometry() instead.')
762
691
  self.commit_geometry(*args)
763
-
692
+
693
+ class SimulationBeta(Simulation):
694
+
695
+
696
+ def __post_init__(self):
697
+ pass
698
+ #self.mesher.set_algorithm(Algorithm3D.HXT)
699
+ #logger.debug('Setting mesh algorithm to HXT')
700
+
701
+
702
+ def _reset_mesh(self):
703
+ #gmsh.clear()
704
+ gmsh.model.mesh.clear()
705
+
706
+ self.mw.reset(_reset_bc = False)
707
+ self.state.reset_mesh()
708
+
709
+ def adaptive_mesh_refinement(self,
710
+ max_steps: int = 6,
711
+ min_refined_passes: int = 1,
712
+ convergence: float = 0.02,
713
+ magnitude_convergence: float = 2.0,
714
+ phase_convergence: float = 180,
715
+ refinement_ratio: float = 0.6,
716
+ growth_rate: float = 2,
717
+ minimum_refinement_percentage: float = 20.0,
718
+ error_field_inclusion_percentage: float = 60.0,
719
+ frequency: float = None,
720
+ show_mesh: bool = False) -> SimulationDataset:
721
+ """ A beta-version of adaptive mesh refinement.
722
+
723
+ Convergence Criteria:
724
+ (1): max(abs(S[n]-S[n-1]))
725
+ (2): max(abs(abs(S[n]) - abs(S[n-1])))
726
+ (3): max(angle(S[n]/S[n-1])) * 180/π
727
+
728
+ Args:
729
+ max_steps (int, optional): The maximum number of refinement steps. Defaults to 6.
730
+ min_refined_passes (int, optional): The minimum number of refined passes. Defaults to 1.
731
+ convergence (float, optional): The S-paramerter convergence (1). Defaults to 0.02.
732
+ magnitude_convergence (float, optional): The S-parameter magnitude convergence (2). Defaults to 2.0.
733
+ phase_convergence (float, optional): The S-parameter Phase convergence (3). Defaults to 180.
734
+ refinement_ratio (float, optional): The size reduction of mesh elements by original length. Defaults to 0.75.
735
+ growth_rate (float, optional): The mesh size growth rate. Defaults to 3.0.
736
+ minimum_refinement_percentage (float, optional): The minimum mesh size increase . Defaults to 15.0.
737
+ error_field_inclusion_percentage (float, optional): A percentage of tet elements to be included for refinement. Defaults to 5.0.
738
+ frequency (float, optional): The refinement frequency. Defaults to None.
739
+ show_mesh (bool, optional): If the intermediate meshes should be shown (freezes simulation). Defaults to False
740
+
741
+ Returns:
742
+ SimulationDataset: _description_
743
+ """
744
+ from .physics.microwave.adaptive_mesh import select_refinement_indices, reduce_point_set, compute_convergence
745
+ from collections import defaultdict
746
+
747
+ max_freq = np.max(self.mw.frequencies)
748
+
749
+ if frequency is not None:
750
+ max_freq = frequency
751
+
752
+ S_matrices: list[np.ndarray] = []
753
+
754
+ last_n_tets: int = self.mesh.n_tets
755
+ logger.info(f'Initial mesh has {last_n_tets} tetrahedra')
756
+
757
+ passed = 0
758
+
759
+ self.state.stash()
760
+
761
+ a0 = 0.26
762
+ c0 = 0.75
763
+ x0 = 12
764
+ q0 = (1-a0)*2/np.pi
765
+ b0 = np.tan((c0-a0)/q0)/x0
766
+ q0 = (0.8-a0)*2/np.pi
767
+
768
+ for step in range(1,max_steps+1):
769
+
770
+ self.data.sim.new(iter_step=step)
771
+
772
+ data, solve_ids = self.mw._run_adaptive_mesh(step, max_freq)
773
+
774
+ field = data.field[-1]
775
+
776
+ Smat_new = data.scalar[-1].Sp
777
+ S_matrices.append(Smat_new)
778
+
779
+ if step > 1:
780
+ S0 = S_matrices[-2]
781
+ S1 = S_matrices[-1]
782
+ conv_complex, conv_mag, conv_phase = compute_convergence(S0, S1)
783
+ logger.info(f'Pass {step}: Convergence = {conv_complex:.3f}, Mag = {conv_mag:.3f}, Phase = {conv_phase:.1f} deg')
784
+ if conv_complex <= convergence and conv_phase < phase_convergence and conv_mag < magnitude_convergence:
785
+ logger.info(f'Pass {step}: Mesh refinement passed!')
786
+ passed += 1
787
+ else:
788
+ passed = 0
789
+
790
+ if passed >= min_refined_passes:
791
+ logger.info(f'Adaptive mesh refinement successfull with {self.mesh.n_tets} tetrahedra.')
792
+ break
793
+
794
+ error, lengths = field._solution_quality(solve_ids)
795
+
796
+ idx = select_refinement_indices(error, error_field_inclusion_percentage/100)
797
+ idx = idx[::-1]
798
+
799
+ npts = idx.shape[0]
800
+ np_percentage = npts/self.mesh.n_tets * 100
801
+
802
+ refinement_ratio = (a0 + np.arctan(b0*np_percentage)*q0)
803
+ #calc_refinement_ratio(refinement_throttle, point_percentage)
804
+ logger.info(f'Adding {npts} refinement points with a ratio: {refinement_ratio}')
805
+
806
+ # tet_nodes = defaultdict(lambda: 1000.)
807
+ # for itet in idx:
808
+ # for inode in self.mesh.tets[:,itet]:
809
+ # tet_nodes[inode] = min(tet_nodes[inode], lengths[itet])
810
+
811
+ # N = len(tet_nodes)
812
+ # coords = np.zeros((3,N), dtype=np.float64)
813
+ # sizes = np.zeros((N,), dtype=np.float64)
814
+ # for i, (index, size) in enumerate(tet_nodes.items()):
815
+ # coords[:,i] = self.mesh.nodes[:,index]
816
+ # sizes[i] = size
817
+
818
+ # self.mesher.add_refinement_points(coords, sizes, refinement_ratio*np.ones((N,)))
819
+
820
+ self.mesher.add_refinement_points(self.mw.mesh.centers[:,idx], lengths[idx], refinement_ratio*np.ones_like(lengths[idx]))
821
+
822
+ logger.debug(f'Pass {step}: Adding {len(idx)} new refinement points.')
823
+
824
+ new_ids = reduce_point_set(self.mesher._amr_coords, growth_rate, self.mesher._amr_sizes, refinement_ratio, 0.20)
825
+
826
+ nremoved = self.mesher._amr_coords.shape[1] - len(new_ids)
827
+ if nremoved > 0:
828
+ logger.info(f'Cleanup of step {step}: Removing {nremoved} points.')
829
+
830
+ self.mesher._amr_coords = self.mesher._amr_coords[:,new_ids]
831
+ self.mesher._amr_sizes = self.mesher._amr_sizes[new_ids]
832
+ self.mesher._amr_ratios = self.mesher._amr_ratios[new_ids]
833
+ self.mesher._amr_new = self.mesher._amr_new[new_ids]
834
+
835
+ def clip(value: float):
836
+ return max(1.3, value)
837
+
838
+ logger.debug(f'Initial refinement ratio: {refinement_ratio}')
839
+
840
+ over = False
841
+ under = False
842
+ throttle = 1.0
843
+
844
+ while True:
845
+
846
+ if over and under:
847
+ throttle *= 2
848
+
849
+ self._reset_mesh()
850
+ logger.debug(f'Pass {step}')
851
+ self.mesher.set_refinement_function(growth_rate, 2.0)
852
+ self.generate_mesh(True)
853
+ percentage = (self.mesh.n_tets/last_n_tets - 1) * 100
854
+ logger.info(f'Pass {step}: New mesh has {self.mesh.n_tets} (+{percentage:.1f}%) tetrahedra.')
855
+
856
+ if percentage < minimum_refinement_percentage:
857
+ F = (2*minimum_refinement_percentage-percentage)/(throttle*minimum_refinement_percentage)
858
+ logger.debug('Not enough mesh refinement, decreasing mesh size constraint.')
859
+ refinement_ratio = self.mesher.refine_finer(F)
860
+ logger.debug(f'New refinement ratio: {refinement_ratio}')
861
+ under=True
862
+ continue
863
+ if percentage > (minimum_refinement_percentage*2):
864
+ F = (percentage - minimum_refinement_percentage)/(throttle*minimum_refinement_percentage)
865
+ logger.debug('Too much mesh refinement, decreasing mesh size constraint.')
866
+ refinement_ratio = self.mesher.refine_coarser(F)
867
+ logger.debug(f'New refinement ratio: {refinement_ratio}')
868
+ over=True
869
+ continue
870
+
871
+ over = False
872
+ under = False
873
+ throttle = 1.0
874
+ last_n_tets = self.mesh.n_tets
875
+ break
876
+
877
+ if show_mesh:
878
+ self.view(plot_mesh=True, volume_mesh=True)
879
+
880
+ if passed < min_refined_passes:
881
+ logger.warning('Adaptive mesh refinement did not converge!')
882
+
883
+ old = self.state.reload()
884
+ return old
885
+
886
+