fiqus 2025.12.0__py3-none-any.whl → 2026.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.
Files changed (52) hide show
  1. fiqus/MainFiQuS.py +4 -8
  2. fiqus/data/DataConductor.py +108 -11
  3. fiqus/data/DataFiQuS.py +2 -1
  4. fiqus/data/DataFiQuSConductorAC_CC.py +345 -0
  5. fiqus/data/DataFiQuSConductorAC_Strand.py +3 -3
  6. fiqus/data/DataFiQuSMultipole.py +363 -165
  7. fiqus/data/DataModelCommon.py +30 -15
  8. fiqus/data/DataMultipole.py +33 -10
  9. fiqus/data/DataWindingsCCT.py +37 -37
  10. fiqus/data/RegionsModelFiQuS.py +1 -1
  11. fiqus/geom_generators/GeometryConductorAC_CC.py +1906 -0
  12. fiqus/geom_generators/GeometryMultipole.py +751 -54
  13. fiqus/getdp_runners/RunGetdpConductorAC_CC.py +123 -0
  14. fiqus/getdp_runners/RunGetdpMultipole.py +181 -31
  15. fiqus/mains/MainConductorAC_CC.py +148 -0
  16. fiqus/mains/MainMultipole.py +109 -17
  17. fiqus/mesh_generators/MeshCCT.py +209 -209
  18. fiqus/mesh_generators/MeshConductorAC_CC.py +1305 -0
  19. fiqus/mesh_generators/MeshMultipole.py +938 -263
  20. fiqus/parsers/ParserCOND.py +2 -1
  21. fiqus/parsers/ParserDAT.py +16 -16
  22. fiqus/parsers/ParserGetDPOnSection.py +212 -212
  23. fiqus/parsers/ParserGetDPTimeTable.py +134 -134
  24. fiqus/parsers/ParserMSH.py +53 -53
  25. fiqus/parsers/ParserRES.py +142 -142
  26. fiqus/plotters/PlotPythonCCT.py +133 -133
  27. fiqus/plotters/PlotPythonMultipole.py +18 -18
  28. fiqus/post_processors/PostProcessAC_CC.py +65 -0
  29. fiqus/post_processors/PostProcessMultipole.py +16 -6
  30. fiqus/pre_processors/PreProcessCCT.py +175 -175
  31. fiqus/pro_assemblers/ProAssembler.py +3 -3
  32. fiqus/pro_material_functions/ironBHcurves.pro +246 -246
  33. fiqus/pro_templates/combined/CAC_CC_template.pro +542 -0
  34. fiqus/pro_templates/combined/CC_Module.pro +1213 -0
  35. fiqus/pro_templates/combined/Multipole_template.pro +2738 -1338
  36. fiqus/pro_templates/combined/TSA_materials.pro +102 -2
  37. fiqus/pro_templates/combined/materials.pro +54 -3
  38. fiqus/utils/Utils.py +18 -25
  39. fiqus/utils/update_data_settings.py +1 -1
  40. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/METADATA +81 -77
  41. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/RECORD +52 -44
  42. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/WHEEL +1 -1
  43. tests/test_geometry_generators.py +47 -30
  44. tests/test_mesh_generators.py +69 -30
  45. tests/test_solvers.py +67 -29
  46. tests/utils/fiqus_test_classes.py +396 -147
  47. tests/utils/generate_reference_files_ConductorAC.py +57 -57
  48. tests/utils/helpers.py +76 -1
  49. /fiqus/pro_templates/combined/{ConductorACRutherford_template.pro → CAC_Rutherford_template.pro} +0 -0
  50. /fiqus/pro_templates/combined/{ConductorAC_template.pro → CAC_Strand_template.pro} +0 -0
  51. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info/licenses}/LICENSE.txt +0 -0
  52. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
+ import logging
1
2
  import os
2
3
  import gmsh
3
4
  import time
4
5
 
6
+ from fiqus.plotters.PlotPythonMultipole import PlotPythonMultipole
5
7
  from fiqus.utils.Utils import GmshUtils
6
8
  from fiqus.utils.Utils import FilesAndFolders as Util
7
9
  from fiqus.data import DataFiQuS as dF
@@ -11,11 +13,10 @@ from fiqus.mesh_generators.MeshMultipole import Mesh
11
13
  from fiqus.getdp_runners.RunGetdpMultipole import RunGetdpMultipole
12
14
  from fiqus.getdp_runners.RunGetdpMultipole import AssignNaming
13
15
  from fiqus.post_processors.PostProcessMultipole import PostProcess
14
- from fiqus.plotters.PlotPythonMultipole import PlotPythonMultipole
15
16
 
16
17
 
17
18
  class MainMultipole:
18
- def __init__(self, fdm: dF.FDM = None, rgd_path: str = None, verbose: bool = None):
19
+ def __init__(self, fdm: dF.FDM = None, rgd_path: str = None, verbose: bool = None, inputs_folder_path = None):
19
20
  """
20
21
  Main class for working with simulations for multipole type magnets
21
22
  :param fdm: FiQuS data model
@@ -30,11 +31,13 @@ class MainMultipole:
30
31
  self.geom_folder = None
31
32
  self.mesh_folder = None
32
33
  self.solution_folder = None
34
+ self.inputs_folder_path = inputs_folder_path
33
35
 
34
36
  def force_symmetry(self):
35
37
  fdm = self.fdm.__deepcopy__()
36
38
  fdm.magnet.geometry.electromagnetics.symmetry = 'x'
37
39
  return fdm
40
+
38
41
  def generate_geometry(self, gui: bool = False):
39
42
  geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
40
43
  fdm = self.force_symmetry() if 'solenoid' in geom.Roxie_Data.coil.coils[1].type else self.fdm # todo: this should be handled by pydantic
@@ -43,14 +46,16 @@ class MainMultipole:
43
46
  plotter.plot_coil_wedges()
44
47
  gg = Geometry(data=fdm, geom=geom, geom_folder=self.geom_folder, verbose=self.verbose)
45
48
  gg.saveHalfTurnCornerPositions()
49
+
46
50
  geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
47
51
  geometry_type_list = []
48
- if fdm.magnet.geometry.electromagnetics.create: geometry_type_list.append('EM')
49
- if fdm.magnet.geometry.thermal.create: geometry_type_list.append('TH')
52
+
53
+ if geometry_settings['EM'].create: geometry_type_list.append('EM')
54
+ if geometry_settings['TH'].create: geometry_type_list.append('TH')
50
55
  for geometry_type in geometry_type_list:
51
56
  gg.saveStrandPositions(geometry_type)
52
- if geometry_settings[geometry_type].with_iron_yoke:
53
- gg.constructIronGeometry(geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
57
+ if any(geometry_settings[geometry_type].areas):
58
+ gg.constructIronGeometry(geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none', geometry_settings[geometry_type], run_type = geometry_type)
54
59
  gg.constructCoilGeometry(geometry_type)
55
60
  if geometry_settings[geometry_type].with_wedges:
56
61
  gg.constructWedgeGeometry(geometry_settings[geometry_type].use_TSA if geometry_type == 'TH' else False)
@@ -60,9 +65,17 @@ class MainMultipole:
60
65
  gg.constructThinShells(geometry_settings[geometry_type].with_wedges)
61
66
  else:
62
67
  gg.constructInsulationGeometry()
68
+ if geometry_settings[geometry_type].use_TSA_new:
69
+ gg.constructAdditionalThinShells()
70
+
63
71
  gg.buildDomains(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
64
72
  if geometry_type == 'EM':
65
73
  gg.fragment()
74
+ if geometry_type =='TH' and 'poles' in geometry_settings[geometry_type].areas:
75
+ # make sure geometry is connected
76
+ gmsh.model.occ.removeAllDuplicates()
77
+ gmsh.model.occ.synchronize()
78
+
66
79
  gg.saveBoundaryRepresentationFile(geometry_type)
67
80
  gg.loadBoundaryRepresentationFile(geometry_type)
68
81
  gg.updateTags(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
@@ -91,29 +104,84 @@ class MainMultipole:
91
104
  gmsh.open(model_file + f'_{run_type}.brep')
92
105
 
93
106
  def mesh(self, gui: bool = False):
94
- mm = Mesh(data=self.fdm, mesh_folder=self.mesh_folder, verbose=self.verbose)
107
+ def _create_physical_group_for_reference(self):
108
+ """
109
+ This code generates the reference models for the FALCOND_C
110
+ """
111
+ ### hardcoded, we need only one reference
112
+ if self.fdm.general.magnet_name == 'TEST_MULTIPOLE_FALCOND_C_TSA_COLLAR_POLE':
113
+ CUT_REFERENCE = True # self specify loop and surf
114
+
115
+ L_col = [56, 57, 58, 35, 16, 49, 48, 47]
116
+ l1 = gmsh.model.occ.addLine(746,48)
117
+ l2 = gmsh.model.occ.addLine(41,691)
118
+ L_inner = list(range(854, 754-1, -1))
119
+ loop1 = gmsh.model.occ.addCurveLoop([l1] + L_col + [l2] + L_inner)
120
+
121
+ R_col = [52, 53, 54, 26, 6, 45, 44, 43]
122
+ r1 = gmsh.model.occ.addLine(38,902)
123
+ r2 = gmsh.model.occ.addLine(847,45)
124
+ R_inner = list(range(910, 1010+1))
125
+ loop2 = gmsh.model.occ.addCurveLoop([r2]+ R_col + [r1] + R_inner)
126
+
127
+ surf1 = gmsh.model.occ.addPlaneSurface([loop1])
128
+ surf2 = gmsh.model.occ.addPlaneSurface([loop2])
129
+ surf = [surf1, surf2] # tag
130
+
131
+ else: raise Exception("Reference meshing is not implemented for this magnet.")
132
+ gmsh.model.occ.synchronize()
133
+
134
+ #add to physical groups / domains -> write to aux file
135
+ file = os.path.join(self.geom_folder, self.fdm.general.magnet_name + '_TH.aux')
136
+ with open(file) as f:
137
+ lines = f.readlines()
138
+ updated_lines = []
139
+ flag = 0
140
+ for line in lines:
141
+ updated_lines.append(line)
142
+ if flag == 0 and 'groups_entities:' in line:
143
+ flag = 1
144
+ if flag==1 and ('ref_mesh: {}' in line):
145
+ if type(surf) == list:
146
+ updated_lines[-1] = f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: {surf}\n'
147
+ else:
148
+ updated_lines[-1] = f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: [{surf}]\n'
149
+ #if not CUT_REFERENCE else f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: '+ str(surf) +'\n'
150
+ flag = -1
151
+
152
+ with open(file, 'w') as f:
153
+ f.writelines(updated_lines)
154
+ logger = logging.getLogger('FiQuS')
155
+ logger.warning("Overwrite the .aux file to include the new domain")
156
+
157
+ mm = Mesh(data=self.fdm, mesh_folder=self.mesh_folder, verbose=self.verbose) ## same mesh object is used for both thermal and EM
95
158
  geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
96
159
  fdm = self.force_symmetry() if 'solenoid' in geom.Roxie_Data.coil.coils[1].type else self.fdm
97
160
  geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
98
161
  mesh_settings = {'EM': fdm.magnet.mesh.electromagnetics, 'TH': fdm.magnet.mesh.thermal}
162
+
99
163
  mesh_type_list = []
100
- if fdm.magnet.mesh.electromagnetics.create: mesh_type_list.append('EM')
101
- if fdm.magnet.mesh.thermal.create: mesh_type_list.append('TH')
164
+ if mesh_settings['EM'].create: mesh_type_list.append('EM')
165
+ if mesh_settings['TH'].create: mesh_type_list.append('TH')
102
166
  for physics_solved in mesh_type_list:
103
167
  self.load_geometry_for_mesh(physics_solved)
104
- if physics_solved == 'TH' and self.fdm.magnet.geometry.thermal.use_TSA:
105
- mm.loadStrandPositions(physics_solved)
168
+ if physics_solved == 'TH' and mesh_settings['TH'].reference.enabled and 'collar' in geometry_settings['TH'].areas:
169
+ if self.fdm.magnet.geometry.thermal.use_TSA_new or self.fdm.magnet.geometry.thermal.use_TSA:
170
+ raise Exception('Reference solution is not implemented for collar with TSA')
171
+ if 'iron_yoke' in geometry_settings['TH'].areas:
172
+ raise Exception('Reference solution is intended (read: hardcoded) without iron yoke')
173
+ _create_physical_group_for_reference(self)
106
174
  mm.loadAuxiliaryFile(physics_solved)
107
- if geometry_settings[physics_solved].with_iron_yoke:
108
- mm.getIronCurvesTags()
175
+ if any(geometry_settings[physics_solved].areas):
176
+ mm.getIronCurvesTags(physics_solved)
109
177
  mm.defineMesh(geometry_settings[physics_solved], mesh_settings[physics_solved], physics_solved)
110
178
  mm.createPhysicalGroups(geometry_settings[physics_solved])
111
179
  mm.updateAuxiliaryFile(physics_solved)
112
180
  if geometry_settings[physics_solved].model_dump().get('use_TSA', False):
113
- mm.rearrangeThinShellsData()
181
+ mm.rearrangeThinShellsData() # rearrange data for the pro file, technically optional
114
182
  mm.assignRegionsTags(geometry_settings[physics_solved], mesh_settings[physics_solved])
115
183
  mm.saveRegionFile(physics_solved)
116
- mm.setMeshOptions(physics_solved)
184
+ mm.setMeshOptions()
117
185
  mm.generateMesh()
118
186
  mm.checkMeshQuality()
119
187
  mm.saveMeshFile(physics_solved)
@@ -121,7 +189,10 @@ class MainMultipole:
121
189
  mm.saveClosestNeighboursList()
122
190
  if self.fdm.magnet.mesh.thermal.isothermal_conductors: mm.selectMeshNodes(elements='conductors')
123
191
  if self.fdm.magnet.geometry.thermal.with_wedges and self.fdm.magnet.mesh.thermal.isothermal_wedges: mm.selectMeshNodes(elements='wedges')
124
- mm.saveRegionCoordinateFile(physics_solved)
192
+ if geometry_settings[physics_solved].model_dump().get('use_TSA_new', False):
193
+ mm.saveClosestNeighboursList_new_TSA()
194
+ mm.saveHalfTurnCornerPositions()
195
+ mm.saveRegionCoordinateFile(physics_solved)
125
196
  mm.clear()
126
197
  mm.ending_step(gui)
127
198
  return mm.mesh_parameters
@@ -134,16 +205,37 @@ class MainMultipole:
134
205
 
135
206
  def solve_and_postprocess_getdp(self, gui: bool = False):
136
207
  an = AssignNaming(data=self.fdm)
137
- rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path, verbose=self.verbose)
208
+ rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path,
209
+ verbose=self.verbose)
210
+ rg.loadRegionFiles()
211
+ if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
212
+ rg.loadRegionCoordinateFile()
213
+ rg.assemblePro()
214
+ start_time = time.time()
215
+ rg.solve_and_postprocess()
216
+ rg.ending_step(gui)
217
+ return time.time() - start_time
218
+
219
+ def solve_and_postprocess_getdp(self, gui: bool = False):
220
+ an = AssignNaming(data=self.fdm)
221
+ rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path,
222
+ verbose=self.verbose)
138
223
  rg.loadRegionFiles()
139
224
  if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
140
225
  rg.loadRegionCoordinateFile()
226
+ rg.read_aux_file(os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}_EM.aux"))
227
+ rg.extract_half_turn_blocks()
228
+ if self.fdm.magnet.geometry.thermal.use_TSA_new:
229
+ rg.read_aux_file(os.path.join(self.mesh_folder,
230
+ f"{self.fdm.general.magnet_name}_TH.aux")) # now load the thermal aux file
231
+ rg.extract_specific_TSA_lines()
141
232
  rg.assemblePro()
142
233
  start_time = time.time()
143
234
  rg.solve_and_postprocess()
144
235
  rg.ending_step(gui)
145
236
  return time.time() - start_time
146
237
 
238
+
147
239
  def post_process_getdp(self, gui: bool = False):
148
240
  an = AssignNaming(data=self.fdm)
149
241
  rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path, verbose=self.verbose)
@@ -1,209 +1,209 @@
1
- import math
2
- import os
3
- import timeit
4
- import json
5
- from typing import List, Any
6
- from pathlib import Path
7
-
8
- import gmsh
9
- import numpy as np
10
- from fiqus.utils.Utils import FilesAndFolders as uff
11
- from fiqus.utils.Utils import GmshUtils
12
- from fiqus.data.DataWindingsCCT import WindingsInformation # for volume information
13
- from fiqus.data.RegionsModelFiQuS import RegionsModel
14
-
15
-
16
- class Mesh:
17
- def __init__(self, fdm, verbose=True):
18
- """
19
- Class to preparing brep files by adding terminals.
20
- :param fdm: FiQuS data model
21
- :param verbose: If True more information is printed in python console.
22
- """
23
- self.cctdm = fdm.magnet
24
- self.model_folder = os.path.join(os.getcwd())
25
- self.magnet_name = fdm.general.magnet_name
26
-
27
- self.geom_folder = Path(self.model_folder).parent
28
-
29
- self.verbose = verbose
30
- regions_file = os.path.join(self.geom_folder, f'{self.magnet_name}.regions')
31
- self.cctrm = uff.read_data_from_yaml(regions_file, RegionsModel)
32
- winding_info_file = os.path.join(self.geom_folder, f'{self.magnet_name}.wi')
33
- self.cctwi = uff.read_data_from_yaml(winding_info_file, WindingsInformation)
34
- self.gu = GmshUtils(self.model_folder, self.verbose)
35
- self.gu.initialize()
36
- self.model_file = f"{os.path.join(self.model_folder, self.magnet_name)}.msh"
37
- self.formers = []
38
- self.powered_vols = []
39
- self.air_boundary_tags = []
40
-
41
- def _find_surf(self, volume_tag):
42
- for surf in gmsh.model.getBoundary([(3, volume_tag)], oriented=False):
43
- _, _, zmin, _, _, zmax = gmsh.model.occ.getBoundingBox(*surf)
44
- z = (zmin + zmax) / 2
45
- if math.isclose(z, self.cctdm.geometry.air.z_min, rel_tol=1e-5) or math.isclose(z, self.cctdm.geometry.air.z_max, rel_tol=1e-5):
46
- return surf[1]
47
-
48
- def generate_physical_groups(self, gui=False):
49
- if self.verbose:
50
- print('Generating Physical Groups Started')
51
- start_time = timeit.default_timer()
52
- r_types = ['w' for _ in self.cctwi.w_names] + ['f' for _ in self.cctwi.f_names] # needed for picking different pow_surf later on
53
- vol_max_loop = 0
54
-
55
- for i, (f_name, r_type, r_name, r_tag) in enumerate(zip(self.cctwi.w_names + self.cctwi.f_names, r_types, self.cctrm.powered['cct'].vol.names, self.cctrm.powered['cct'].vol.numbers)):
56
- vol_dict = json.load(open(f"{os.path.join(self.geom_folder, f_name)}.vi"))
57
- volumes_file = np.array(vol_dict['all'])
58
- vol_max_file = np.max(volumes_file)
59
- vols_to_use = volumes_file + vol_max_loop
60
- v_tags = (list(map(int, vols_to_use)))
61
- vol_max_loop = vol_max_file + vol_max_loop
62
- self.powered_vols.extend(v_tags) # used later for meshing
63
- gmsh.model.addPhysicalGroup(dim=3, tags=v_tags, tag=r_tag)
64
- gmsh.model.setPhysicalName(dim=3, tag=r_tag, name=r_name)
65
- powered_in_surf = self._find_surf(v_tags[0])
66
- gmsh.model.addPhysicalGroup(dim=2, tags=[powered_in_surf], tag=self.cctrm.powered['cct'].surf_in.numbers[i])
67
- gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_in.numbers[i], name=self.cctrm.powered['cct'].surf_in.names[i])
68
- powered_out_surf = self._find_surf(v_tags[-1])
69
- gmsh.model.addPhysicalGroup(dim=2, tags=[powered_out_surf], tag=self.cctrm.powered['cct'].surf_out.numbers[i])
70
- gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_out.numbers[i], name=self.cctrm.powered['cct'].surf_out.names[i])
71
-
72
- vol_max_loop = v_tags[-1]
73
-
74
- for r_name, r_tag in zip(self.cctrm.induced['cct'].vol.names, self.cctrm.induced['cct'].vol.numbers):
75
- vol_max_loop += 1
76
- self.formers.append(vol_max_loop)
77
- gmsh.model.addPhysicalGroup(dim=3, tags=[vol_max_loop], tag=r_tag)
78
- gmsh.model.setPhysicalName(dim=3, tag=r_tag, name=r_name)
79
-
80
- vol_max_loop += 1
81
- gmsh.model.addPhysicalGroup(dim=3, tags=[vol_max_loop], tag=self.cctrm.air.vol.number)
82
- gmsh.model.setPhysicalName(dim=3, tag=self.cctrm.air.vol.number, name=self.cctrm.air.vol.name)
83
- abt = gmsh.model.getEntities(2)[-3:]
84
- self.air_boundary_tags = [surf[1] for surf in abt]
85
- gmsh.model.addPhysicalGroup(dim=2, tags=self.air_boundary_tags, tag=self.cctrm.air.surf.number)
86
- gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.air.surf.number, name=self.cctrm.air.surf.name)
87
-
88
- # air_line_tags = []
89
- # for air_boundary_tag in self.air_boundary_tags:
90
- # air_line_tags.extend(gmsh.model.getBoundary([(2, air_boundary_tag)], oriented=False)[1])
91
- # self.air_center_line_tags = [int(np.max(air_line_tags) + 1)] # this assumes that the above found the lines of air boundary but not the one in the middle that is just with the subsequent tag
92
- # gmsh.model.addPhysicalGroup(dim=1, tags=self.air_center_line_tags, tag=self.cctrm.air.line.number)
93
- # gmsh.model.setPhysicalName(dim=1, tag=self.cctrm.air.line.number, name=self.cctrm.air.line.name)
94
-
95
- gmsh.model.occ.synchronize()
96
- if gui:
97
- self.gu.launch_interactive_GUI()
98
- if self.verbose:
99
- print(f'Generating Physical Groups Took {timeit.default_timer() - start_time:.2f} s')
100
-
101
- def generate_mesh(self, gui=False):
102
- if self.verbose:
103
- print('Generating Mesh Started')
104
- start_time = timeit.default_timer()
105
- # gmsh.option.setNumber("Mesh.AngleToleranceFacetOverlap", 0.01)
106
- gmsh.option.setNumber("Mesh.Algorithm", 5)
107
- gmsh.option.setNumber("Mesh.Algorithm3D", 10)
108
- gmsh.option.setNumber("Mesh.AllowSwapAngle", 20)
109
- line_tags_transfinite = []
110
- num_div_transfinite = []
111
- line_tags_mesh: List[Any] = []
112
- point_tags_mesh = []
113
- for vol_tag in self.powered_vols:
114
- vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
115
- for surf_tag in vol_surf_tags:
116
- line_tags = gmsh.model.getAdjacencies(2, surf_tag)[1]
117
- # line_tags_mesh.extend(line_tags)
118
- line_lengths = []
119
- for line_tag in line_tags:
120
- point_tags = gmsh.model.getAdjacencies(1, line_tag)[1]
121
- if line_tag not in line_tags_mesh:
122
- line_tags_mesh.append(line_tag)
123
- x = []
124
- y = []
125
- z = []
126
- for p, point_tag in enumerate(point_tags):
127
- xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, point_tag)
128
- x.append((xmin + xmax) / 2)
129
- y.append((ymin + ymax) / 2)
130
- z.append((zmin + zmax) / 2)
131
- if point_tag not in point_tags_mesh:
132
- point_tags_mesh.append(point_tag)
133
- line_lengths.append(math.sqrt((x[0] - x[1]) ** 2 + (y[0] - y[1]) ** 2 + (z[0] - z[1]) ** 2))
134
- # num_div = math.ceil(dist / self.cctdm.mesh_generators.MeshSizeWindings) + 1
135
- l_length_min = np.min(line_lengths)
136
- for line_tag, l_length in zip(line_tags, line_lengths):
137
- aspect = l_length / l_length_min
138
- if aspect > self.cctdm.mesh.MaxAspectWindings:
139
- num_div = math.ceil(l_length / (l_length_min * self.cctdm.mesh.MaxAspectWindings)) + 1
140
- if line_tag in line_tags_transfinite:
141
- idx = line_tags_transfinite.index(line_tag)
142
- num_div_set = num_div_transfinite[idx]
143
- if num_div_set < num_div:
144
- num_div_transfinite[idx] = num_div
145
- else:
146
- line_tags_transfinite.append(line_tag)
147
- num_div_transfinite.append(num_div)
148
- for line_tag, num_div in zip(line_tags_transfinite, num_div_transfinite):
149
- gmsh.model.mesh.setTransfiniteCurve(line_tag, num_div)
150
-
151
- gmsh.model.setColor([(1, i) for i in line_tags_mesh], 255, 0, 0) # , recursive=True) # Red
152
-
153
- # gmsh.model.mesh.setTransfiniteCurve(self.air_center_line_tags[0], 15)
154
- # gmsh.model.setColor([(1, i) for i in self.air_center_line_tags], 0, 0, 0)
155
-
156
- sld = gmsh.model.mesh.field.add("Distance") # straight line distance
157
- gmsh.model.mesh.field.setNumbers(sld, "CurvesList", line_tags_mesh)
158
- # gmsh.model.mesh_generators.field.setNumbers(1, "PointsList", point_tags_mesh)
159
- gmsh.model.mesh.field.setNumber(sld, "Sampling", 100)
160
- slt = gmsh.model.mesh.field.add("Threshold") # straight line threshold
161
- gmsh.model.mesh.field.setNumber(slt, "InField", sld)
162
- gmsh.model.mesh.field.setNumber(slt, "SizeMin", self.cctdm.mesh.ThresholdSizeMin)
163
- gmsh.model.mesh.field.setNumber(slt, "SizeMax", self.cctdm.mesh.ThresholdSizeMax)
164
- gmsh.model.mesh.field.setNumber(slt, "DistMin", self.cctdm.mesh.ThresholdDistMin)
165
- gmsh.model.mesh.field.setNumber(slt, "DistMax", self.cctdm.mesh.ThresholdDistMax)
166
- # gmsh.model.mesh_generators.field.add("Min", 7)
167
- # gmsh.model.mesh_generators.field.setNumbers(7, "FieldsList", [slt])
168
- gmsh.model.mesh.field.setAsBackgroundMesh(slt)
169
- gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
170
- gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
171
- gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
172
- gmsh.option.setNumber("Mesh.OptimizeNetgen", 0)
173
- gmsh.model.mesh.generate(3)
174
- if self.verbose:
175
- print(f'Generating Mesh Took {timeit.default_timer() - start_time:.2f} s')
176
- if gui:
177
- self.gu.launch_interactive_GUI()
178
-
179
- def generate_cuts(self, gui=False):
180
- if self.verbose:
181
- print('Generating Cuts Started')
182
- start_time = timeit.default_timer()
183
- for vol, surf_in, surf_out in zip(self.cctrm.powered['cct'].vol.numbers, self.cctrm.powered['cct'].surf_in.numbers, self.cctrm.powered['cct'].surf_out.numbers):
184
- gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], subdomainTags=[surf_in, surf_out], dims=[1, 2, 3])
185
- for vol in self.cctrm.induced['cct'].vol.numbers:
186
- gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], dims=[1, 2, 3])
187
- gmsh.model.mesh.computeHomology()
188
- if self.verbose:
189
- print(f'Generating Cuts Took {timeit.default_timer() - start_time:.2f} s')
190
- if gui:
191
- self.gu.launch_interactive_GUI()
192
-
193
- def save_mesh(self, gui=False):
194
- if self.verbose:
195
- print('Saving Mesh Started')
196
- start_time = timeit.default_timer()
197
- gmsh.write(self.model_file)
198
- if self.verbose:
199
- print(f'Saving Mesh Took {timeit.default_timer() - start_time:.2f} s')
200
- if gui:
201
- self.gu.launch_interactive_GUI()
202
- else:
203
- gmsh.clear()
204
- gmsh.finalize()
205
-
206
- def load_mesh(self, gui=False):
207
- gmsh.open(self.model_file)
208
- if gui:
209
- self.gu.launch_interactive_GUI()
1
+ import math
2
+ import os
3
+ import timeit
4
+ import json
5
+ from typing import List, Any
6
+ from pathlib import Path
7
+
8
+ import gmsh
9
+ import numpy as np
10
+ from fiqus.utils.Utils import FilesAndFolders as uff
11
+ from fiqus.utils.Utils import GmshUtils
12
+ from fiqus.data.DataWindingsCCT import WindingsInformation # for volume information
13
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
14
+
15
+
16
+ class Mesh:
17
+ def __init__(self, fdm, verbose=True):
18
+ """
19
+ Class to preparing brep files by adding terminals.
20
+ :param fdm: FiQuS data model
21
+ :param verbose: If True more information is printed in python console.
22
+ """
23
+ self.cctdm = fdm.magnet
24
+ self.model_folder = os.path.join(os.getcwd())
25
+ self.magnet_name = fdm.general.magnet_name
26
+
27
+ self.geom_folder = Path(self.model_folder).parent
28
+
29
+ self.verbose = verbose
30
+ regions_file = os.path.join(self.geom_folder, f'{self.magnet_name}.regions')
31
+ self.cctrm = uff.read_data_from_yaml(regions_file, RegionsModel)
32
+ winding_info_file = os.path.join(self.geom_folder, f'{self.magnet_name}.wi')
33
+ self.cctwi = uff.read_data_from_yaml(winding_info_file, WindingsInformation)
34
+ self.gu = GmshUtils(self.model_folder, self.verbose)
35
+ self.gu.initialize()
36
+ self.model_file = f"{os.path.join(self.model_folder, self.magnet_name)}.msh"
37
+ self.formers = []
38
+ self.powered_vols = []
39
+ self.air_boundary_tags = []
40
+
41
+ def _find_surf(self, volume_tag):
42
+ for surf in gmsh.model.getBoundary([(3, volume_tag)], oriented=False):
43
+ _, _, zmin, _, _, zmax = gmsh.model.occ.getBoundingBox(*surf)
44
+ z = (zmin + zmax) / 2
45
+ if math.isclose(z, self.cctdm.geometry.air.z_min, rel_tol=1e-5) or math.isclose(z, self.cctdm.geometry.air.z_max, rel_tol=1e-5):
46
+ return surf[1]
47
+
48
+ def generate_physical_groups(self, gui=False):
49
+ if self.verbose:
50
+ print('Generating Physical Groups Started')
51
+ start_time = timeit.default_timer()
52
+ r_types = ['w' for _ in self.cctwi.w_names] + ['f' for _ in self.cctwi.f_names] # needed for picking different pow_surf later on
53
+ vol_max_loop = 0
54
+
55
+ for i, (f_name, r_type, r_name, r_tag) in enumerate(zip(self.cctwi.w_names + self.cctwi.f_names, r_types, self.cctrm.powered['cct'].vol.names, self.cctrm.powered['cct'].vol.numbers)):
56
+ vol_dict = json.load(open(f"{os.path.join(self.geom_folder, f_name)}.vi"))
57
+ volumes_file = np.array(vol_dict['all'])
58
+ vol_max_file = np.max(volumes_file)
59
+ vols_to_use = volumes_file + vol_max_loop
60
+ v_tags = (list(map(int, vols_to_use)))
61
+ vol_max_loop = vol_max_file + vol_max_loop
62
+ self.powered_vols.extend(v_tags) # used later for meshing
63
+ gmsh.model.addPhysicalGroup(dim=3, tags=v_tags, tag=r_tag)
64
+ gmsh.model.setPhysicalName(dim=3, tag=r_tag, name=r_name)
65
+ powered_in_surf = self._find_surf(v_tags[0])
66
+ gmsh.model.addPhysicalGroup(dim=2, tags=[powered_in_surf], tag=self.cctrm.powered['cct'].surf_in.numbers[i])
67
+ gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_in.numbers[i], name=self.cctrm.powered['cct'].surf_in.names[i])
68
+ powered_out_surf = self._find_surf(v_tags[-1])
69
+ gmsh.model.addPhysicalGroup(dim=2, tags=[powered_out_surf], tag=self.cctrm.powered['cct'].surf_out.numbers[i])
70
+ gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_out.numbers[i], name=self.cctrm.powered['cct'].surf_out.names[i])
71
+
72
+ vol_max_loop = v_tags[-1]
73
+
74
+ for r_name, r_tag in zip(self.cctrm.induced['cct'].vol.names, self.cctrm.induced['cct'].vol.numbers):
75
+ vol_max_loop += 1
76
+ self.formers.append(vol_max_loop)
77
+ gmsh.model.addPhysicalGroup(dim=3, tags=[vol_max_loop], tag=r_tag)
78
+ gmsh.model.setPhysicalName(dim=3, tag=r_tag, name=r_name)
79
+
80
+ vol_max_loop += 1
81
+ gmsh.model.addPhysicalGroup(dim=3, tags=[vol_max_loop], tag=self.cctrm.air.vol.number)
82
+ gmsh.model.setPhysicalName(dim=3, tag=self.cctrm.air.vol.number, name=self.cctrm.air.vol.name)
83
+ abt = gmsh.model.getEntities(2)[-3:]
84
+ self.air_boundary_tags = [surf[1] for surf in abt]
85
+ gmsh.model.addPhysicalGroup(dim=2, tags=self.air_boundary_tags, tag=self.cctrm.air.surf.number)
86
+ gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.air.surf.number, name=self.cctrm.air.surf.name)
87
+
88
+ # air_line_tags = []
89
+ # for air_boundary_tag in self.air_boundary_tags:
90
+ # air_line_tags.extend(gmsh.model.getBoundary([(2, air_boundary_tag)], oriented=False)[1])
91
+ # self.air_center_line_tags = [int(np.max(air_line_tags) + 1)] # this assumes that the above found the lines of air boundary but not the one in the middle that is just with the subsequent tag
92
+ # gmsh.model.addPhysicalGroup(dim=1, tags=self.air_center_line_tags, tag=self.cctrm.air.line.number)
93
+ # gmsh.model.setPhysicalName(dim=1, tag=self.cctrm.air.line.number, name=self.cctrm.air.line.name)
94
+
95
+ gmsh.model.occ.synchronize()
96
+ if gui:
97
+ self.gu.launch_interactive_GUI()
98
+ if self.verbose:
99
+ print(f'Generating Physical Groups Took {timeit.default_timer() - start_time:.2f} s')
100
+
101
+ def generate_mesh(self, gui=False):
102
+ if self.verbose:
103
+ print('Generating Mesh Started')
104
+ start_time = timeit.default_timer()
105
+ # gmsh.option.setNumber("Mesh.AngleToleranceFacetOverlap", 0.01)
106
+ gmsh.option.setNumber("Mesh.Algorithm", 5)
107
+ gmsh.option.setNumber("Mesh.Algorithm3D", 10)
108
+ gmsh.option.setNumber("Mesh.AllowSwapAngle", 20)
109
+ line_tags_transfinite = []
110
+ num_div_transfinite = []
111
+ line_tags_mesh: List[Any] = []
112
+ point_tags_mesh = []
113
+ for vol_tag in self.powered_vols:
114
+ vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
115
+ for surf_tag in vol_surf_tags:
116
+ line_tags = gmsh.model.getAdjacencies(2, surf_tag)[1]
117
+ # line_tags_mesh.extend(line_tags)
118
+ line_lengths = []
119
+ for line_tag in line_tags:
120
+ point_tags = gmsh.model.getAdjacencies(1, line_tag)[1]
121
+ if line_tag not in line_tags_mesh:
122
+ line_tags_mesh.append(line_tag)
123
+ x = []
124
+ y = []
125
+ z = []
126
+ for p, point_tag in enumerate(point_tags):
127
+ xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, point_tag)
128
+ x.append((xmin + xmax) / 2)
129
+ y.append((ymin + ymax) / 2)
130
+ z.append((zmin + zmax) / 2)
131
+ if point_tag not in point_tags_mesh:
132
+ point_tags_mesh.append(point_tag)
133
+ line_lengths.append(math.sqrt((x[0] - x[1]) ** 2 + (y[0] - y[1]) ** 2 + (z[0] - z[1]) ** 2))
134
+ # num_div = math.ceil(dist / self.cctdm.mesh_generators.MeshSizeWindings) + 1
135
+ l_length_min = np.min(line_lengths)
136
+ for line_tag, l_length in zip(line_tags, line_lengths):
137
+ aspect = l_length / l_length_min
138
+ if aspect > self.cctdm.mesh.MaxAspectWindings:
139
+ num_div = math.ceil(l_length / (l_length_min * self.cctdm.mesh.MaxAspectWindings)) + 1
140
+ if line_tag in line_tags_transfinite:
141
+ idx = line_tags_transfinite.index(line_tag)
142
+ num_div_set = num_div_transfinite[idx]
143
+ if num_div_set < num_div:
144
+ num_div_transfinite[idx] = num_div
145
+ else:
146
+ line_tags_transfinite.append(line_tag)
147
+ num_div_transfinite.append(num_div)
148
+ for line_tag, num_div in zip(line_tags_transfinite, num_div_transfinite):
149
+ gmsh.model.mesh.setTransfiniteCurve(line_tag, num_div)
150
+
151
+ gmsh.model.setColor([(1, i) for i in line_tags_mesh], 255, 0, 0) # , recursive=True) # Red
152
+
153
+ # gmsh.model.mesh.setTransfiniteCurve(self.air_center_line_tags[0], 15)
154
+ # gmsh.model.setColor([(1, i) for i in self.air_center_line_tags], 0, 0, 0)
155
+
156
+ sld = gmsh.model.mesh.field.add("Distance") # straight line distance
157
+ gmsh.model.mesh.field.setNumbers(sld, "CurvesList", line_tags_mesh)
158
+ # gmsh.model.mesh_generators.field.setNumbers(1, "PointsList", point_tags_mesh)
159
+ gmsh.model.mesh.field.setNumber(sld, "Sampling", 100)
160
+ slt = gmsh.model.mesh.field.add("Threshold") # straight line threshold
161
+ gmsh.model.mesh.field.setNumber(slt, "InField", sld)
162
+ gmsh.model.mesh.field.setNumber(slt, "SizeMin", self.cctdm.mesh.ThresholdSizeMin)
163
+ gmsh.model.mesh.field.setNumber(slt, "SizeMax", self.cctdm.mesh.ThresholdSizeMax)
164
+ gmsh.model.mesh.field.setNumber(slt, "DistMin", self.cctdm.mesh.ThresholdDistMin)
165
+ gmsh.model.mesh.field.setNumber(slt, "DistMax", self.cctdm.mesh.ThresholdDistMax)
166
+ # gmsh.model.mesh_generators.field.add("Min", 7)
167
+ # gmsh.model.mesh_generators.field.setNumbers(7, "FieldsList", [slt])
168
+ gmsh.model.mesh.field.setAsBackgroundMesh(slt)
169
+ gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
170
+ gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
171
+ gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
172
+ gmsh.option.setNumber("Mesh.OptimizeNetgen", 0)
173
+ gmsh.model.mesh.generate(3)
174
+ if self.verbose:
175
+ print(f'Generating Mesh Took {timeit.default_timer() - start_time:.2f} s')
176
+ if gui:
177
+ self.gu.launch_interactive_GUI()
178
+
179
+ def generate_cuts(self, gui=False):
180
+ if self.verbose:
181
+ print('Generating Cuts Started')
182
+ start_time = timeit.default_timer()
183
+ for vol, surf_in, surf_out in zip(self.cctrm.powered['cct'].vol.numbers, self.cctrm.powered['cct'].surf_in.numbers, self.cctrm.powered['cct'].surf_out.numbers):
184
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], subdomainTags=[surf_in, surf_out], dims=[1, 2, 3])
185
+ for vol in self.cctrm.induced['cct'].vol.numbers:
186
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], dims=[1, 2, 3])
187
+ gmsh.model.mesh.computeHomology()
188
+ if self.verbose:
189
+ print(f'Generating Cuts Took {timeit.default_timer() - start_time:.2f} s')
190
+ if gui:
191
+ self.gu.launch_interactive_GUI()
192
+
193
+ def save_mesh(self, gui=False):
194
+ if self.verbose:
195
+ print('Saving Mesh Started')
196
+ start_time = timeit.default_timer()
197
+ gmsh.write(self.model_file)
198
+ if self.verbose:
199
+ print(f'Saving Mesh Took {timeit.default_timer() - start_time:.2f} s')
200
+ if gui:
201
+ self.gu.launch_interactive_GUI()
202
+ else:
203
+ gmsh.clear()
204
+ gmsh.finalize()
205
+
206
+ def load_mesh(self, gui=False):
207
+ gmsh.open(self.model_file)
208
+ if gui:
209
+ self.gu.launch_interactive_GUI()