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.
- emerge/__init__.py +15 -3
- emerge/_emerge/const.py +2 -1
- emerge/_emerge/elements/ned2_interp.py +122 -42
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/operations.py +20 -0
- emerge/_emerge/geo/pcb.py +162 -71
- emerge/_emerge/geo/shapes.py +12 -7
- emerge/_emerge/geo/step.py +177 -41
- emerge/_emerge/geometry.py +189 -27
- emerge/_emerge/logsettings.py +26 -2
- emerge/_emerge/material.py +2 -0
- emerge/_emerge/mesh3d.py +6 -8
- emerge/_emerge/mesher.py +67 -11
- emerge/_emerge/mth/common_functions.py +1 -1
- emerge/_emerge/mth/optimized.py +2 -2
- emerge/_emerge/physics/microwave/adaptive_mesh.py +549 -116
- emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
- emerge/_emerge/physics/microwave/microwave_3d.py +133 -83
- emerge/_emerge/physics/microwave/microwave_bc.py +158 -8
- emerge/_emerge/physics/microwave/microwave_data.py +94 -5
- emerge/_emerge/plot/pyvista/display.py +36 -23
- emerge/_emerge/selection.py +17 -2
- emerge/_emerge/settings.py +124 -6
- emerge/_emerge/simmodel.py +273 -150
- emerge/_emerge/simstate.py +106 -0
- emerge/_emerge/simulation_data.py +11 -23
- emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
- emerge/_emerge/solver.py +4 -4
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/METADATA +7 -3
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/RECORD +33 -32
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/licenses/LICENSE +0 -0
emerge/_emerge/simmodel.py
CHANGED
|
@@ -16,19 +16,20 @@
|
|
|
16
16
|
# <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
|
-
from .mesher import Mesher
|
|
20
|
-
from .geometry import GeoObject
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
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'
|
|
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
|
|
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
|
|
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=
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
651
|
-
self.
|
|
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.
|
|
649
|
+
self.state.store_geometry_data()
|
|
661
650
|
|
|
662
651
|
if not clear_mesh:
|
|
663
|
-
|
|
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
|
+
|