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

@@ -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:
@@ -476,7 +474,7 @@ class Simulation:
476
474
  gmsh.model.occ.synchronize()
477
475
  gmsh.fltk.run()
478
476
  return
479
- for geo in _GEOMANAGER.all_geometries():
477
+ for geo in self.state.current_geo_state:
480
478
  self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh, label=labels)
481
479
  if selections:
482
480
  [self.display.add_object(sel, color='red', opacity=0.6, label=labels) for sel in selections]
@@ -512,17 +510,13 @@ class Simulation:
512
510
  The geometries may be provided (legacy behavior) but are automatically managed in the background.
513
511
 
514
512
  """
515
- geometries_parsed: Any = None
516
513
  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}')
514
+ self.state.store_geometry_data()
515
+
516
+ logger.trace(f'Parsed geometries = {self.state.geos}')
522
517
 
523
- self._save_geometries()
518
+ self.mesher.submit_objects(self.state.geos)
524
519
 
525
- self.mesher.submit_objects(geometries_parsed)
526
520
  self._defined_geometries = True
527
521
  self.display._facetags = [dt[1] for dt in gmsh.model.get_entities(2)]
528
522
 
@@ -532,22 +526,9 @@ class Simulation:
532
526
  Returns:
533
527
  list[GeoObject]: A list of all GeoObjects
534
528
  """
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
529
+ return self.state.current_geo_state
549
530
 
550
- def generate_mesh(self) -> None:
531
+ def generate_mesh(self, regenerate: bool = False) -> None:
551
532
  """Generate the mesh.
552
533
  This can only be done after commit_geometry(...) is called and if frequencies are defined.
553
534
 
@@ -557,26 +538,38 @@ class Simulation:
557
538
  Raises:
558
539
  ValueError: ValueError if no frequencies are defined.
559
540
  """
560
- logger.trace('Starting mesh generation phase.')
561
- if not self._defined_geometries:
562
- self.commit_geometry()
563
-
564
- logger.trace(' (1) Installing periodic boundaries in mesher.')
565
- # Set the cell periodicity in GMSH
566
- if self._cell is not None:
567
- self.mesher.set_periodic_cell(self._cell)
568
-
569
- self.mw._initialize_bcs(_GEOMANAGER.get_surfaces())
541
+ logger.info('Starting mesh generation phase.')
542
+ if not regenerate:
543
+
544
+ if not self._defined_geometries:
545
+ self.commit_geometry()
546
+
547
+ logger.trace(' (1) Installing periodic boundaries in mesher.')
548
+ # Set the cell periodicity in GMSH
549
+ if self._cell is not None:
550
+ self.mesher.set_periodic_cell(self._cell)
551
+
552
+ self.mw._initialize_bcs(self.state.manager.get_surfaces())
570
553
 
571
- # Check if frequencies are defined: TODO: Replace with a more generic check
572
- if self.mw.frequencies is None:
573
- raise ValueError('No frequencies defined for the simulation. Please set frequencies before generating the mesh.')
554
+ # Check if frequencies are defined: TODO: Replace with a more generic check
555
+ if self.mw.frequencies is None:
556
+ raise ValueError('No frequencies defined for the simulation. Please set frequencies before generating the mesh.')
574
557
 
575
558
  gmsh.model.occ.synchronize()
576
559
 
577
560
  # Set the mesh size
578
- self.mesher._configure_mesh_size(self.mw.get_discretizer(), self.mw.resolution)
579
-
561
+ self.mesher._configure_mesh_size(self.mw.get_discretizer(), self.mw.resolution) # This makes no sense to do this here
562
+
563
+ # Validity check
564
+ x1, y1, z1, x2, y2, z2 = gmsh.model.getBoundingBox(-1, -1)
565
+ bb_volume = (x2-x1)*(y2-y1)*(z2-z1)
566
+ wl = 299792458/self.mw.frequencies[-1]
567
+ Nelem = int(5 * bb_volume / (wl**3))
568
+ if Nelem > 100_000 and DEFAULT_SETTINGS.size_check:
569
+ 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.' +
570
+ 'To disable this message. Set the .size_check parameter in model.settings to False.')
571
+
572
+ raise SimulationError('Simulation requires too many elements.')
580
573
  logger.trace(' (2) Calling GMSH mesher')
581
574
  try:
582
575
  gmsh.logger.start()
@@ -589,13 +582,13 @@ class Simulation:
589
582
  logger.error('GMSH Mesh error detected.')
590
583
  print(_GMSH_ERROR_TEXT)
591
584
  raise
585
+
592
586
  logger.info('GMSH Meshing complete!')
593
587
  self.mesh._pre_update(self.mesher._get_periodic_bcs())
594
588
  self.mesh.exterior_face_tags = self.mesher.domain_boundary_face_tags
595
589
  gmsh.model.occ.synchronize()
596
- self._set_mesh(self.mesh)
597
590
  logger.trace(' (3) Mesh routine complete')
598
-
591
+
599
592
  def parameter_sweep(self, clear_mesh: bool = True, **parameters: np.ndarray) -> Generator[tuple[float,...], None, None]:
600
593
  """Executes a parameteric sweep iteration.
601
594
 
@@ -631,14 +624,13 @@ class Simulation:
631
624
  if clear_mesh and i_iter > 0:
632
625
  logger.info('Cleaning up mesh.')
633
626
  gmsh.clear()
634
- mesh = Mesh3D(self.mesher)
635
- _GEOMANAGER.reset(self.modelname)
636
- self._set_mesh(mesh)
627
+ self.state.reset_geostate(self.modelname)
637
628
  self.mw.reset()
629
+
638
630
 
639
631
  params = {key: dim[i_iter] for key,dim in zip(paramlist, dims_flat)}
640
- self.mw._params = params
641
- self.data.sim.new(**params)
632
+
633
+ self.state.set_parameters(params)
642
634
 
643
635
  logger.info(f'Iterating: {params}')
644
636
  if len(dims_flat)==1:
@@ -646,14 +638,16 @@ class Simulation:
646
638
  else:
647
639
  yield (dim[i_iter] for dim in dims_flat) # type: ignore
648
640
 
641
+
649
642
  if not clear_mesh:
650
- self._save_geometries()
643
+ self.state.store_geometry_data()
651
644
 
652
645
  if not clear_mesh:
653
- self._save_geometries()
646
+ self.state.store_geometry_data()
654
647
 
655
648
  self.mw.cache_matrices = True
656
-
649
+
650
+
657
651
  def export(self, filename: str):
658
652
  """Exports the model or mesh depending on the extension.
659
653
 
@@ -689,4 +683,153 @@ class Simulation:
689
683
  """
690
684
  logger.warning('define_geometry() will be derpicated. Use commit_geometry() instead.')
691
685
  self.commit_geometry(*args)
692
-
686
+
687
+ class SimulationBeta(Simulation):
688
+
689
+
690
+ def __post_init__(self):
691
+
692
+ self.mesher.set_algorithm(Algorithm3D.HXT)
693
+ logger.debug('Setting mesh algorithm to HXT')
694
+
695
+
696
+ def _reset_mesh(self):
697
+ #gmsh.clear()
698
+ gmsh.model.mesh.clear()
699
+
700
+ self.mw.reset(_reset_bc = False)
701
+ self.state.reset_mesh()
702
+
703
+ def adaptive_mesh_refinement(self,
704
+ max_steps: int = 6,
705
+ min_refined_passes: int = 1,
706
+ convergence: float = 0.02,
707
+ magnitude_convergence: float = 2.0,
708
+ phase_convergence: float = 180,
709
+ refinement_ratio: float = 0.9,
710
+ growth_rate: float = 3,
711
+ minimum_refinement_percentage: float = 20.0,
712
+ error_field_inclusion_percentage: float = 5.0,
713
+ frequency: float = None,
714
+ show_mesh: bool = False) -> SimulationDataset:
715
+ """ A beta-version of adaptive mesh refinement.
716
+
717
+ Convergence Criteria:
718
+ (1): max(abs(S[n]-S[n-1]))
719
+ (2): max(abs(abs(S[n]) - abs(S[n-1])))
720
+ (3): max(angle(S[n]/S[n-1])) * 180/π
721
+
722
+ Args:
723
+ max_steps (int, optional): The maximum number of refinement steps. Defaults to 6.
724
+ min_refined_passes (int, optional): The minimum number of refined passes. Defaults to 1.
725
+ convergence (float, optional): The S-paramerter convergence (1). Defaults to 0.02.
726
+ magnitude_convergence (float, optional): The S-parameter magnitude convergence (2). Defaults to 2.0.
727
+ phase_convergence (float, optional): The S-parameter Phase convergence (3). Defaults to 180.
728
+ refinement_ratio (float, optional): The size reduction of mesh elements by original length. Defaults to 0.75.
729
+ growth_rate (float, optional): The mesh size growth rate. Defaults to 3.0.
730
+ minimum_refinement_percentage (float, optional): The minimum mesh size increase . Defaults to 15.0.
731
+ error_field_inclusion_percentage (float, optional): A percentage of tet elements to be included for refinement. Defaults to 5.0.
732
+ frequency (float, optional): The refinement frequency. Defaults to None.
733
+ show_mesh (bool, optional): If the intermediate meshes should be shown (freezes simulation). Defaults to False
734
+
735
+ Returns:
736
+ SimulationDataset: _description_
737
+ """
738
+ from .physics.microwave.adaptive_mesh import select_refinement_indices, reduce_point_set, compute_convergence
739
+
740
+
741
+ max_freq = np.max(self.mw.frequencies)
742
+
743
+ if frequency is not None:
744
+ max_freq = frequency
745
+
746
+ S_matrices: list[np.ndarray] = []
747
+
748
+ last_n_tets: int = self.mesh.n_tets
749
+ logger.info(f'Initial mesh has {last_n_tets} tetrahedra')
750
+
751
+ passed = 0
752
+
753
+ self.state.stash()
754
+
755
+ for step in range(1,max_steps+1):
756
+
757
+ self.data.sim.new(iter_step=step)
758
+
759
+ data = self.mw._run_adaptive_mesh(step, max_freq)
760
+
761
+ field = data.field[-1]
762
+
763
+ Smat_new = data.scalar[-1].Sp
764
+ S_matrices.append(Smat_new)
765
+
766
+ if step > 1:
767
+ S0 = S_matrices[-2]
768
+ S1 = S_matrices[-1]
769
+ conv_complex, conv_mag, conv_phase = compute_convergence(S0, S1)
770
+ logger.info(f'Pass {step}: Convergence = {conv_complex:.3f}, Mag = {conv_mag:.3f}, Phase = {conv_phase:.1f} deg')
771
+ if conv_complex <= convergence and conv_phase < phase_convergence and conv_mag < magnitude_convergence:
772
+ logger.info(f'Pass {step}: Mesh refinement passed!')
773
+ passed += 1
774
+ else:
775
+ passed = 0
776
+
777
+ if passed >= min_refined_passes:
778
+ logger.info('Adaptive mesh refinement successfull')
779
+ break
780
+
781
+ error, lengths = field._solution_quality()
782
+
783
+ idx = select_refinement_indices(error, error_field_inclusion_percentage/100)
784
+ idx = idx[::-1]
785
+
786
+ self.mesher.add_refinement_points(self.mw.mesh.centers[:,idx], lengths[idx])
787
+
788
+ logger.debug(f'Pass {step}: Adding {len(idx)} new refinement points.')
789
+
790
+ new_ids = reduce_point_set(self.mesher._amr_coords, growth_rate, self.mesher._amr_sizes, refinement_ratio, 0.20)
791
+
792
+ logger.debug(f'Pass {step}: Removing {self.mesher._amr_coords.shape[1] - len(new_ids)} points from {self.mesher._amr_coords.shape[1]} to {len(new_ids)}')
793
+
794
+ self.mesher._amr_coords = self.mesher._amr_coords[:,new_ids]
795
+ self.mesher._amr_sizes = self.mesher._amr_sizes[new_ids]
796
+
797
+
798
+ while True:
799
+
800
+ self._reset_mesh()
801
+
802
+ logger.debug(f'Pass {step}: Adding {len(idx)} refinement points.')
803
+
804
+ self.mesher.set_refinement_function(refinement_ratio, growth_rate, 1.0)
805
+
806
+ self.generate_mesh(True)
807
+
808
+ percentage = (self.mesh.n_tets/last_n_tets - 1) * 100
809
+ logger.info(f'Pass {step}: New mesh has {self.mesh.n_tets} (+{percentage:.1f}%) tetrahedra.')
810
+
811
+ if percentage < minimum_refinement_percentage:
812
+ logger.debug('Not enough mesh refinement, decreasing mesh size constraint.')
813
+ refinement_ratio = refinement_ratio * 0.9
814
+ logger.debug(f'New refinement ratio: {refinement_ratio}')
815
+ continue
816
+
817
+ if percentage > 2*minimum_refinement_percentage and refinement_ratio < 0.99:
818
+ logger.debug('Too much mesh refinement, decreasing mesh size constraint.')
819
+ refinement_ratio = refinement_ratio ** (1-np.log(percentage/minimum_refinement_percentage)/4)
820
+ logger.debug(f'New refinement ratio: {refinement_ratio}')
821
+
822
+
823
+ last_n_tets = self.mesh.n_tets
824
+ break
825
+
826
+ if show_mesh:
827
+ self.view(plot_mesh=True, volume_mesh=True)
828
+
829
+ if passed < min_refined_passes:
830
+ logger.warning('Adaptive mesh refinement did not converge!')
831
+
832
+ old = self.state.reload()
833
+ return old
834
+
835
+
@@ -0,0 +1,106 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ from .mesh3d import Mesh3D
20
+ from .geometry import GeoObject, _GeometryManager, _GEOMANAGER
21
+ from .dataset import SimulationDataset
22
+ from loguru import logger
23
+
24
+ from typing import Any
25
+
26
+
27
+ class SimState:
28
+
29
+ def __init__(self):
30
+ self.mesh: Mesh3D = Mesh3D()
31
+ self.geos: list[GeoObject] = []
32
+ self.data: SimulationDataset = SimulationDataset()
33
+ self.params: dict[str, float] = dict()
34
+ self._stashed: SimulationDataset | None = None
35
+ self.manager: _GeometryManager = _GEOMANAGER
36
+
37
+ @property
38
+ def current_geo_state(self) -> list[GeoObject]:
39
+ return self.manager.all_geometries()
40
+
41
+ def reset_geostate(self, modelname: str) -> None:
42
+ _GEOMANAGER.reset(modelname)
43
+ self.clear_mesh()
44
+
45
+ def init(self, modelname: str) -> None:
46
+ self.mesh = Mesh3D()
47
+ self.geos = []
48
+ self.reset_geostate(modelname)
49
+ self.init_data()
50
+
51
+ def stash(self) -> None:
52
+ self._stashed = self.data
53
+ self.data = SimulationDataset()
54
+
55
+ def set_parameters(self, parameters: dict[str, float]) -> None:
56
+ self.params = parameters
57
+
58
+ def init_data(self) -> None:
59
+ self.data.sim.new(**self.params)
60
+
61
+ def reload(self) -> SimulationDataset:
62
+ old = self._stashed
63
+ self.data = self._stashed
64
+ self._stashed = None
65
+ return old
66
+
67
+ def reset_mesh(self) -> None:
68
+ self.mesh = Mesh3D()
69
+
70
+ def set_mesh(self, mesh: Mesh3D) -> None:
71
+ self.mesh = mesh
72
+
73
+ def set_geos(self, geos: list[GeoObject]) -> None:
74
+ self.geos = geos
75
+ _GEOMANAGER.set_geometries(geos)
76
+
77
+ def clear_mesh(self) -> None:
78
+ self.mesh = Mesh3D()
79
+
80
+ def store_geometry_data(self) -> None:
81
+ """Saves the current geometry state to the simulatin dataset
82
+ """
83
+ logger.trace('Storing geometries in data.sim')
84
+ self.geos = self.current_geo_state
85
+ self.data.sim['geos'] = self.geos
86
+ self.data.sim['mesh'] = self.mesh
87
+
88
+ def get_dataset(self) -> dict[str, Any]:
89
+ return dict(simdata=self.data, mesh=self.mesh)
90
+
91
+ def load_dataset(self, dataset: dict[str, Any]):
92
+ self.data = dataset['simdata']
93
+ self.mesh = dataset['mesh']
94
+
95
+ def activate(self, _indx: int | None = None, **variables):
96
+ """Searches for the permutaions of parameter sweep variables and sets the current geometry to the provided set."""
97
+ if _indx is not None:
98
+ dataset = self.data.sim.index(_indx)
99
+ else:
100
+ dataset = self.data.sim.find(**variables)
101
+
102
+ variables = ', '.join([f'{key}={value}' for key,value in dataset.vars.items()])
103
+ logger.info(f'Activated entry with variables: {variables}')
104
+ self.set_mesh(dataset['mesh'])
105
+ self.set_geos(dataset['geos'])
106
+ return self