Coverage for fiqus/mains/MainMultipole.py: 59%
220 statements
« prev ^ index » next coverage.py v7.4.4, created at 2026-01-20 01:41 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2026-01-20 01:41 +0000
1import logging
2import os
3import gmsh
4import time
6from fiqus.plotters.PlotPythonMultipole import PlotPythonMultipole
7from fiqus.utils.Utils import GmshUtils
8from fiqus.utils.Utils import FilesAndFolders as Util
9from fiqus.data import DataFiQuS as dF
10from fiqus.data.DataRoxieParser import FiQuSGeometry
11from fiqus.geom_generators.GeometryMultipole import Geometry
12from fiqus.mesh_generators.MeshMultipole import Mesh
13from fiqus.getdp_runners.RunGetdpMultipole import RunGetdpMultipole
14from fiqus.getdp_runners.RunGetdpMultipole import AssignNaming
15from fiqus.post_processors.PostProcessMultipole import PostProcess
18class MainMultipole:
19 def __init__(self, fdm: dF.FDM = None, rgd_path: str = None, verbose: bool = None, inputs_folder_path = None):
20 """
21 Main class for working with simulations for multipole type magnets
22 :param fdm: FiQuS data model
23 :param rgd_path: ROXIE geometry data path
24 :param verbose: if True, more info is printed in the console
25 """
26 self.fdm = fdm
27 self.rgd = rgd_path
28 self.verbose = verbose
30 self.GetDP_path = None
31 self.geom_folder = None
32 self.mesh_folder = None
33 self.solution_folder = None
34 self.inputs_folder_path = inputs_folder_path
36 def force_symmetry(self):
37 fdm = self.fdm.__deepcopy__()
38 fdm.magnet.geometry.electromagnetics.symmetry = 'x'
39 return fdm
41 def generate_geometry(self, gui: bool = False):
42 geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
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
44 if self.fdm.magnet.geometry.plot_preview:
45 plotter = PlotPythonMultipole(geom, self.fdm)
46 plotter.plot_coil_wedges()
47 gg = Geometry(data=fdm, geom=geom, geom_folder=self.geom_folder, verbose=self.verbose)
48 gg.saveHalfTurnCornerPositions()
50 geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
51 geometry_type_list = []
53 if geometry_settings['EM'].create: geometry_type_list.append('EM')
54 if geometry_settings['TH'].create: geometry_type_list.append('TH')
55 for geometry_type in geometry_type_list:
56 gg.saveStrandPositions(geometry_type)
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)
59 gg.constructCoilGeometry(geometry_type)
60 if geometry_settings[geometry_type].with_wedges:
61 gg.constructWedgeGeometry(geometry_settings[geometry_type].use_TSA if geometry_type == 'TH' else False)
62 gmsh.model.occ.synchronize()
63 if geometry_type == 'TH':
64 if geometry_settings[geometry_type].use_TSA:
65 gg.constructThinShells(geometry_settings[geometry_type].with_wedges)
66 else:
67 gg.constructInsulationGeometry()
68 if geometry_settings[geometry_type].use_TSA_new:
69 gg.constructAdditionalThinShells()
71 gg.buildDomains(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
72 if geometry_type == 'EM':
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()
79 gg.saveBoundaryRepresentationFile(geometry_type)
80 gg.loadBoundaryRepresentationFile(geometry_type)
81 gg.updateTags(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
82 gg.saveAuxiliaryFile(geometry_type)
83 gg.clear()
84 gg.ending_step(gui)
86 def load_geometry(self, gui: bool = False):
87 pass
88 # gu = GmshUtils(self.geom_folder, self.verbose)
89 # gu.initialize(verbosity_Gmsh=self.fdm.run.verbosity_Gmsh)
90 # model_file = os.path.join(self.geom_folder, self.fdm.general.magnet_name)
91 # gmsh.option.setString(name='Geometry.OCCTargetUnit', value='M') # set units to meters
92 # gmsh.open(model_file + '_EM.brep')
93 # gmsh.open(model_file + '_TH.brep')
94 # if gui: gu.launch_interactive_GUI()
96 def pre_process(self, gui: bool = False):
97 pass
99 def load_geometry_for_mesh(self, run_type):
100 gu = GmshUtils(self.geom_folder, self.verbose)
101 gu.initialize(verbosity_Gmsh=self.fdm.run.verbosity_Gmsh)
102 model_file = os.path.join(self.geom_folder, self.fdm.general.magnet_name)
103 gmsh.option.setString(name='Geometry.OCCTargetUnit', value='M') # set units to meters
104 gmsh.open(model_file + f'_{run_type}.brep')
106 def mesh(self, gui: bool = False):
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
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)
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)
127 surf1 = gmsh.model.occ.addPlaneSurface([loop1])
128 surf2 = gmsh.model.occ.addPlaneSurface([loop2])
129 surf = [surf1, surf2] # tag
131 else: raise Exception("Reference meshing is not implemented for this magnet.")
132 gmsh.model.occ.synchronize()
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
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")
157 mm = Mesh(data=self.fdm, mesh_folder=self.mesh_folder, verbose=self.verbose) ## same mesh object is used for both thermal and EM
158 geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
159 fdm = self.force_symmetry() if 'solenoid' in geom.Roxie_Data.coil.coils[1].type else self.fdm
160 geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
161 mesh_settings = {'EM': fdm.magnet.mesh.electromagnetics, 'TH': fdm.magnet.mesh.thermal}
163 mesh_type_list = []
164 if mesh_settings['EM'].create: mesh_type_list.append('EM')
165 if mesh_settings['TH'].create: mesh_type_list.append('TH')
166 for physics_solved in mesh_type_list:
167 self.load_geometry_for_mesh(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)
174 mm.loadAuxiliaryFile(physics_solved)
175 if any(geometry_settings[physics_solved].areas):
176 mm.getIronCurvesTags(physics_solved)
177 mm.defineMesh(geometry_settings[physics_solved], mesh_settings[physics_solved], physics_solved)
178 mm.createPhysicalGroups(geometry_settings[physics_solved])
179 mm.updateAuxiliaryFile(physics_solved)
180 if geometry_settings[physics_solved].model_dump().get('use_TSA', False):
181 mm.rearrangeThinShellsData() # rearrange data for the pro file, technically optional
182 mm.assignRegionsTags(geometry_settings[physics_solved], mesh_settings[physics_solved])
183 mm.saveRegionFile(physics_solved)
184 mm.setMeshOptions()
185 mm.generateMesh()
186 mm.checkMeshQuality()
187 mm.saveMeshFile(physics_solved)
188 if geometry_settings[physics_solved].model_dump().get('use_TSA', False):
189 mm.saveClosestNeighboursList()
190 if self.fdm.magnet.mesh.thermal.isothermal_conductors: mm.selectMeshNodes(elements='conductors')
191 if self.fdm.magnet.geometry.thermal.with_wedges and self.fdm.magnet.mesh.thermal.isothermal_wedges: mm.selectMeshNodes(elements='wedges')
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)
196 mm.clear()
197 mm.ending_step(gui)
198 return mm.mesh_parameters
200 def load_mesh(self, gui: bool = False):
201 gu = GmshUtils(self.geom_folder, self.verbose)
202 gu.initialize(verbosity_Gmsh=self.fdm.run.verbosity_Gmsh)
203 gmsh.open(f"{os.path.join(self.mesh_folder, self.fdm.general.magnet_name)}.msh")
204 if gui: gu.launch_interactive_GUI()
206 def solve_and_postprocess_getdp(self, gui: bool = False):
207 an = AssignNaming(data=self.fdm)
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
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)
223 rg.loadRegionFiles()
224 if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
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()
232 rg.assemblePro()
233 start_time = time.time()
234 rg.solve_and_postprocess()
235 rg.ending_step(gui)
236 return time.time() - start_time
239 def post_process_getdp(self, gui: bool = False):
240 an = AssignNaming(data=self.fdm)
241 rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path, verbose=self.verbose)
242 rg.loadRegionFiles()
243 if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
244 rg.loadRegionCoordinateFile()
245 rg.assemblePro()
246 rg.postprocess()
247 rg.ending_step(gui)
249 def post_process_python(self, gui: bool = False):
250 if self.fdm.run.type == 'post_process_python_only':
251 an = AssignNaming(data=self.fdm)
252 data = an.data
253 else: data = self.fdm
255 run_types = []
256 if self.fdm.magnet.solve.electromagnetics.solve_type: run_types.append('EM')
257 if self.fdm.magnet.solve.thermal.solve_type: run_types.append('TH')
258 pp_settings = {'EM': self.fdm.magnet.postproc.electromagnetics, 'TH': self.fdm.magnet.postproc.thermal}
259 pp = PostProcess(data=data, solution_folder=self.solution_folder, verbose=self.verbose)
260 for run_type in run_types:
261 pp.prepare_settings(pp_settings[run_type])
262 pp.loadStrandPositions(run_type)
263 pp.loadAuxiliaryFile(run_type)
264 if pp_settings[run_type].plot_all != 'False': pp.loadHalfTurnCornerPositions()
265 if pp_settings[run_type].model_dump().get('take_average_conductor_temperature', False): pp.loadRegionFile()
266 pp.postProcess(pp_settings[run_type])
267 if run_type == 'EM' and self.fdm.magnet.geometry.electromagnetics.symmetry != 'none': pp.completeMap2d()
268 pp.clear()
269 pp.ending_step(gui)
270 return pp.postprocess_parameters
272 def plot_python(self):
273 os.chdir(self.solution_folder)
274 p = PlotPythonMultipole(self.fdm, self.fdm)
275 p.plot_coil_wedges()