Coverage for fiqus/mesh_generators/MeshMultipole.py: 88%
1330 statements
« prev ^ index » next coverage.py v7.4.4, created at 2026-02-01 01:38 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2026-02-01 01:38 +0000
1import os
2import re
4import gmsh
5import copy
6import numpy as np
7import json
8import logging
10from fiqus.utils.Utils import GmshUtils
11from fiqus.utils.Utils import FilesAndFolders as Util
12from fiqus.utils.Utils import GeometricFunctions as Func
13from fiqus.data import DataFiQuS as dF
14from fiqus.data import DataMultipole as dM
15from fiqus.data import RegionsModelFiQuS as rM
17logger = logging.getLogger('FiQuS')
19class Mesh:
20 def __init__(self, data: dF.FDM() = None, mesh_folder: str = None,
21 verbose: bool = False):
22 """
23 Class to generate mesh
24 :param data: FiQuS data model
25 :param verbose: If True more information is printed in python console.
26 """
27 self.data: dF.FDM() = data
28 self.mesh_folder = mesh_folder
29 self.verbose: bool = verbose
31 self.md = dM.MultipoleData()
32 self.rc = dM.MultipoleRegionCoordinate()
33 self.rm = rM.RegionsModel()
34 self.strands = None
36 self.gu = GmshUtils(self.mesh_folder, self.verbose)
37 self.gu.initialize(verbosity_Gmsh=self.data.run.verbosity_Gmsh)
38 self.occ = gmsh.model.occ
39 self.mesh = gmsh.model.mesh
41 self.brep_curves = {}
42 for name in list(
43 set(self.data.magnet.geometry.electromagnetics.areas + self.data.magnet.geometry.thermal.areas)):
44 self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
46 self.mesh_parameters = dict.fromkeys(['SJ', 'SICN', 'SIGE', 'Gamma', 'nodes'])
47 self.geom_files = os.path.join(os.path.dirname(self.mesh_folder), self.data.general.magnet_name)
48 self.model_file = os.path.join(self.mesh_folder, self.data.general.magnet_name)
50 # Insulation sequence involving cable insulation only (turn-to-turn, outlying conductor edge)
51 self.ins_type_cond = {}
52 # Insulation sequence involving quench heaters (outlying or mid-layer/pole)
53 qh_keys = {key: {} for key in range(1, self.data.quench_protection.quench_heaters.N_strips + 1)}
54 self.ins_type_qh = {'internal_double': {}, 'internal': copy.deepcopy(qh_keys),
55 'external': copy.deepcopy(qh_keys)}
56 # Insulation sequence between blocks (layer-to-layer, pole-to-pole)
57 self.ins_type = {'mid_pole': {}, 'mid_winding': {}, 'mid_layer': {}, 'aux': {}, 'collar': {}, 'poles': {}}
59 self.qh_data, self.wedge_cond = {}, {}
61 self.colors = {'wedges': [86, 180, 233], # sky blue
62 'insul': [119, 136, 153], # light slate grey
63 'half_turns_pos': [213, 94, 0], # vermilion
64 'half_turns_neg': [255, 136, 42], # light vermilion
65 'air': [240, 228, 66], # yellow
66 'air_inf': [220, 208, 46], # dark yellow
67 # yoke
68 'BHiron1': [0, 114, 178], # blue
69 'BHiron2': [0, 158, 115], # bluish green
70 'BHiron4': [86, 180, 233], # sky blue
71 # key
72 'BHiron3': [220, 208, 46], # dark yellow
73 # [230, 159, 0], # orange
74 'BH_air': [255, 128, 0], # also orange
75 'Air': [255, 128, 0], # also orange
76 'BHiron5': [204, 121, 167], # hopbush
77 'BHiron6': [0, 114, 178], # blue
78 'BHiron7': [204, 121, 167]} # reddish purple
80 def clear(self):
81 self.md = dM.MultipoleData()
82 self.rc = dM.MultipoleRegionCoordinate()
83 self.rm = rM.RegionsModel()
84 for name in self.brep_curves.keys():
85 self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
86 gmsh.clear()
88 def ending_step(self, gui: bool = False):
89 if gui:
90 self.gu.launch_interactive_GUI()
91 else:
92 if gmsh.isInitialized():
93 gmsh.clear()
94 gmsh.finalize()
96 def loadStrandPositions(self, run_type):
97 with open(f"{self.geom_files}_{run_type}.strs", 'r') as f:
98 self.strands = json.load(f)
100 def loadAuxiliaryFile(self, run_type):
101 self.md = Util.read_data_from_yaml(f"{self.geom_files}_{run_type}.aux", dM.MultipoleData)
103 def updateAuxiliaryFile(self, run_type):
104 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.aux', self.md.model_dump())
105 # md2 = Util.read_data_from_yaml(f"{self.geom_files}.aux", dM.MultipoleData)
106 # md2.domains.physical_groups = self.md.domains.physical_groups
107 # Util.write_data_to_yaml(f"{self.geom_files}.aux", md2.dict())
109 def saveMeshFile(self, run_type):
110 gmsh.write(f'{self.model_file}_{run_type}.msh')
112 def saveRegionFile(self, run_type):
113 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.reg', self.rm.model_dump())
115 def saveRegionCoordinateFile(self, run_type):
116 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.reco', self.rc.model_dump())
118 def getIronCurvesTags(self, physics_solved):
119 for t in self.data.magnet.geometry.electromagnetics.areas if physics_solved == 'EM' else self.data.magnet.geometry.thermal.areas:
120 for quadrant, qq in getattr(self.md.geometries, t).quadrants.items():
121 for ar_name, area in qq.areas.items():
122 if area.surface and not re.match(r"^ar.h", ar_name):
123 self.brep_curves[t][quadrant] |= set(gmsh.model.getAdjacencies(2, area.surface)[1])
125 def defineMesh(self, geometry, mesh, run_type):
126 thresholds = []
127 self.occ.synchronize()
129 if mesh.conductors.field.enabled:
130 distance_conductors = self.mesh.field.add("Distance")
131 self.mesh.field.setNumbers(distance_conductors, "CurvesList",
132 [line for coil_nr, coil in
133 self.md.geometries.coil.anticlockwise_order.coils.items() for layer_nr, layer in
134 coil.layers.items()
135 for _, block_order in enumerate(layer) for _, line in
136 self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
137 layer_nr].windings[block_order.winding].blocks[
138 block_order.block].half_turns.lines.items()]
139 )
140 self.mesh.field.setNumber(distance_conductors, "Sampling", 100)
142 threshold_conductors = self.mesh.field.add("Threshold")
143 self.mesh.field.setNumber(threshold_conductors, "InField", distance_conductors)
144 self.mesh.field.setNumber(threshold_conductors, "SizeMin", mesh.conductors.field.SizeMin)
145 self.mesh.field.setNumber(threshold_conductors, "SizeMax", mesh.conductors.field.SizeMax)
146 self.mesh.field.setNumber(threshold_conductors, "DistMin", mesh.conductors.field.DistMin)
147 self.mesh.field.setNumber(threshold_conductors, "DistMax", mesh.conductors.field.DistMax)
148 self.mesh.field.setNumber(threshold_conductors, "StopAtDistMax", 1)
149 thresholds.append(threshold_conductors)
151 if mesh.wedges.field.enabled:
152 distance_wedges = self.mesh.field.add("Distance")
153 self.mesh.field.setNumbers(distance_wedges, "CurvesList",
154 [line for _, coil in self.md.geometries.wedges.coils.items() for _, layer in
155 coil.layers.items() for _, wdg in layer.wedges.items() for _, line in
156 wdg.lines.items()]
157 )
158 self.mesh.field.setNumber(distance_wedges, "Sampling", 100)
160 # raise Exception(f"cannot set threshold for wedges field: {[line for _, coil in self.md.geometries.wedges.coils.items() for _, layer in coil.layers.items() for _, wdg in layer.wedges.items() for _, line in wdg.lines.items()]}")
162 threshold_wedges = self.mesh.field.add("Threshold")
163 self.mesh.field.setNumber(threshold_wedges, "InField", distance_wedges)
164 self.mesh.field.setNumber(threshold_wedges, "SizeMin", mesh.wedges.field.SizeMin)
165 self.mesh.field.setNumber(threshold_wedges, "SizeMax", mesh.wedges.field.SizeMax)
166 self.mesh.field.setNumber(threshold_wedges, "DistMin", mesh.wedges.field.DistMin)
167 self.mesh.field.setNumber(threshold_wedges, "DistMax", mesh.wedges.field.DistMax)
168 self.mesh.field.setNumber(threshold_wedges, "StopAtDistMax", 1)
169 thresholds.append(threshold_wedges)
171 for area in geometry.areas:
172 distance = self.mesh.field.add("Distance")
173 if not (area == 'collar' and self.data.magnet.mesh.thermal.collar.Enforce_TSA_mapping and run_type == 'TH'):
174 self.mesh.field.setNumbers(distance, "CurvesList",
175 [line for _, qq in self.brep_curves[area].items() for line in qq])
176 else:
177 # make sure this does not interfere with the enforced TSA mapping. Apply threshold to the inner collar points and the cooling holes
178 vals = [x for sublist in self.md.geometries.collar.inner_boundary_tags.values() for x in
179 sublist] ## flatten
180 self.mesh.field.setNumbers(distance, "PointsList", list(set([point[1] for point in
181 gmsh.model.getBoundary(
182 [(1, line) for line in vals],
183 combined=False, oriented=False)])))
185 self.mesh.field.setNumber(distance, "Sampling", 100)
187 k = area if area != 'iron_yoke' else 'iron_field'
188 if getattr(mesh, k).enabled:
189 threshold = self.mesh.field.add("Threshold")
190 self.mesh.field.setNumber(threshold, "InField", distance)
191 self.mesh.field.setNumber(threshold, "SizeMin", getattr(mesh, k).SizeMin)
192 self.mesh.field.setNumber(threshold, "SizeMax", getattr(mesh, k).SizeMax)
193 self.mesh.field.setNumber(threshold, "DistMin", getattr(mesh, k).DistMin)
194 self.mesh.field.setNumber(threshold, "DistMax", getattr(mesh, k).DistMax)
195 thresholds.append(threshold)
197 if run_type == 'EM' and mesh.bore_field.enabled:
198 distance_bore = self.mesh.field.add("Distance")
199 self.mesh.field.setNumbers(distance_bore, "PointsList",
200 [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if
201 'bore' in pnt_name])
202 self.mesh.field.setNumber(distance_bore, "Sampling", 100)
204 threshold_bore = self.mesh.field.add("Threshold")
205 self.mesh.field.setNumber(threshold_bore, "InField", distance_bore)
206 self.mesh.field.setNumber(threshold_bore, "SizeMin", mesh.bore_field.SizeMin)
207 self.mesh.field.setNumber(threshold_bore, "SizeMax", mesh.bore_field.SizeMax)
208 self.mesh.field.setNumber(threshold_bore, "DistMin", mesh.bore_field.DistMin)
209 self.mesh.field.setNumber(threshold_bore, "DistMax", mesh.bore_field.DistMax)
210 self.mesh.field.setNumber(threshold_bore, "StopAtDistMax", 1)
211 thresholds.append(threshold_bore)
213 if run_type == 'TH' and mesh.reference.enabled: # add reference meshing between collar and insulation
214 """
215 (hardcoded) REFERENCE MESH for the FalconD-type magnet
216 """
217 if not 'collar' in geometry.areas:
218 raise Exception("Adding the reference segment without collar is not intended.")
219 distance_ref = self.mesh.field.add("Distance")
220 if 'FALCOND_C' in self.data.general.magnet_name.upper() and 'POLE' in self.data.general.magnet_name.upper():
221 lines = list(range(754, 855)) + list(range(910, 1011))
222 self.mesh.field.setNumbers(distance_ref, "CurvesList", lines)
223 else:
224 raise Exception("Reference meshing is not implemented for this magnet.")
226 self.mesh.field.setNumber(distance_ref, "Sampling", 100)
228 threshold_ref = self.mesh.field.add("Threshold")
229 self.mesh.field.setNumber(threshold_ref, "InField", distance_ref)
230 self.mesh.field.setNumber(threshold_ref, "SizeMin", mesh.reference.SizeMin)
231 self.mesh.field.setNumber(threshold_ref, "SizeMax", mesh.reference.SizeMax)
232 self.mesh.field.setNumber(threshold_ref, "DistMin", mesh.reference.DistMin)
233 self.mesh.field.setNumber(threshold_ref, "DistMax", mesh.reference.DistMax)
234 self.mesh.field.setNumber(threshold_ref, "StopAtDistMax", 1)
235 thresholds.append(threshold_ref)
237 insulation_mesh_fields = []
238 if run_type == 'TH':
239 for coil_nr, coil in self.md.geometries.insulation.coils.items():
240 for _, group in coil.group.items():
241 for area_name, area in group.ins.areas.items():
242 if area_name.isdigit():
243 insulation_mesh_fields.append(self.mesh.field.add("Constant"))
244 insulation_mesh_field = insulation_mesh_fields[-1]
245 self.mesh.field.setNumbers(insulation_mesh_field, "SurfacesList", [area.surface])
246 self.mesh.field.setNumber(insulation_mesh_field, "VIn", mesh.insulation.global_size)
248 background = self.mesh.field.add("Min")
249 self.mesh.field.setNumbers(background, "FieldsList", thresholds + insulation_mesh_fields)
250 self.mesh.field.setAsBackgroundMesh(background)
252 # Apply transfinite curves and potentially surfaces to conductors and wedges
253 if mesh.conductors.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
254 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
255 for layer_nr, layer in coil.layers.items():
256 for _, block_order in enumerate(layer):
257 winding = self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
258 layer_nr].windings[block_order.winding]
259 cable = self.data.conductors[winding.conductor_name].cable
260 for line_key, line in winding.blocks[block_order.block].half_turns.lines.items():
261 if mesh.model_dump().get('isothermal_conductors', False):
262 elements = 1
263 elif any([i in line_key for i in ['i', 'o']]):
264 elements = max(1, round(
265 cable.bare_cable_height_mean / mesh.conductors.transfinite.curve_target_size_height))
266 elif any([i in line_key for i in ['l', 'h']]):
267 elements = max(1, round(
268 cable.bare_cable_width / mesh.conductors.transfinite.curve_target_size_width))
270 self.mesh.setTransfiniteCurve(line, elements)
271 if mesh.conductors.transfinite.enabled_for == 'curves_and_surfaces' or mesh.model_dump().get(
272 'isothermal_conductors', False):
273 for _, area in winding.blocks[block_order.block].half_turns.areas.items():
274 self.mesh.setTransfiniteSurface(area.surface)
275 self.mesh.setRecombine(2, area.surface)
277 if 'insulation' in mesh.model_dump() and 'TSA' in mesh.model_dump()["insulation"]:
278 # Apply transfinite curves to thin shell lines
279 if geometry.use_TSA:
280 gts = self.md.geometries.thin_shells
281 conductor_target_sizes = {'width': mesh.conductors.transfinite.curve_target_size_width,
282 'height': mesh.conductors.transfinite.curve_target_size_height}
283 wedge_target_sizes = {'width': mesh.wedges.transfinite.curve_target_size_width,
284 'height': mesh.wedges.transfinite.curve_target_size_height}
285 for ts_group, side in zip([gts.mid_layers_ht_to_ht, gts.mid_layers_ht_to_wdg, gts.mid_layers_wdg_to_ht,
286 gts.mid_layers_wdg_to_wdg,
287 gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn,
288 gts.mid_layers_aux],
289 ['height', 'height', 'height', 'height', 'width', 'width', 'width', 'width',
290 'height']):
291 for ts_name, ts in ts_group.items():
292 for _, line in ts.lines.items() if isinstance(ts, dM.Region) else ts.mid_layers.lines.items():
293 if mesh.isothermal_conductors or mesh.isothermal_wedges:
294 elements = 1
295 else:
296 coords = gmsh.model.getValue(1, line, [i[0] for i in
297 gmsh.model.getParametrizationBounds(1, line)])
298 target_size = wedge_target_sizes[side] if ts_name.count('w') == 2 else \
299 conductor_target_sizes[side]
300 elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size))
301 # it's a wedge
302 if ts_name.count('w') == 2 and mesh.wedges.transfinite.enabled_for in ['curves',
303 'curves_and_surfaces']:
304 self.mesh.setTransfiniteCurve(line, elements)
305 elif ts_name.count('w') != 2 and mesh.conductors.transfinite.enabled_for in ['curves',
306 'curves_and_surfaces']:
307 self.mesh.setTransfiniteCurve(line, elements)
309 if geometry.use_TSA_new:
310 if not self.data.magnet.mesh.thermal.collar.Enforce_TSA_mapping:
311 r_tmp = np.abs(Func.points_distance(coords[:2], [0, 0]))
312 gts = self.md.geometries.thin_shells.collar_layers
313 # conductor and wedge target sizes are defined above
314 collar_size = None
315 # accounts for the distance between the collar and the TSA line: should be col2 = col1*r2/r1. r_tmp is approx. distance outer ht to centre
316 for ts_name, ts in gts.items():
317 for name, line in ts.lines.items():
318 coords = gmsh.model.getValue(1, line,
319 [i[0] for i in gmsh.model.getParametrizationBounds(1, line)])
320 if collar_size is None:
321 collar_size = r_tmp / Func.points_distance(coords[:2], [0,
322 0]) * self.data.magnet.mesh.thermal.collar.SizeMin
323 target_size = min(
324 wedge_target_sizes['width'] if ts_name.startswith('w') else conductor_target_sizes[
325 'width'], collar_size)
326 elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size))
328 self.mesh.setTransfiniteCurve(line, elements)
329 else: # since we force the mesh on the collar side anyway
330 gts = self.md.geometries.thin_shells.collar_layers
331 # conductor and wedge target sizes are defined above
332 collar_size = self.data.magnet.mesh.thermal.collar.SizeMin
333 for ts_name, ts in gts.items():
334 for name, line in ts.lines.items():
335 coords = gmsh.model.getValue(1, line,
336 [i[0] for i in gmsh.model.getParametrizationBounds(1, line)])
337 target_size = collar_size
338 elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size)) + 1
339 if elements % 2 == 1: elements += 1
340 self.mesh.setTransfiniteCurve(line, elements)
342 # COMMENTED since this overwrites also the cable transfinite meshes and in general,
343 # restricting the insulation boundaries to be transfinite seems very restrictive due to their complex geometry
344 # Apply transfinite curves to insulation boundaries
345 # else:
346 # for coil_nr, coil in self.md.geometries.insulation.coils.items():
347 # for group_nr, group in coil.group.items():
348 # cable_height = self.data.conductors[self.md.geometries.coil.coils[coil_nr].poles[
349 # group.blocks[0][0]].layers[group.blocks[0][1]].windings[group.blocks[0][2]].conductor_name].cable.bare_cable_height_mean
350 # for line in [bnd[1] for bnd in gmsh.model.getBoundary(
351 # [(2, list(group.ins.areas.values())[0].surface)], # +
352 # # [(2, ht.surface) for blk_order in group.blocks for ht in
353 # # self.md.geometries.coil.coils[coil_nr].poles[blk_order[0]].layers[blk_order[1]].windings[blk_order[2]].blocks[blk_order[3]].half_turns.areas.values()] +
354 # # [(2, self.md.geometries.wedges.coils[coil_nr].layers[wdg_order[0]].wedges[wdg_order[1]].areas[str(wdg_order[1])].surface) for wdg_order in group.wedges],
355 # combined=True, oriented=False)]:
356 # pnts = gmsh.model.getAdjacencies(1, line)[1]
357 # length = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], []))
358 # self.mesh.setTransfiniteCurve(line,max(1, round(length / (mesh.conductor_target_sizes.width if length > 2 * cable_height else mesh.conductor_target_sizes.height))))
360 # Apply transfinite curves to wedges
361 if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
362 if geometry.with_wedges:
363 for coil_nr, coil in self.md.geometries.wedges.coils.items():
364 for layer_nr, layer in coil.layers.items():
365 for _, wedge in layer.wedges.items():
366 pnts = gmsh.model.getAdjacencies(1, wedge.lines['i'])[1]
367 inner_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
368 gmsh.model.getValue(0, pnts[1], []))
369 pnts = gmsh.model.getAdjacencies(1, wedge.lines['l'])[1]
370 width = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
371 gmsh.model.getValue(0, pnts[1], []))
372 pnts = gmsh.model.getAdjacencies(1, wedge.lines['o'])[1]
373 outer_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
374 gmsh.model.getValue(0, pnts[1], []))
375 for line_key, line in wedge.lines.items():
376 if mesh.model_dump().get('isothermal_wedges', False):
377 elements = 1
378 elif 'i' in line_key:
379 elements = max(1, round(
380 inner_height / mesh.wedges.transfinite.curve_target_size_height))
381 elif 'o' in line_key:
382 elements = max(1, round((inner_height if mesh.wedges.transfinite.enabled_for in [
383 'curves', 'curves_and_surfaces']
384 else outer_height) / mesh.wedges.transfinite.curve_target_size_height))
385 elif any([i in line_key for i in ['l', 'h']]):
386 elements = max(1, round(width / mesh.wedges.transfinite.curve_target_size_width))
387 if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
388 self.mesh.setTransfiniteCurve(line, elements)
389 if mesh.wedges.transfinite.enabled_for == 'curves_and_surfaces' or mesh.model_dump().get(
390 'isothermal_wedges', False):
391 self.mesh.setTransfiniteSurface(list(wedge.areas.values())[0].surface)
392 self.mesh.setRecombine(2, list(wedge.areas.values())[0].surface)
394 def createPhysicalGroups(self, geometry):
395 """
396 Creates physical groups by grouping the mirrored entities according to the Roxie domains
397 """
398 offset: int = 1 if 'symmetry' in geometry.model_dump() else int(1e6)
399 pg_tag = offset
400 point_offset = 4000000
401 point_tag = offset
403 # Create physical groups of iron yoke regions and block insulation
404 pg = self.md.domains.physical_groups
405 ge = self.md.domains.groups_entities
407 # Create the physical groups of iron + set color
408 group_keys = geometry.areas + ['ref_mesh']
409 for key in group_keys:
410 group_surfaces = getattr(ge, key)
411 pg_surfaces = getattr(pg, key).surfaces
412 for group_name, surfaces in group_surfaces.items():
413 pg_surfaces[group_name] = gmsh.model.addPhysicalGroup(2, surfaces, pg_tag)
414 gmsh.model.setPhysicalName(2, pg_surfaces[group_name], group_name)
415 if group_name not in self.colors:
416 logger.warning(f"Color for group '{group_name}' not defined, using default color [0, 0, 0].")
417 color = self.colors.get(group_name, [0, 0, 0])
418 gmsh.model.setColor([(2, i) for i in surfaces], *color)
419 pg_tag += 1
421 # Create the physical group of air infinite + set color of entities.air
422 if 'symmetry' in geometry.model_dump():
423 gmsh.model.setPhysicalName(0, gmsh.model.addPhysicalGroup(
424 0, [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore_field' in pnt_name],
425 pg_tag), 'bore_centers')
426 pg_tag += 1
427 pg.air_inf_bnd = gmsh.model.addPhysicalGroup(1, [self.md.geometries.air_inf.lines['outer']], pg_tag)
428 gmsh.model.setPhysicalName(1, pg.air_inf_bnd, 'air_inf_bnd')
429 pg_tag += 1
430 pg.air_inf = gmsh.model.addPhysicalGroup(2, [self.md.geometries.air_inf.areas['outer'].surface], pg_tag)
431 gmsh.model.setPhysicalName(2, pg.air_inf, 'air_inf')
432 gmsh.model.setColor([(2, self.md.geometries.air_inf.areas['outer'].surface)], self.colors['air_inf'][0],
433 self.colors['air_inf'][1], self.colors['air_inf'][2])
434 pg_tag += 1
435 pg.air = gmsh.model.addPhysicalGroup(2, ge.air, pg_tag)
436 gmsh.model.setPhysicalName(2, pg.air, 'air')
437 gmsh.model.setColor([(2, i) for i in ge.air], self.colors['air'][0], self.colors['air'][1],
438 self.colors['air'][2])
439 pg_tag += 1
441 # Create physical groups of half turns
442 lyr_list_group = []
443 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
444 lyr_list_group.extend(['cl' + str(coil_nr) + 'ly' + str(lyr) for lyr in list(coil.layers.keys())])
445 for layer_nr, layer in coil.layers.items():
446 ht_list_group = []
447 ht_nodes_group = []
448 for nr, block_order in enumerate(layer):
449 wnd = self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
450 layer_nr].windings[block_order.winding]
451 block = wnd.blocks[block_order.block]
452 block_nr = block_order.block
453 pg.blocks[block_nr] = dM.PoweredBlock()
454 blk_pg = pg.blocks[block_nr]
455 blk_pg.current_sign = block.current_sign
456 blk_pg.conductor = wnd.conductor_name
457 color = self.colors['half_turns_pos'] if block.current_sign > 0 else self.colors['half_turns_neg']
458 ht_list = list(block.half_turns.areas.keys())
459 ht_list_group.extend(ht_list)
460 if nr + 1 < len(layer):
461 if layer[nr + 1].pole == block_order.pole and layer[nr + 1].winding != block_order.winding:
462 ht_list_group.append('w' + str(nr))
463 # Create 2D physical groups of half turns
464 for ht_key, ht in block.half_turns.areas.items():
465 blk_pg.half_turns[int(ht_key)] = dM.PoweredGroup()
466 ht_pg = blk_pg.half_turns[int(ht_key)]
467 # Create physical group and assign name and color
468 ht_pg.tag = gmsh.model.addPhysicalGroup(2, [ht.surface], pg_tag)
469 gmsh.model.setPhysicalName(2, ht_pg.tag, ht_key)
470 gmsh.model.setColor([(2, ht.surface)], color[0], color[1], color[2])
471 pg_tag += 1
473 # Assign thin-shell group
474 # the check for reversed block coil is not tested well
475 if geometry.model_dump().get('correct_block_coil_tsa_checkered_scheme', False) and \
476 self.md.geometries.coil.coils[coil_nr].type == 'reversed-block-coil':
477 azimuthal = 'a1' if list(wnd.blocks.keys()).index(block_nr) % 2 == 0 else 'a2'
478 else:
479 azimuthal = 'a1' if lyr_list_group.index(
480 'cl' + str(coil_nr) + 'ly' + str(layer_nr)) % 2 == 0 else 'a2'
481 radial = 'r1' if ht_list_group.index(ht_key) % 2 == 0 else 'r2'
482 ht_pg.group = radial + '_' + azimuthal
484 # Create 1D physical groups of thin shells
485 for ht_line_key, ht_line in block.half_turns.lines.items():
486 ht_nr = ht_line_key[:-1]
487 # Create half turn line groups
488 line_pg = gmsh.model.addPhysicalGroup(1, [ht_line], pg_tag)
489 gmsh.model.setPhysicalName(1, line_pg, ht_line_key)
490 color = [0, 0, 0] if blk_pg.half_turns[int(ht_nr)].group[:2] == 'r1' else [150, 150, 150]
491 gmsh.model.setColor([(1, ht_line)], color[0], color[1], color[2])
492 pg_tag += 1
493 # Store thin shell tags
494 blk_pg.half_turns[int(ht_nr)].lines[ht_line_key[-1]] = line_pg
496 # Add single_node per half turn for EM mesh
497 for ht_key, ht in block.half_turns.areas.items():
498 inner_line = \
499 gmsh.model.getEntitiesForPhysicalGroup(1, blk_pg.half_turns[int(ht_key)].lines['i'])[0]
500 inner_nodes = gmsh.model.getBoundary([(1, inner_line)])
502 higher_line = \
503 gmsh.model.getEntitiesForPhysicalGroup(1, blk_pg.half_turns[int(ht_key)].lines['h'])[0]
504 higher_nodes = gmsh.model.getBoundary([(1, higher_line)])
506 single_node = list(set(inner_nodes) & set(higher_nodes))[0][1]
507 blk_pg.half_turns[int(ht_key)].single_node = gmsh.model.addPhysicalGroup(0, [single_node],
508 point_offset + point_tag,
509 f"{ht_key}_single_node")
511 point_tag += 1
513 # Create points region for projection
514 if 'use_TSA' in geometry.model_dump():
515 point_list = [gmsh.model.getAdjacencies(1, gmsh.model.getAdjacencies(2, ht.surface)[1][0])[1][0]
516 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items()
517 for layer_nr, layer in coil.layers.items() for block_order in layer
518 for ht_key, ht in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
519 layer_nr].windings[block_order.winding].blocks[
520 block_order.block].half_turns.areas.items()]
521 ### add one point per wedge
522 wdg_points = []
523 for coil_nr, coil in self.md.geometries.wedges.coils.items():
524 for layer_nr, layer in coil.layers.items():
525 for wedge_nr, wedge in layer.wedges.items():
526 wdg_points.append(list(gmsh.model.getAdjacencies(2, wedge.areas[str(wedge_nr)].surface)[1])[0])
528 self.md.domains.physical_groups.half_turns_points = gmsh.model.addPhysicalGroup(0, wdg_points + point_list,
529 int(4e6))
530 gmsh.model.setPhysicalName(0, self.md.domains.physical_groups.half_turns_points, 'points')
532 # Create physical groups of insulations
533 if not geometry.model_dump().get('use_TSA', True):
534 for coil_nr, coil in self.md.geometries.insulation.coils.items():
535 for group_nr, group in coil.group.items():
536 # Areas
537 for area_name, area in group.ins.areas.items():
538 if area_name.isdigit():
539 pg.insulations.surfaces[area_name] = gmsh.model.addPhysicalGroup(2, [area.surface], pg_tag)
540 gmsh.model.setPhysicalName(2, pg.insulations.surfaces[area_name], 'insul_' + area_name)
541 gmsh.model.setColor([(2, area.surface)], self.colors['insul'][0], self.colors['insul'][1],
542 self.colors['insul'][2])
543 pg_tag += 1
545 # Boundaries
546 area_name = list(group.ins.areas.keys())[0] # todo: test for Mono
547 pg.insulations.curves['ext' + area_name] = gmsh.model.addPhysicalGroup(
548 1, [bnd[1] for bnd in
549 gmsh.model.getBoundary([(2, list(group.ins.areas.values())[0].surface)], combined=False,
550 oriented=False)[:len(group.ins.lines)]], pg_tag)
551 gmsh.model.setPhysicalName(1, pg.insulations.curves['ext' + area_name], 'insul_ext' + area_name)
552 pg_tag += 1
553 # todo: NOT COMPLETED: would work if the tags were updated in the Geometry script after saving and loading brep
554 # side_lines = {'i': [], 'o': [], 'l': [], 'h': []}
555 # for line_name, line in group.ins.lines.items():
556 # side_lines[line_name[-1] if line_name[-1].isalpha() else sorted(line_name)[-1]].append(line)
557 # for side, side_line in side_lines.items():
558 # pg.insulations.curves[str(group_nr) + side] = gmsh.model.addPhysicalGroup(1, side_line, pg_tag)
559 # gmsh.model.setPhysicalName(1, pg.insulations.curves[str(group_nr) + side], str(group_nr) + side)
560 # pg_tag += 1
562 # Create physical groups of wedges
563 for coil_nr, coil in self.md.geometries.wedges.coils.items():
564 for layer_nr, layer in coil.layers.items():
565 for wedge_nr, wedge in layer.wedges.items():
566 pg.wedges[wedge_nr] = dM.WedgeGroup()
567 wedge_pg = pg.wedges[wedge_nr]
568 wedge_pg.tag = gmsh.model.addPhysicalGroup(2, [wedge.areas[str(wedge_nr)].surface], pg_tag)
569 gmsh.model.setPhysicalName(2, wedge_pg.tag, 'w' + str(wedge_nr))
570 gmsh.model.setColor([(2, wedge.areas[str(wedge_nr)].surface)],
571 self.colors['wedges'][0], self.colors['wedges'][1], self.colors['wedges'][2])
572 pg_tag += 1
573 # Assign thin-shell group
574 prev_block_hts = pg.blocks[layer.block_prev[wedge_nr]].half_turns
575 if len(list(prev_block_hts.keys())) > 1:
576 wedge_pg.group = prev_block_hts[list(prev_block_hts.keys())[-2]].group
577 else:
578 prev_group = prev_block_hts[list(prev_block_hts.keys())[0]].group
579 wedge_pg.group = ('r1' if prev_group[1] == '2' else 'r2') + prev_group[prev_group.index('_'):]
581 for line_key, line in wedge.lines.items():
582 wedge_pg.lines[line_key] = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
583 gmsh.model.setPhysicalName(1, wedge_pg.lines[line_key], 'w' + str(wedge_nr) + line_key)
584 color = [0, 0, 0] if wedge_pg.group[:2] == 'r1' else [150, 150, 150]
585 gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
586 pg_tag += 1
588 # Create physical groups of thin shells
589 if geometry.model_dump().get('use_TSA', False):
590 gts = self.md.geometries.thin_shells
591 # Create physical groups of block mid-layer lines
592 block_coil_flag = False
593 for ts_name, ts in gts.mid_layers_ht_to_ht.items():
594 blk_i, blk_o = ts_name[:ts_name.index('_')], ts_name[ts_name.index('_') + 1:]
595 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
596 if (any(int(blk_i) == blk_order.block for blk_order in
597 self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1]) and
598 any(int(blk_o) == blk_order.block for blk_order in
599 self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[
600 1])): # block-coil mid-pole case
601 block_coil_flag = True
602 for line_name, line in ts.mid_layers.lines.items():
603 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
604 gmsh.model.setPhysicalName(1, line_pg, line_name)
605 pg_tag += 1
606 ht1, ht2 = int(line_name[:line_name.index('_')]), int(line_name[line_name.index('_') + 1:])
607 if ht1 in ts.half_turn_lists[blk_i]:
608 if block_coil_flag:
609 pg.blocks[int(blk_i)].half_turns[ht1].mid_layer_lines.inner[line_name] = line_pg
610 else:
611 pg.blocks[int(blk_i)].half_turns[ht1].mid_layer_lines.outer[line_name] = line_pg
612 pg.blocks[int(blk_o)].half_turns[ht2].mid_layer_lines.inner[line_name] = line_pg
613 else:
614 pg.blocks[int(blk_o)].half_turns[ht1].mid_layer_lines.inner[line_name] = line_pg
615 if block_coil_flag:
616 pg.blocks[int(blk_i)].half_turns[ht2].mid_layer_lines.inner[line_name] = line_pg
617 else:
618 pg.blocks[int(blk_i)].half_turns[ht2].mid_layer_lines.outer[line_name] = line_pg
620 # Create physical groups of wedge-to-block mid-layer lines
621 for ts_name, ts in gts.mid_layers_wdg_to_ht.items():
622 wdg, blk = ts_name.split('_')
623 for line_name, line in ts.lines.items():
624 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
625 gmsh.model.setPhysicalName(1, line_pg, line_name)
626 pg_tag += 1
627 pg.wedges[int(wdg[1:])].mid_layer_lines.outer[line_name] = line_pg
628 ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[
629 line_name.index('_') + 1:]
630 pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.inner[line_name] = line_pg
632 # Create physical groups of block-to-wedge mid-layer lines
633 for ts_name, ts in gts.mid_layers_ht_to_wdg.items():
634 wdg, blk = ts_name.split('_')
635 for line_name, line in ts.lines.items():
636 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
637 gmsh.model.setPhysicalName(1, line_pg, line_name)
638 pg_tag += 1
639 pg.wedges[int(wdg[1:])].mid_layer_lines.inner[line_name] = line_pg
640 ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[
641 line_name.index('_') + 1:]
642 pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.outer[line_name] = line_pg
644 # Create physical groups of wedge-to-wedge mid-layer lines
645 for ts_name, ts in gts.mid_layers_wdg_to_wdg.items():
646 wdg_i, wdg_o = ts_name[1:ts_name.index('_')], ts_name[ts_name.index('_') + 2:]
647 line_pg = gmsh.model.addPhysicalGroup(1, [ts.lines[list(ts.lines.keys())[0]]], pg_tag)
648 gmsh.model.setPhysicalName(1, line_pg, ts_name)
649 pg_tag += 1
650 pg.wedges[int(wdg_i)].mid_layer_lines.outer[ts_name] = line_pg
651 pg.wedges[int(wdg_o)].mid_layer_lines.inner[ts_name] = line_pg
653 # Create non-mid-layer thin shells
654 for ts_group_name, ts_group in zip(['mid_pole', 'mid_winding', 'mid_turn', 'mid_turn', 'aux'],
655 [gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks,
656 gts.mid_wedge_turn, gts.mid_layers_aux]):
657 for ts_name, ts in ts_group.items():
658 if '_' in ts_name:
659 el_name1, el_name2 = ts_name.split('_')
660 el_name1, el_name2 = el_name1.strip('w'), el_name2.strip('w')
661 else: # mid_turn_blocks
662 el_name1, el_name2 = ts_name, ts_name
663 for line_name, line in ts.lines.items():
664 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
665 gmsh.model.setPhysicalName(1, line_pg, line_name)
666 pg_tag += 1
667 if ts_group_name == 'aux':
668 ht1 = int(list(ts.points.keys())[0][:-1])
669 else:
670 ht1, ht2 = line_name.split('_')
671 pg.blocks[int(el_name2)].half_turns[int(ht2)].__dict__[ts_group_name + '_lines'][
672 line_name] = line_pg
673 if ts_name[0] == 'w':
674 pg.wedges[int(el_name1)].__dict__[ts_group_name + '_lines'][line_name] = line_pg
675 else:
676 pg.blocks[int(el_name1)].half_turns[int(ht1)].__dict__[ts_group_name + '_lines'][
677 line_name] = line_pg
679 # Create physical groups for TSL
680 # add the physical group inner collar lines, only nonempty for the thermal solution with collar
681 lines = []
682 for quad, tags in self.md.geometries.collar.outer_boundary_tags.items():
683 if 'poles' in self.data.magnet.geometry.thermal.areas:
684 lines.extend(tags[:-1]) # skip the last line, this is the line between the pole and collar
685 else:
686 lines.extend(tags)
687 gmsh.model.occ.synchronize()
688 tag = gmsh.model.addPhysicalGroup(1, lines)
689 gmsh.model.setPhysicalName(1, tag, "outer_col")
690 pg.outer_col = tag
691 gmsh.model.occ.synchronize()
693 if geometry.model_dump().get('use_TSA_new', False):
694 # line names are defined earlier when creating the geometry (so wedge and coil names are already separated)
695 # f'{ht_nr}_x' for ht to mid and f'w{wedge_idx}_x' for wdg to mid
696 # poles look like f'p{pole_idx}_x'
697 layer = self.md.geometries.thin_shells.collar_layers
698 for name, l in layer.items():
699 line = l.lines['1'] # default line name, contains only one line
700 line_pg = gmsh.model.addPhysicalGroup(1, [line])
701 gmsh.model.setPhysicalName(1, line_pg, name)
702 color = [0, 0, 0]
703 gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
704 # self.ins_type_col['mid_lines'][name] = line_pg
705 self.ins_type['collar'][name] = line_pg
707 # only apply to TSA
708 # add the physical group inner collar lines
709 lines = []
710 for quad, tags in self.md.geometries.collar.inner_boundary_tags.items():
711 lines.extend(tags)
712 gmsh.model.occ.synchronize()
713 tag = gmsh.model.addPhysicalGroup(1, lines)
714 gmsh.model.setPhysicalName(1, tag, "inner_col")
715 pg.inner_col = tag
716 gmsh.model.occ.synchronize()
718 # do the same for the poles, if they are defined
719 if 'poles' in self.data.magnet.geometry.thermal.areas:
720 layer = self.md.geometries.thin_shells.pole_layers
721 for name, l in layer.items():
722 line = l.lines['1'] # default line name, contains only one line
723 line_pg = gmsh.model.addPhysicalGroup(1, [line])
724 gmsh.model.setPhysicalName(1, line_pg, name)
725 color = [0, 0, 0]
726 gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
727 self.ins_type['poles'][name] = line_pg
729 lines = []
730 for quad, tags in self.brep_curves['poles'].items():
731 lines.extend(tags)
732 # maybe only select the lines relevant for TSA?
733 gmsh.model.occ.synchronize()
734 tag = gmsh.model.addPhysicalGroup(1, lines)
735 gmsh.model.setPhysicalName(1, tag, "pole_boundary")
736 pg.poles.curves["bdry"] = tag
737 gmsh.model.occ.synchronize()
739 # Create physical group of collar cooling boudnary
740 if self.data.magnet.solve.thermal.collar_cooling.enabled and 'use_TSA' in geometry.model_dump():
741 ## make physical group for collar cooling
742 tag = gmsh.model.addPhysicalGroup(1, self.md.geometries.collar.cooling_tags)
743 gmsh.model.setPhysicalName(1, tag, "collar cooling")
744 pg.collar_cooling = tag
745 gmsh.model.occ.synchronize()
747 # Create physical groups of symmetric boundaries
748 if geometry.model_dump().get('symmetry', 'none') != 'none':
749 line_tags_normal_free, line_tags_tangent_free = [], []
750 if geometry.symmetry == 'xy':
751 if len(self.md.geometries.coil.coils[1].poles) == 2:
752 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.y
753 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x
754 elif len(self.md.geometries.coil.coils[1].poles) == 4: # todo: think about other multi-pole types
755 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x + \
756 self.md.domains.groups_entities.symmetric_boundaries.y
757 elif geometry.symmetry == 'x':
758 if 'solenoid' in self.md.geometries.coil.coils[1].type:
759 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.x
760 else:
761 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x
762 elif geometry.symmetry == 'y':
763 if len(self.md.geometries.coil.coils[1].poles) == 2:
764 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.y
765 elif len(self.md.geometries.coil.coils[1].poles) == 4:
766 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.y
767 if line_tags_normal_free:
768 pg.symmetric_boundaries.normal_free = gmsh.model.addPhysicalGroup(1, line_tags_normal_free, pg_tag)
769 gmsh.model.setPhysicalName(1, pg.symmetric_boundaries.normal_free, 'normal_free_bnd')
770 pg_tag += 1
771 if line_tags_tangent_free:
772 pg.symmetric_boundaries.tangential_free = gmsh.model.addPhysicalGroup(1, line_tags_tangent_free, pg_tag)
773 gmsh.model.setPhysicalName(1, pg.symmetric_boundaries.tangential_free, 'tangent_free_bnd')
774 pg_tag += 1
776 def rearrangeThinShellsData(self):
777 pg = self.md.domains.physical_groups
778 ins_th = self.md.geometries.thin_shells.ins_thickness
779 qh = self.data.quench_protection.quench_heaters
781 def _store_qh_data(position, thin_shell, ts_tag):
782 qh_ins = self.ins_type_qh[position][qh.iQH_toHalfTurn_From[ht_index]]
783 if thin_shell not in qh_ins: qh_ins[thin_shell] = []
784 qh_ins[thin_shell].append(ts_tag)
785 if thin_shell not in self.qh_data[qh.iQH_toHalfTurn_From[ht_index]]:
786 self.qh_data[qh.iQH_toHalfTurn_From[ht_index]][thin_shell] = {'conductor': blk_pg.conductor,
787 'ht_side': qh_side}
789 def _store_ts_tags(pg_el, geom_ts_name='', geom_ts_name2=None, ts_grp='', lines='', lines_side=None):
790 geom_ts = self.md.geometries.thin_shells.model_dump()[geom_ts_name]
791 for ln_name, ln_tag in (
792 pg_el.model_dump()[lines][lines_side] if lines_side else pg_el.model_dump()[lines]).items():
793 for ts_name in ts_groups[ts_grp]:
794 if ts_name in geom_ts:
795 if ln_name in (
796 geom_ts[ts_name][geom_ts_name2]['lines'] if geom_ts_name2 else geom_ts[ts_name]['lines']):
797 if ts_name not in self.ins_type[ts_grp]: self.ins_type[ts_grp][ts_name] = []
798 self.ins_type[ts_grp][ts_name].append(ln_tag)
799 break
801 # Collect thin shell tags
802 # Half turns
803 for blk_pg_nr, blk_pg in pg.blocks.items():
804 ts_groups = {'mid_wedge_turn': [ts_name for ts_name in self.md.geometries.thin_shells.mid_wedge_turn if
805 blk_pg_nr == int(ts_name.split('_')[1])],
806 'aux': [ts_name for ts_name in ins_th.mid_layer if str(blk_pg_nr) in ts_name.split('_')],
807 'mid_winding': [ts_name for ts_name in ins_th.mid_winding if
808 blk_pg_nr == int(ts_name.split('_')[0])],
809 'mid_pole': [ts_name for ts_name in ins_th.mid_pole if
810 blk_pg_nr == int(ts_name.split('_')[0])],
811 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if
812 ts_name[0] != 'w' and blk_pg_nr == int(ts_name.split('_')[0])
813 or ts_name[0] == 'w' and ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(
814 ts_name.split('_')[1])],
815 'mid_layer_qh_i': [ts_name for ts_name in ins_th.mid_layer if
816 ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(
817 ts_name.split('_')[1])]}
818 ht_list = list(blk_pg.half_turns.keys())
819 self.ins_type_cond[str(blk_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [], 'mid_turn': []}
820 for ht_nr, ht in blk_pg.half_turns.items():
821 if ht_nr in qh.iQH_toHalfTurn_To and qh.N_strips > 0: # check if a quench heater strip touches the current half-turn
822 ht_index = qh.iQH_toHalfTurn_To.index(ht_nr)
823 qh_side = qh.turns_sides[ht_index]
824 if qh.iQH_toHalfTurn_From[ht_index] not in self.qh_data: self.qh_data[
825 qh.iQH_toHalfTurn_From[ht_index]] = {}
826 else:
827 qh_side = ''
829 # find conductor type of ht adjacent to wedge
830 if ht_list.index(ht_nr) == len(ht_list) - 1 and ht.mid_turn_lines:
831 for line_name, line_tag in ht.mid_turn_lines.items():
832 for ts_name in ts_groups['mid_wedge_turn']:
833 if line_name in self.md.geometries.thin_shells.mid_wedge_turn[ts_name].lines:
834 self.wedge_cond[int(ts_name[1:ts_name.index('_')])] = blk_pg.conductor
835 break
837 self.ins_type_cond[str(blk_pg_nr)]['mid_turn'].extend(
838 list(ht.mid_turn_lines.values())) # mid-turn insulation
840 if ht.aux_lines: # outer mid-layer insulation
841 _store_ts_tags(ht, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines')
843 if ht.mid_layer_lines.inner and qh_side == 'i':
844 for line_name, line_tag in ht.mid_layer_lines.inner.items():
845 for ts_name in ts_groups['mid_layer_qh_i']:
846 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht:
847 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
848 elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_ht:
849 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines
850 else:
851 ts_lines = []
852 if line_name in ts_lines:
853 _store_qh_data('internal', ts_name, line_tag)
854 break
855 elif ht.mid_layer_lines.inner: # block-coil (!) inner mid-layer insulation
856 _store_ts_tags(ht, geom_ts_name='mid_layers_ht_to_ht', geom_ts_name2='mid_layers',
857 ts_grp='mid_layer', lines='mid_layer_lines', lines_side='inner')
858 elif not ht.mid_layer_lines.inner and qh_side == 'i': # quench heater inner insulation
859 _store_qh_data('external', blk_pg_nr, ht.lines['i'])
860 elif not ht.mid_layer_lines.inner: # inner insulation
861 self.ins_type_cond[str(blk_pg_nr)]['inner'].append(ht.lines['i'])
863 if ht.mid_layer_lines.outer and qh_side == 'o': # mid-layer quench heater insulation
864 for line_name, line_tag in ht.mid_layer_lines.outer.items():
865 for ts_name in ts_groups['mid_layer']:
866 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht:
867 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
868 elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg:
869 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines
870 else:
871 ts_lines = []
872 if line_name in ts_lines:
873 _store_qh_data('internal', ts_name, line_tag)
874 break
875 elif ht.mid_layer_lines.outer: # mid-layer insulation
876 for line_name, line_tag in ht.mid_layer_lines.outer.items():
877 for ts_name in ts_groups['mid_layer']:
878 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht:
879 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
880 elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg:
881 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines
882 else:
883 ts_lines = []
884 if line_name in ts_lines:
885 if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = []
886 self.ins_type['mid_layer'][ts_name].append(line_tag)
887 break
888 elif not ht.mid_layer_lines.outer and qh_side == 'o': # quench heater outer insulation
889 _store_qh_data('external', blk_pg_nr, ht.lines['o'])
890 else: # outer insulation
891 self.ins_type_cond[str(blk_pg_nr)]['outer'].append(ht.lines['o'])
893 # mid-pole insulation
894 _store_ts_tags(ht, geom_ts_name='mid_poles', ts_grp='mid_pole', lines='mid_pole_lines')
896 # mid-winding insulation
897 _store_ts_tags(ht, geom_ts_name='mid_windings', ts_grp='mid_winding', lines='mid_winding_lines')
899 if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(
900 ht.mid_pole_lines) == 1: # lower angle external side insulation
901 if qh_side == 'l':
902 _store_qh_data('external', blk_pg_nr, ht.lines['l'])
903 else:
904 self.ins_type_cond[str(blk_pg_nr)]['lower'].append(ht.lines['l'])
905 if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(
906 ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # higher angle external side insulation
907 if qh_side == 'h':
908 _store_qh_data('external', blk_pg_nr, ht.lines['h'])
909 else:
910 self.ins_type_cond[str(blk_pg_nr)]['higher'].append(ht.lines['h'])
912 # Wedges
913 for wdg_pg_nr, wdg_pg in pg.wedges.items():
914 ts_groups = {'aux': [ts_name for ts_name in ins_th.mid_layer if 'w' + str(wdg_pg_nr) in ts_name.split('_')],
915 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if
916 'w' + str(wdg_pg_nr) == ts_name.split('_')[0]]}
917 self.ins_type_cond['w' + str(wdg_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [],
918 'mid_turn': []}
919 if wdg_pg.aux_lines: _store_ts_tags(wdg_pg, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines')
920 if not wdg_pg.mid_layer_lines.inner: self.ins_type_cond['w' + str(wdg_pg_nr)]['inner'].append(
921 wdg_pg.lines['i'])
922 if wdg_pg.mid_layer_lines.outer:
923 for line_name, line_tag in wdg_pg.mid_layer_lines.outer.items():
924 for ts_name in ts_groups['mid_layer']:
925 if ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_ht:
926 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines
927 elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_wdg:
928 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_wdg[ts_name].lines
929 else:
930 ts_lines = []
931 if line_name in ts_lines:
932 if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = []
933 self.ins_type['mid_layer'][ts_name].append(line_tag)
934 break
935 else:
936 self.ins_type_cond['w' + str(wdg_pg_nr)]['outer'].append(wdg_pg.lines['o'])
938 # Collect common thin shells for double qh mid-layers
939 for qh_nr, ts_groups in self.ins_type_qh['internal'].items():
940 for qh_nr2, ts_groups2 in self.ins_type_qh['internal'].items():
941 if qh_nr != qh_nr2:
942 common_ts_groups = list(set(ts_groups) & set(ts_groups2))
943 for ts in common_ts_groups:
944 tags, tags2 = ts_groups[ts], ts_groups2[ts]
945 common_tags = list(set(tags) & set(tags2))
946 for tag in common_tags:
947 tags.remove(tag), tags2.remove(tag)
948 if self.qh_data[qh_nr2][ts]['ht_side'] == 'i':
949 qh_name = str(qh_nr) + '_' + str(qh_nr2)
950 else:
951 qh_name = str(qh_nr2) + '_' + str(qh_nr)
952 if qh_name not in self.ins_type_qh['internal_double']: self.ins_type_qh['internal_double'][
953 qh_name] = {}
954 qh_ins_id = self.ins_type_qh['internal_double'][qh_name]
955 if ts not in qh_ins_id: qh_ins_id[ts] = []
956 qh_ins_id[ts].append(tag)
958 def assignRegionsTags(self, geometry, mesh):
959 def _get_input_insulation_data(i_name, i_type=None):
960 ow_idx = next((index for index, couple in enumerate(
961 self.data.magnet.solve.thermal.insulation_TSA.block_to_block.blocks_connection_overwrite)
962 if all(element in couple for element in i_name.split('_'))), None)
963 if i_type == 'mid_winding':
964 mid_mat, mid_th = [self.data.magnet.solve.wedges.material], []
965 elif ow_idx is not None:
966 mid_mat = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.materials_overwrite[ow_idx]
967 mid_th = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.thicknesses_overwrite[ow_idx]
968 else:
969 mid_mat, mid_th = [self.data.magnet.solve.thermal.insulation_TSA.block_to_block.material], []
970 return mid_mat, mid_th
972 def _compute_insulation_thicknesses(tot_th, known_ins_th):
973 if not mid_thicknesses:
974 mid_lyrs = [Func.sig_dig(tot_th - known_ins_th) / len(mid_materials)] * len(mid_materials)
975 elif None in mid_thicknesses:
976 input_ths = sum([th for th in mid_thicknesses if th is not None])
977 mid_lyrs = [
978 th if th is not None else Func.sig_dig(tot_th - known_ins_th - input_ths) / mid_thicknesses.count(
979 None) for th in mid_thicknesses]
980 else:
981 mid_lyrs = mid_thicknesses
982 zeros = [nbr for nbr, th in enumerate(mid_lyrs) if th < 1e-8]
983 if tot_th - known_ins_th - sum(mid_lyrs) < -1e-8:
984 raise ValueError(
985 "Layer-to-layer insulation exceeds the space between blocks: check 'solve'->'insulation_TSA'->'block_to_block'->'thicknesses_overwrite'")
986 else:
987 return mid_lyrs, zeros
989 pg = self.md.domains.physical_groups
990 qh = self.data.quench_protection.quench_heaters
992 # Air and air far field
993 if 'bore_field' in mesh.model_dump():
994 self.rm.air_far_field.vol.radius_out = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies(
995 1, self.md.geometries.air_inf.lines['outer'])[1][0], []), key=abs)))
996 self.rm.air_far_field.vol.radius_in = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies(
997 1, self.md.geometries.air_inf.lines['inner'])[1][0], []), key=abs)))
998 self.rm.air.vol.name = "Air"
999 self.rm.air.vol.number = pg.air
1000 self.rm.air_far_field.vol.names = ["AirInf"]
1001 self.rm.air_far_field.vol.numbers = [pg.air_inf]
1002 self.rm.air_far_field.surf.name = "Surface_Inf"
1003 self.rm.air_far_field.surf.number = pg.air_inf_bnd
1004 if geometry.model_dump().get('symmetry', 'none') != 'none':
1005 self.rm.boundaries.symmetry.normal_free.name = 'normal_free_bnd'
1006 self.rm.boundaries.symmetry.normal_free.number = pg.symmetric_boundaries.normal_free
1007 self.rm.boundaries.symmetry.tangential_free.name = 'tangent_free_bnd'
1008 self.rm.boundaries.symmetry.tangential_free.number = pg.symmetric_boundaries.tangential_free
1010 if 'use_TSA' in geometry.model_dump():
1011 self.rm.projection_points.name = 'projection_points'
1012 self.rm.projection_points.number = self.md.domains.physical_groups.half_turns_points
1014 # Initialize lists
1015 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']:
1016 self.rm.powered[group] = rM.Powered()
1017 self.rm.powered[group].vol.names = []
1018 self.rm.powered[group].vol.numbers = []
1019 for cond_name in self.data.conductors.keys(): self.rm.powered[group].conductors[cond_name] = []
1020 if geometry.with_wedges:
1021 self.rm.induced[group] = rM.Induced()
1022 self.rm.induced[group].vol.names = []
1023 self.rm.induced[group].vol.numbers = []
1024 if 'bore_field' in mesh.model_dump():
1025 initial_current = self.data.power_supply.I_initial
1026 self.rm.powered[group].vol.currents = []
1027 self.rm.powered[group].curve.names = []
1028 self.rm.powered[group].curve.numbers = []
1029 self.rm.powered[group].surf_in.names = []
1030 self.rm.powered[group].surf_in.numbers = []
1031 if geometry.with_wedges:
1032 self.rm.induced[group].surf_in.names = []
1033 self.rm.induced[group].surf_in.numbers = []
1034 if geometry.model_dump().get('use_TSA', False):
1035 self.rm.powered[group].surf_out.names = []
1036 self.rm.powered[group].surf_out.numbers = []
1037 if geometry.with_wedges:
1038 self.rm.induced[group].surf_out.names = []
1039 self.rm.induced[group].surf_out.numbers = []
1041 for attr in geometry.areas:
1042 h = getattr(self.rm, attr).vol
1043 h.names = []
1044 h.numbers = []
1046 # initialise reference
1047 if self.data.magnet.mesh.thermal.reference.enabled:
1048 self.rm.ref_mesh.vol.names = []
1049 self.rm.ref_mesh.vol.numbers = []
1051 self.rm.thin_shells.normals_directed['azimuthally'] = []
1052 self.rm.thin_shells.normals_directed['radially'] = []
1054 if geometry.model_dump().get('use_TSA', False):
1055 unique_thin_shells = []
1056 self.rm.thin_shells.second_group_is_next['azimuthally'] = []
1057 self.rm.thin_shells.second_group_is_next['radially'] = []
1058 else:
1059 self.rm.insulator.vol.names = []
1060 self.rm.insulator.vol.numbers = []
1061 self.rm.insulator.surf.names = []
1062 self.rm.insulator.surf.numbers = []
1064 # always
1065 if 'collar' in geometry.areas:
1066 self.rm.thin_shells.bdry_curves['outer_collar'] = [pg.outer_col]
1067 if geometry.model_dump().get('use_TSA', False):
1068 # Categorize insulation types
1069 min_h = mesh.insulation.global_size
1070 # min_h = 1
1071 # for conductor in self.data.conductors.keys():
1072 # min_h = min([self.data.conductors[conductor].cable.th_insulation_along_height,
1073 # self.data.conductors[conductor].cable.th_insulation_along_width, min_h])
1074 min_h_QH = mesh.insulation.TSA.global_size_QH if mesh.insulation.TSA.global_size_QH else min_h
1075 min_h_COL = mesh.insulation.TSA.global_size_COL if mesh.insulation.TSA.global_size_QH else min_h
1077 # Conductor insulation layers
1078 max_layer = len(
1079 [k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()]) # todo: more elegant way ?
1080 for el, ins in self.ins_type_cond.items():
1081 cond = self.data.conductors[self.wedge_cond[int(el[1:])]].cable if 'w' in el else self.data.conductors[
1082 pg.blocks[int(el)].conductor].cable
1083 for ins_side, tags in ins.items():
1084 if tags:
1085 side_ins_type = [cond.material_insulation]
1086 if ins_side in ['inner', 'outer']:
1087 side_ins = [cond.th_insulation_along_width]
1088 elif ins_side in ['higher', 'lower']:
1089 side_ins = [cond.th_insulation_along_height]
1090 else: # mid_turn
1091 side_ins = [cond.th_insulation_along_height, cond.th_insulation_along_height]
1092 side_ins_type.append(cond.material_insulation)
1093 if ins_side[0] in 'iohl' and el + ins_side[
1094 0] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
1095 add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(
1096 el + ins_side[0])
1097 side_ins.extend(
1098 self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx])
1099 side_ins_type.extend(
1100 self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx])
1101 if ins_side[0] in 'il': side_ins.reverse(), side_ins_type.reverse()
1102 self.rm.thin_shells.insulation_types.layers_number.append(0)
1103 self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags)))
1104 self.rm.thin_shells.insulation_types.thicknesses.append([])
1105 self.rm.thin_shells.insulation_types.layers_material.append([])
1106 if ins_side == 'outer': # scale thin shell lines linked to the collar
1107 if el[0] == 'w': # no wedge correction
1108 DUMMY = 1.0
1109 self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1110 else:
1111 bare_to_ins = (self.data.conductors[
1112 pg.blocks[int(el)].conductor].cable.bare_cable_height_high + 2 *
1113 self.data.conductors[
1114 pg.blocks[int(el)].conductor].cable.th_insulation_along_height) / \
1115 self.data.conductors[
1116 pg.blocks[int(el)].conductor].cable.bare_cable_height_high
1117 for key, ht in pg.blocks[int(el)].half_turns.items(): # try only first key
1118 if str(ht.group[-1]) == str(max_layer): # ensure outer layer !
1119 DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1120 if DUMMY < 0.0: # default value
1121 DUMMY = bare_to_ins
1122 else: # inner layer
1123 # print(ht.lines['o']) #-> without the break this prints the linetags of the desired lines
1124 # we are within the ins_side = "outer" loop so
1125 DUMMY = bare_to_ins
1126 self.rm.thin_shells.insulation_types.correction_factors.append(
1127 DUMMY) # default value if not outer layer
1128 break # break after first key
1129 elif ins_side[0] in 'hl': # hl, long ht side adjacent to the poles
1130 DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_azimuthal)
1131 if DUMMY < 0.0: # default value
1132 DUMMY = 1.0
1133 self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1134 else:
1135 self.rm.thin_shells.insulation_types.correction_factors.append(1.0) # default value
1136 for nr, ins_lyr in enumerate(side_ins):
1137 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h))
1138 self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers
1139 self.rm.thin_shells.insulation_types.thicknesses[-1].extend(
1140 [ins_lyr / tsa_layers] * tsa_layers)
1141 self.rm.thin_shells.insulation_types.layers_material[-1].extend(
1142 [side_ins_type[nr]] * tsa_layers)
1144 # Mid-pole, mid-winding, and mid-layer insulation layers
1145 ins_th_dict = self.md.geometries.thin_shells.ins_thickness.model_dump()
1146 for ins_type, ins in self.ins_type.items():
1147 if ins_type in ['collar', 'poles']:
1148 continue # these are added later
1149 for ins_name, tags in ins.items():
1150 # Get conductors insulation
1151 if ins_name.count('w') == 2:
1152 el1, el2 = int(ins_name[1:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 2:])
1153 cond1 = self.data.conductors[self.wedge_cond[el1]].cable
1154 cond2 = self.data.conductors[self.wedge_cond[el2]].cable
1155 elif ins_name.count('w') == 1:
1156 el1, el2 = int(ins_name[1:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 1:])
1157 cond1 = self.data.conductors[self.wedge_cond[el1]].cable
1158 cond2 = self.data.conductors[pg.blocks[el2].conductor].cable
1159 if ins_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg: cond1, cond2 = cond2, cond1
1160 else:
1161 el1, el2 = int(ins_name[:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 1:])
1162 cond1 = self.data.conductors[pg.blocks[el1].conductor].cable
1163 cond2 = self.data.conductors[pg.blocks[el2].conductor].cable
1164 if ins_type in ['mid_layer', 'aux']:
1165 cond_ins1, cond_ins2 = cond1.th_insulation_along_width, cond2.th_insulation_along_width
1166 else:
1167 cond_ins1, cond_ins2 = cond1.th_insulation_along_height, cond2.th_insulation_along_height
1168 # Get insulation layer thickness
1169 mid_materials, mid_thicknesses = _get_input_insulation_data(ins_name, i_type=ins_type)
1170 # for aux: 1/2 due to triangular insulation shape, 1/2 because the triangle height is half the radial distance between the points of the ht insulation
1171 ins_thickness = 1 / 2 * ins_th_dict['mid_layer'][ins_name] / 2 if ins_type == 'aux' else \
1172 ins_th_dict[ins_type][ins_name]
1173 mid_lyr_th, null_idx = _compute_insulation_thicknesses(ins_thickness, cond_ins1 + cond_ins2)
1174 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
1175 side_ins = [cond_ins1] + mid_lyr_th + [cond_ins2]
1176 # Get insulation materials
1177 side_ins_type = [cond1.material_insulation] + mid_materials + [cond2.material_insulation]
1178 # Initialize sublists
1179 self.rm.thin_shells.insulation_types.layers_number.append(0)
1180 self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags)))
1181 self.rm.thin_shells.insulation_types.thicknesses.append([])
1182 self.rm.thin_shells.insulation_types.layers_material.append([])
1183 self.rm.thin_shells.insulation_types.correction_factors.append(1.0) # default value
1185 for nr, ins_lyr in enumerate(side_ins):
1186 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h))
1187 self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers
1188 self.rm.thin_shells.insulation_types.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
1189 self.rm.thin_shells.insulation_types.layers_material[-1].extend(
1190 [side_ins_type[nr]] * tsa_layers)
1192 # Quench heater insulation layers
1193 for ins_type, ins in self.ins_type_qh.items():
1194 for qh_nr, ts_groups in ins.items():
1195 for ts_name, tags in ts_groups.items():
1196 if ins_type != 'external': mid_materials, mid_thicknesses = _get_input_insulation_data(ts_name)
1197 if tags:
1198 if ins_type == 'external':
1199 data = self.qh_data[qh_nr]
1200 if str(ts_name) + data[ts_name][
1201 'ht_side'] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
1202 add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(
1203 str(ts_name) + data[ts_name]['ht_side'])
1204 additional_ths = \
1205 self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[
1206 add_mat_idx]
1207 additional_mats = \
1208 self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx]
1209 else:
1210 additional_ths, additional_mats = [], []
1211 cond = self.data.conductors[data[ts_name]['conductor']].cable
1212 ht_ins_th = cond.th_insulation_along_width if data[ts_name][
1213 'ht_side'] in 'io' else cond.th_insulation_along_height
1214 side_ins = [ht_ins_th] + [s_ins for s_ins in qh.s_ins[qh_nr - 1]] + [
1215 qh.h[qh_nr - 1]] + [s_ins_He for s_ins_He in
1216 qh.s_ins_He[qh_nr - 1]] + additional_ths
1217 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1218 qh.type_ins[qh_nr - 1]] + ['SS'] + \
1219 [type_ins_He for type_ins_He in
1220 qh.type_ins_He[qh_nr - 1]] + additional_mats
1221 if data[ts_name]['ht_side'] in 'il': side_ins.reverse(), side_ins_type.reverse()
1222 qh_list = [qh_nr]
1223 elif ins_type == 'internal':
1224 data = self.qh_data[qh_nr]
1225 cond = self.data.conductors[data[ts_name]['conductor']].cable
1226 cond2 = self.data.conductors[
1227 self.wedge_cond[int(ts_name.split('_')[0][1:])] if 'w' in ts_name
1228 else pg.blocks[
1229 int(ts_name.split('_')[1]) if data[ts_name]['ht_side'] == 'o' else int(
1230 ts_name.split('_')[0])].conductor].cable
1231 side_ins_qh = [s_ins for s_ins in qh.s_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [s_ins_He
1232 for
1233 s_ins_He
1234 in
1235 qh.s_ins_He[
1236 qh_nr - 1]]
1237 mid_lyr_th, null_idx = _compute_insulation_thicknesses(
1238 ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width,
1239 cond2.th_insulation_along_width] + side_ins_qh))
1240 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
1241 side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + [
1242 cond2.th_insulation_along_width]
1243 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1244 qh.type_ins[qh_nr - 1]] + ['SS'] + \
1245 [type_ins_He for type_ins_He in
1246 qh.type_ins_He[qh_nr - 1]] + mid_materials + [
1247 cond2.material_insulation]
1248 if data[ts_name]['ht_side'] == 'i': side_ins.reverse(), side_ins_type.reverse()
1249 qh_list = [qh_nr]
1250 elif ins_type == 'internal_double':
1251 qh_nr1, qh_nr2 = int(qh_nr.split('_')[0]), int(qh_nr.split('_')[1])
1252 data, data2 = self.qh_data[qh_nr1], self.qh_data[qh_nr2]
1253 cond = self.data.conductors[data[ts_name]['conductor']].cable
1254 cond2 = self.data.conductors[data2[ts_name]['conductor']].cable
1255 side_ins_qh = [s_ins for s_ins in qh.s_ins[qh_nr1 - 1]] + [qh.h[qh_nr1 - 1]] + [s_ins_He
1256 for
1257 s_ins_He
1258 in
1259 qh.s_ins_He[
1260 qh_nr1 - 1]]
1261 side_ins_qh2 = [s_ins_He for s_ins_He in qh.s_ins_He[qh_nr2 - 1][::-1]] + [
1262 qh.h[qh_nr2 - 1]] + [s_ins for s_ins in qh.s_ins[qh_nr2 - 1][::-1]]
1263 mid_lyr_th, null_idx = _compute_insulation_thicknesses(
1264 ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width,
1265 cond2.th_insulation_along_width] + side_ins_qh + side_ins_qh2))
1266 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
1267 side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + side_ins_qh2 + [
1268 cond2.th_insulation_along_width]
1269 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1270 qh.type_ins[qh_nr1 - 1]] + ['SS'] + \
1271 [type_ins_He for type_ins_He in
1272 qh.type_ins_He[qh_nr1 - 1]] + mid_materials + \
1273 [type_ins_He for type_ins_He in qh.type_ins_He[qh_nr2 - 1][::-1]] + [
1274 'SS'] + \
1275 [type_ins for type_ins in qh.type_ins[qh_nr2 - 1][::-1]] + [
1276 cond2.material_insulation]
1277 qh_list = [qh_nr1, qh_nr2]
1278 qh_labels = [1 if m == 'SS' else None for m in side_ins_type]
1279 ss_indexes = [index for index, value in enumerate(qh_labels) if value == 1]
1280 for nr, idx in enumerate(ss_indexes): qh_labels[idx] = qh_list[nr]
1281 self.rm.thin_shells.quench_heaters.layers_number.append(0)
1282 self.rm.thin_shells.quench_heaters.thin_shells.append(list(set(tags)))
1283 self.rm.thin_shells.quench_heaters.thicknesses.append([])
1284 self.rm.thin_shells.quench_heaters.layers_material.append([])
1285 self.rm.thin_shells.quench_heaters.label.append([])
1286 self.rm.thin_shells.quench_heaters.correction_factors.append(1.0) # default value
1287 for nr, ins_lyr in enumerate(side_ins):
1288 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_QH,
1289 round(ins_lyr / min_h_QH))
1290 self.rm.thin_shells.quench_heaters.layers_number[-1] += tsa_layers
1291 self.rm.thin_shells.quench_heaters.thicknesses[-1].extend(
1292 [ins_lyr / tsa_layers] * tsa_layers)
1293 self.rm.thin_shells.quench_heaters.layers_material[-1].extend(
1294 [side_ins_type[nr]] * tsa_layers)
1295 self.rm.thin_shells.quench_heaters.label[-1].extend([qh_labels[nr]] * tsa_layers)
1297 if geometry.model_dump().get('use_TSA_new', False): # collar
1298 total_ins_th_dict = self.md.geometries.thin_shells.ins_thickness.collar
1299 for ins_name, tags in self.ins_type['collar'].items(): # self.ins_type_col['mid_lines']
1300 qh_th = 0.0 # todo, correct for thickness, total ins th already takes into account the insulation layer but not qh
1301 residual_th = total_ins_th_dict[ins_name] - qh_th
1302 side_ins = [residual_th] # only one layer of insulation, as the other layer is captured in QH
1303 # Get insulation materials
1304 side_ins_type = [self.data.magnet.solve.thermal.insulation_TSA.between_collar.material]
1306 self.rm.thin_shells.collar.layers_number.append(0)
1307 self.rm.thin_shells.collar.thin_shells.append([tags])
1308 self.rm.thin_shells.collar.thicknesses.append([])
1309 self.rm.thin_shells.collar.layers_material.append([])
1311 cond_name = next(iter(self.data.conductors.keys()))
1312 ins_to_bare_ratio = (self.data.conductors[cond_name].cable.bare_cable_height_high + 2 *
1313 self.data.conductors[cond_name].cable.th_insulation_along_height) / \
1314 self.data.conductors[cond_name].cable.bare_cable_height_high
1315 DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1316 if DUMMY < 0.0: # default value
1317 DUMMY = ins_to_bare_ratio # ins to bare scaling
1318 self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1320 for nr, ins_lyr in enumerate(side_ins):
1321 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_COL, round(ins_lyr / min_h_COL))
1322 self.rm.thin_shells.collar.layers_number[-1] += tsa_layers
1323 self.rm.thin_shells.collar.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
1324 self.rm.thin_shells.collar.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1326 # mid collar groups
1328 self.rm.thin_shells.ts_collar_groups['1_1'] = []
1329 self.rm.thin_shells.ts_collar_groups['2_1'] = []
1330 self.rm.thin_shells.ts_collar_groups['1_2'] = []
1331 self.rm.thin_shells.ts_collar_groups['2_2'] = []
1333 max_layer = len([k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()])
1334 for blk_nr, blk in self.md.domains.physical_groups.blocks.items(): # only need this for the outer layer :)
1335 for ht_nr, el in blk.half_turns.items():
1336 if str(el.group[-1]) == str(
1337 max_layer): #### 2nd index is swapped due to the checkboard pattern -> otherwise we have to swap it in the .pro file
1338 self.rm.thin_shells.ts_collar_groups[
1339 el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)].append(
1340 self.ins_type['collar'][f'{ht_nr}_x']) # self.ins_type_col['mid_lines']
1342 # wedges
1343 for wdg_nr, el in self.md.domains.physical_groups.wedges.items():
1344 if str(el.group[-1]) == str(max_layer):
1345 self.rm.thin_shells.ts_collar_groups[el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)].append(
1346 self.ins_type['collar'][f'w{wdg_nr}_x']) # self.ins_type_col['mid_lines']
1348 # collar
1349 self.rm.thin_shells.bdry_curves['collar'] = [pg.inner_col]
1351 if geometry.model_dump().get('use_TSA', False): # poles
1352 total_ins_th_dict = self.md.geometries.thin_shells.ins_thickness.poles
1353 for ins_name, tags in self.ins_type['poles'].items():
1354 other_corrections = 0.0 # assuming the other corrections are zero #debug
1355 residual_th = total_ins_th_dict[ins_name] - other_corrections
1356 side_ins = [residual_th]
1357 side_ins_type = [self.data.magnet.solve.thermal.insulation_TSA.between_collar.material]
1358 # assuming the insulation between the pole and ht are the same as for the collar
1360 self.rm.thin_shells.poles.layers_number.append(0)
1361 self.rm.thin_shells.poles.thin_shells.append([tags])
1362 self.rm.thin_shells.poles.thicknesses.append([])
1363 self.rm.thin_shells.poles.layers_material.append([])
1365 # Scaling to the pole lines (2nd insulation layer)
1366 if ins_name.startswith('pw'): # wedge pole line
1367 DUMMY = 1.0 # default
1368 elif ins_name.endswith('_r'): # radial line -> halfturn
1369 DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1370 if DUMMY < 0.0: DUMMY = ins_to_bare_ratio # default value
1371 else: # pole lines
1372 DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_azimuthal)
1373 if DUMMY < 0.0: DUMMY = 1.0 # default value
1374 self.rm.thin_shells.poles.correction_factors.append(DUMMY)
1376 for nr, ins_lyr in enumerate(side_ins):
1377 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations,
1378 round(ins_lyr / min_h)) # use default TSA
1379 self.rm.thin_shells.poles.layers_number[-1] += tsa_layers
1380 self.rm.thin_shells.poles.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
1381 self.rm.thin_shells.poles.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1383 # checkboard pattern to link with the hts
1384 self.rm.thin_shells.ts_pole_groups['a_1_1'] = []
1385 self.rm.thin_shells.ts_pole_groups['a_2_1'] = []
1386 self.rm.thin_shells.ts_pole_groups['a_1_2'] = []
1387 self.rm.thin_shells.ts_pole_groups['a_2_2'] = []
1388 self.rm.thin_shells.ts_pole_groups['r_1_2'] = []
1389 self.rm.thin_shells.ts_pole_groups['r_2_2'] = []
1390 self.rm.thin_shells.ts_pole_groups['r_1_1'] = [] # empty
1391 self.rm.thin_shells.ts_pole_groups['r_2_1'] = [] # empty
1393 for key, tag in self.ins_type['poles'].items():
1394 alignment = key[-1] # either r or a aligned lines
1395 # find the corresponding half turn
1396 nr = key[1:key.index('_')]
1397 if nr.startswith('w'):
1398 # wedge to pole line
1399 for _, gr in self.md.domains.physical_groups.wedges.items():
1400 # for name, tag in gr.aux_lines.items():
1401 tag = gr.aux_lines.get(nr)
1402 if tag is not None:
1403 group = gr.group
1404 group = group[1] + '_' + str(1 + int(group[-1]) % 2)
1405 line_tag = self.ins_type['poles'][f'p{nr}_r'] # need to get the correct tag
1406 self.rm.thin_shells.ts_pole_groups['a_' + group].append(line_tag)
1408 else:
1409 ht_nr = int(nr)
1410 for blk_nr, blk in self.md.domains.physical_groups.blocks.items(): # only need this for the outer layer :)
1411 el = blk.half_turns.get(ht_nr, None)
1412 if el is not None:
1413 break
1414 # alignment : direction of the normal vecetor
1415 if alignment == 'r': #### 2nd index is swapped -> radial difference (inner to outer)
1416 group = el.group[1] + '_' + str(
1417 1 + int(el.group[-1]) % 2) # group = el.group[1] + '_' + el.group[-1] #
1418 self.rm.thin_shells.ts_pole_groups['a_' + group].append(tag)
1419 elif alignment == 'a': #### 1st index is swapped -> azimuthal difference (left to right)
1420 group = str(1 + int(el.group[1]) % 2) + '_' + el.group[
1421 -1] # el.group[1] + '_' + el.group[-1] #
1422 self.rm.thin_shells.ts_pole_groups['r_' + group].append(tag)
1423 # save boundary line for the TSA
1424 tag = pg.poles.curves.get("bdry", None)
1425 if tag is not None:
1426 self.rm.thin_shells.bdry_curves['poles'] = [tag]
1428 # Powered
1429 for blk_nr, blk in pg.blocks.items():
1430 ht_list = list(blk.half_turns.keys())
1431 for ht_nr, ht in blk.half_turns.items():
1432 ht_name = f"ht{ht_nr}_{'EM' if 'bore_field' in mesh.model_dump() else 'TH'}"
1433 self.rm.powered[ht.group].conductors[blk.conductor].append(ht_name)
1434 self.rm.powered[ht.group].vol.names.append(ht_name)
1435 self.rm.powered[ht.group].vol.numbers.append(ht.tag)
1436 if 'bore_field' in mesh.model_dump():
1437 self.rm.powered[ht.group].vol.currents.append(initial_current * (1 if blk.current_sign > 0 else -1))
1438 self.rm.powered[ht.group].curve.names.append(ht_name)
1439 self.rm.powered[ht.group].curve.numbers.append(ht.single_node)
1441 for line in ['l', 'i', 'o', 'h']:
1442 # Bare edges
1443 self.rm.powered[ht.group].surf_in.names.append(ht_name + line)
1444 self.rm.powered[ht.group].surf_in.numbers.append(ht.lines[line])
1445 if line in 'io':
1446 self.rm.thin_shells.normals_directed['radially'].append(ht.lines[line])
1447 else:
1448 self.rm.thin_shells.normals_directed['azimuthally'].append(ht.lines[line])
1450 if geometry.model_dump().get('use_TSA', False):
1451 # Auxiliary thin shells
1452 for line_name, line_tag in ht.aux_lines.items():
1453 # update this must be _out instead of in to be not assigned to bare_layers_{i}_{j} in the .pro
1454 # (not used in my example, since ht.aux_lines is empty)
1455 self.rm.powered[ht.group].surf_out.names.append(line_name)
1456 self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
1457 self.rm.thin_shells.normals_directed['radially'].append(line_tag)
1458 # Thin shells
1459 for line_name, line_tag in dict(ht.mid_layer_lines.inner, **ht.mid_layer_lines.outer).items():
1460 self.rm.powered[ht.group].surf_out.names.append(line_name)
1461 self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
1462 self.rm.thin_shells.normals_directed['radially'].append(line_tag)
1463 for line_name, line_tag in dict(ht.mid_pole_lines, **ht.mid_winding_lines,
1464 **ht.mid_turn_lines).items():
1465 self.rm.powered[ht.group].surf_out.names.append(line_name)
1466 self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
1467 self.rm.thin_shells.normals_directed['azimuthally'].append(line_tag)
1469 # Which thin shells or exterior conductor edges precede a second group (r2 or a2)
1470 if ht.group[1] == '2':
1471 # mid-turn thin shells precede r2
1472 for line_name, line_tag in ht.mid_turn_lines.items():
1473 if (ht_list.index(ht_nr) != 0 and int(line_name[line_name.index('_') + 1:]) == ht_nr) or \
1474 (ht_list.index(ht_nr) == 0 and 'w' in line_name):
1475 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag)
1476 # mid-pole thin shells precede r2
1477 if ht_list.index(ht_nr) == 0 and ht.mid_pole_lines:
1478 self.rm.thin_shells.second_group_is_next['azimuthally'].append(
1479 list(ht.mid_pole_lines.values())[0])
1480 # conductor edges precede r2
1481 elif ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(
1482 ht.mid_pole_lines) == 1:
1483 self.rm.thin_shells.second_group_is_next['azimuthally'].append(ht.lines['l'])
1484 # mid-winding thin shells precede r2
1485 for line_name, line_tag in ht.mid_winding_lines.items():
1486 if int(line_name[line_name.index('_') + 1:]) == ht_nr:
1487 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag)
1488 elif ht_list.index(ht_nr) == len(ht_list) - 1:
1489 # mid-turn thin shells precede r2
1490 for line_name, line_tag in ht.mid_turn_lines.items():
1491 if 'w' in line_name:
1492 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag)
1493 # conductor edges precede r2
1494 if len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1495 self.rm.thin_shells.second_group_is_next['azimuthally'].append(ht.lines['h'])
1496 if ht.group[4] == '2':
1497 # mid-layer thin shells precede a2
1498 for line_name, line_tag in ht.mid_layer_lines.inner.items():
1499 self.rm.thin_shells.second_group_is_next['radially'].append(line_tag)
1500 elif not ht.mid_layer_lines.outer:
1501 # conductor edges precede a2
1502 self.rm.thin_shells.second_group_is_next['radially'].append(ht.lines['o'])
1504 if geometry.model_dump().get('use_TSA', False):
1505 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']:
1506 unique_thin_shells.extend(self.rm.powered[group].surf_out.numbers)
1508 # Wedges
1509 if geometry.with_wedges:
1510 for wdg_nr, wdg in pg.wedges.items():
1511 wdg_name = f"w{wdg_nr}_{'EM' if 'bore_field' in mesh.model_dump() else 'TH'}"
1512 self.rm.induced[wdg.group].vol.names.append(wdg_name)
1513 self.rm.induced[wdg.group].vol.numbers.append(wdg.tag)
1514 for line in ['l', 'i', 'o', 'h']:
1515 self.rm.induced[wdg.group].surf_in.names.append(wdg_name + line)
1516 self.rm.induced[wdg.group].surf_in.numbers.append(wdg.lines[line])
1518 if geometry.model_dump().get('use_TSA', False):
1519 # Bare edges
1520 for line in ['l', 'i', 'o', 'h']:
1521 if line in 'io':
1522 self.rm.thin_shells.normals_directed['radially'].append(wdg.lines[line])
1523 else:
1524 self.rm.thin_shells.normals_directed['azimuthally'].append(wdg.lines[line])
1525 # Auxiliary thin shells
1526 for line_name, line_tag in wdg.aux_lines.items():
1527 # update this must be _out instead of in to be not assigned to bare_layers_{i}_{j} in the .pro
1528 self.rm.induced[wdg.group].surf_out.names.append(line_name)
1529 self.rm.induced[wdg.group].surf_out.numbers.append(line_tag)
1530 self.rm.thin_shells.normals_directed['radially'].append(line_tag)
1531 # Thin shells
1532 for line_name, line_tag in dict(wdg.mid_layer_lines.inner, **wdg.mid_layer_lines.outer).items():
1533 self.rm.induced[wdg.group].surf_out.names.append(line_name)
1534 self.rm.induced[wdg.group].surf_out.numbers.append(line_tag)
1535 self.rm.thin_shells.normals_directed['radially'].append(line_tag)
1536 for line_name, line_tag in wdg.mid_turn_lines.items():
1537 self.rm.induced[wdg.group].surf_out.names.append(line_name)
1538 self.rm.induced[wdg.group].surf_out.numbers.append(line_tag)
1539 self.rm.thin_shells.normals_directed['azimuthally'].append(line_tag)
1540 # Which thin shells or exterior conductor edges precede a second group (r2 or a2)
1541 if wdg.group[4] == '2':
1542 for line_name, line_tag in wdg.mid_layer_lines.inner.items():
1543 if line_name.count('w') == 2:
1544 self.rm.thin_shells.second_group_is_next['radially'].append(line_tag)
1545 elif not wdg.mid_layer_lines.outer:
1546 self.rm.thin_shells.second_group_is_next['radially'].append(wdg.lines['o'])
1547 if geometry.model_dump().get('use_TSA', False):
1548 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']:
1549 unique_thin_shells.extend(self.rm.induced[group].surf_out.numbers)
1551 # Unique mid layers
1552 if geometry.model_dump().get('use_TSA', False):
1553 self.rm.thin_shells.mid_turns_layers_poles = list(set(unique_thin_shells))
1555 # Insulation
1556 for group_name, surface in pg.insulations.surfaces.items():
1557 self.rm.insulator.vol.names.append('ins' + group_name)
1558 self.rm.insulator.vol.numbers.append(surface)
1559 if 'insulation' in mesh.model_dump() and 'TSA' in mesh.model_dump()["insulation"]:
1560 for group_name, curve in pg.insulations.curves.items():
1561 self.rm.insulator.surf.names.append('ins' + group_name)
1562 self.rm.insulator.surf.numbers.append(curve)
1564 for attr in geometry.areas:
1565 for group_name, surface in getattr(pg, attr).surfaces.items():
1566 h = getattr(self.rm, attr).vol
1567 h.names.append(group_name)
1568 h.numbers.append(surface)
1570 if self.data.magnet.mesh.thermal.reference.enabled:
1571 if not pg.ref_mesh.surfaces == {}:
1572 for name, num in pg.ref_mesh.surfaces.items():
1573 self.rm.ref_mesh.vol.names.append(name)
1574 self.rm.ref_mesh.vol.numbers.append(num)
1576 # Boundary conditions
1577 if 'insulation' in mesh.model_dump() and 'TSA' in mesh.model_dump()["insulation"]:
1578 # Initialize lists
1579 for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions,
1580 self.rm.boundaries.thermal): # b.c. type
1581 bc_rm[1].bc.names = []
1582 bc_rm[1].bc.numbers = []
1583 if bc_data[0] == 'cooling':
1584 bc_rm[1].bc.values = []
1585 for group in ['1_r1_a1', '2_r1_a1', '1_r2_a1', '2_r2_a1', '1_r1_a2', '2_r1_a2', '1_r2_a2',
1586 '2_r2_a2']:
1587 bc_rm[1].groups[group] = []
1588 else:
1589 bc_rm[1].bc.value = []
1590 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']:
1591 bc_rm[1].groups[group] = []
1593 # Apply general cooling and adiabatic
1594 if self.data.magnet.solve.thermal.He_cooling.enabled:
1595 cooling_side = {'i': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in
1596 ['inner', 'external']),
1597 'o': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in
1598 ['outer', 'external']),
1599 'hl': self.data.magnet.solve.thermal.He_cooling.sides == 'external'}
1600 else:
1601 cooling_side = {'i': False, 'o': False, 'hl': False}
1603 def __assign_bnd_tag(el, name, side, bc_type, tag=None):
1604 line_tag = tag if tag else el.lines[side]
1605 bnd_list_names[bc_type].append(name + side)
1606 bnd_list_numbers[bc_type].append(line_tag)
1607 if side in 'io':
1608 new_group = el.group[:3] + 'a1' if el.group[4] == '2' else el.group[:3] + 'a2'
1609 else:
1610 new_group = 'r1' + el.group[2:] if el.group[1] == '2' else 'r2' + el.group[2:]
1611 bc_rm[bc_type].groups[new_group].append(line_tag)
1612 for group_name, group in self.rm.thin_shells.groups.items():
1613 if line_tag in group:
1614 bc_rm[bc_type].groups[el.group[0] + '_' + new_group].append(line_tag)
1615 break
1617 bc_rm = {'Robin': self.rm.boundaries.thermal.cooling, 'Neumann': self.rm.boundaries.thermal.heat_flux,
1618 'collar': self.rm.boundaries.thermal.collar}
1619 bnd_list_names = {'Robin': [], 'Neumann': []}
1620 bnd_list_numbers = {'Robin': [], 'Neumann': []}
1621 DISABLE_BNDRY_COND = False # debug: this should always be false
1622 if geometry.model_dump().get('use_TSA', False):
1623 # Half turn boundaries
1624 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
1625 for lyr_nr, orders in coil.layers.items():
1626 for order in orders:
1627 ht_list = list(pg.blocks[order.block].half_turns.keys())
1628 for ht_nr, ht in pg.blocks[
1629 order.block].half_turns.items(): # apply bdry condition if no TSL
1630 if not ht.mid_layer_lines.inner:
1631 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'i',
1632 'Robin' if cooling_side['i'] else 'Neumann')
1633 if not ht.mid_layer_lines.outer:
1634 if DISABLE_BNDRY_COND and self.data.magnet.geometry.thermal.use_TSA_new and \
1635 ht.group[4] == max_layer: # do not add new boundary condition:
1636 logger.warning("\033[93m DISABLING NEUMANN CONDITION")
1637 else:
1638 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o',
1639 'Robin' if cooling_side['o'] else 'Neumann')
1640 if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(
1641 ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1642 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'l',
1643 'Robin' if cooling_side['hl'] else 'Neumann')
1644 if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(
1645 ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1646 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'h',
1647 'Robin' if cooling_side['hl'] else 'Neumann')
1648 if ht.aux_lines:
1649 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o',
1650 'Robin' if cooling_side['hl'] else 'Neumann',
1651 list(ht.aux_lines.values())[0])
1653 # Wedge boundaries
1654 for wdg_nr, wdg in pg.wedges.items():
1655 if not wdg.mid_layer_lines.inner:
1656 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'i', 'Robin' if cooling_side['i'] else 'Neumann')
1657 if not wdg.mid_layer_lines.outer:
1658 if DISABLE_BNDRY_COND and self.data.magnet.geometry.thermal.use_TSA_new and wdg.group[
1659 4] == max_layer:
1660 logger.warning("\033[93m DISABLING NEUMANN CONDITION")
1661 else:
1662 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann')
1663 if wdg.aux_lines:
1664 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann',
1665 list(wdg.aux_lines.values())[0])
1667 else: # insulation case, no TSA
1668 for curves_group, tag in pg.insulations.curves.items():
1669 if self.data.magnet.solve.thermal.He_cooling.enabled:
1670 if self.data.magnet.solve.thermal.He_cooling.sides == 'external':
1671 bc_type = 'Robin'
1672 else:
1673 raise ValueError(
1674 f"Cooling side '{self.data.magnet.solve.thermal.He_cooling.sides}' is not supported for meshed insulation models.")
1675 # bc_type = 'Robin' if (self.data.magnet.solve.thermal.He_cooling.sides == 'external' or
1676 # ('inner' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'i') or
1677 # ('outer' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'o')) else 'Neumann'
1678 else:
1679 bc_type = 'Neumann'
1680 bnd_list_names[bc_type].append('ins' + curves_group)
1681 bnd_list_numbers[bc_type].append(tag)
1683 if self.data.magnet.solve.thermal.He_cooling.enabled:
1684 bc_rm['Robin'].bc.names.append(bnd_list_names['Robin'])
1685 bc_rm['Robin'].bc.numbers.append(bnd_list_numbers['Robin'])
1686 bc_rm['Robin'].bc.values.append([self.data.magnet.solve.thermal.He_cooling.heat_transfer_coefficient,
1687 self.data.magnet.solve.thermal.init_temperature])
1689 if self.data.magnet.solve.thermal.collar_cooling.enabled:
1690 cool = self.data.magnet.solve.thermal.collar_cooling
1691 bc_rm['collar'].bc.numbers = [pg.collar_cooling] # only one physical group for the collar cooling
1692 bc_rm['collar'].bc.values = [self.data.magnet.solve.thermal.collar_cooling.heat_transfer_coefficient,
1693 cool.ref_temperature if cool.ref_temperature is not None else self.data.magnet.solve.thermal.init_temperature] # [coef or functionname, inittemp]
1695 # save the boundary names and numbers
1696 if bnd_list_names['Neumann']:
1697 bc_rm['Neumann'].bc.names.append(bnd_list_names['Neumann'])
1698 bc_rm['Neumann'].bc.numbers.append(bnd_list_numbers['Neumann'])
1699 bc_rm['Neumann'].bc.value.append(0.)
1701 # Apply specific boundary conditions
1702 for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions,
1703 self.rm.boundaries.thermal): # b.c. type
1704 # bc_data is a tuple like: ('temperature', {'const_T1': boundaries, value)})
1705 # bc_rm is a tuple like: ('temperature', DirichletCondition(names, numbers, value))
1707 for _, bc in bc_data[
1708 1].items(): # all boundary conditions of one b.c. type (e.g., Dirichlet with different temperatures)
1709 bnd_list_names = []
1710 bnd_list_numbers = []
1711 if geometry.model_dump().get('use_TSA', False):
1712 for bnd in bc.boundaries: # all boundaries of one boundary condition
1713 if bnd[0] == 'w':
1714 if not geometry.with_wedges:
1715 raise Exception('Wedge regions are disabled.')
1716 # Fetch the physical group of the wedge
1717 pg_el = pg.wedges[int(bnd[1:-1])]
1718 name = 'wd' + bnd[1:]
1719 else:
1720 # Fetch the physical group of the half turn
1721 ht_index = self.strands['ht'].index(int(bnd[:-1]))
1722 pg_el = pg.blocks[self.strands['block'][ht_index]].half_turns[int(bnd[:-1])]
1723 name = 'ht' + bnd
1724 line_pg_tag = pg_el.lines[bnd[-1]]
1725 bnd_list_names.append(name)
1726 bnd_list_numbers.append(line_pg_tag)
1727 # Find the half turn group this boundary is assigned to and take the complementary
1728 if bnd[-1] in 'io':
1729 new_group = pg_el.group[:3] + 'a1' if pg_el.group[4] == '2' else pg_el.group[:3] + 'a2'
1730 else: # ['l', 'h'] todo: if applied to an inner line (i.e., not domain boundaries), extra code needed because the line would belong to two groups
1731 new_group = 'r1' + pg_el.group[2:] if pg_el.group[1] == '2' else 'r2' + pg_el.group[2:]
1732 # Overwrite general cooling and adiabatic condition
1733 if self.data.magnet.solve.thermal.He_cooling.enabled:
1734 if name in self.rm.boundaries.thermal.cooling.bc.names[0]:
1735 bnd_idx = self.rm.boundaries.thermal.cooling.bc.names[0].index(name)
1736 self.rm.boundaries.thermal.cooling.bc.names[0].pop(bnd_idx)
1737 bnd_idx = self.rm.boundaries.thermal.cooling.bc.numbers[0].pop(bnd_idx)
1738 self.rm.boundaries.thermal.cooling.groups[new_group].pop(
1739 self.rm.boundaries.thermal.cooling.groups[new_group].index(bnd_idx))
1740 if self.data.magnet.solve.thermal.He_cooling.sides != 'external':
1741 if name in self.rm.boundaries.thermal.heat_flux.bc.names[0]:
1742 bnd_idx = self.rm.boundaries.thermal.heat_flux.bc.names[0].index(name)
1743 self.rm.boundaries.thermal.heat_flux.bc.names[0].pop(bnd_idx)
1744 bnd_idx = self.rm.boundaries.thermal.heat_flux.bc.numbers[0].pop(bnd_idx)
1745 self.rm.boundaries.thermal.heat_flux.groups[new_group].pop(
1746 self.rm.boundaries.thermal.heat_flux.groups[new_group].index(bnd_idx))
1747 # Assign the tag
1748 bc_rm[1].groups[new_group].append(line_pg_tag)
1749 # Extra grouping for Robin virtual shells
1750 if bc_data[0] == 'cooling':
1751 for group_name, group in self.rm.thin_shells.groups.items():
1752 if line_pg_tag in group:
1753 bc_rm[1].groups[group_name[0] + '_' + new_group].append(line_pg_tag)
1754 break
1755 else: # the b.c. are assigned to insulation boundaries instead
1756 pass # todo: not supported yet
1757 bc_rm[1].bc.names.append(bnd_list_names)
1758 bc_rm[1].bc.numbers.append(bnd_list_numbers)
1759 if bc_data[0] == 'cooling':
1760 bc_rm[1].bc.values.append(
1761 [bc.heat_transfer_coefficient, self.data.magnet.solve.thermal.init_temperature])
1762 elif bc_data[0] == 'temperature':
1763 bc_rm[1].bc.value.append(bc.const_temperature)
1764 elif bc_data[0] == 'heat_flux':
1765 bc_rm[1].bc.value.append(bc.const_heat_flux)
1767 def setMeshOptions(self):
1768 """
1769 Meshes the generated domain
1770 """
1771 gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
1772 gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
1773 gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
1774 gmsh.option.setNumber("Mesh.Algorithm", 6)
1775 gmsh.option.setNumber("Mesh.Optimize", 1)
1776 gmsh.option.setNumber("Mesh.ElementOrder", 1)
1778 def generateMesh(self):
1779 gmsh.option.setNumber("General.Terminal", self.verbose)
1780 self.mesh.generate(2)
1781 # self.mesh.removeDuplicateNodes()
1782 # self.occ.synchronize()
1783 # self.gu.launch_interactive_GUI()
1785 def checkMeshQuality(self):
1786 tags = self.mesh.getElements(2)[1][0]
1788 self.mesh_parameters['SJ'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSJ'))
1789 self.mesh_parameters['SICN'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSICN'))
1790 self.mesh_parameters['SIGE'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSIGE'))
1791 self.mesh_parameters['Gamma'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='gamma'))
1792 self.mesh_parameters['nodes'] = len(self.mesh.getNodes()[0])
1794 def saveClosestNeighboursList(self):
1796 def _closest_node_on_reference(origin_points, reference_points):
1797 """
1798 Compute list of lists with each list representing one original node (3 entries for 3 coordinates) and the closest
1799 reference mesh node (3 entries for 3 coordinates).
1800 :param origin_points: list of origin point as list of 3 floats per origin node
1801 :param reference_points: list of coordinates of reference mesh points as list of 3 floats per reference mesh node
1802 :return: returns list of lists with the closest reference mesh node per origin node
1803 """
1804 closest_node = []
1805 for x in range(0, len(origin_points), 3):
1806 origin_point = origin_points[x:x + 3]
1807 # Compute distance list between origin point and reference point list
1808 dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in
1809 range(0, len(reference_points), 3)]
1810 min_idx = 3 * np.argmin(dist_lst)
1811 closest_node.append(origin_point + reference_points[min_idx:min_idx + 3])
1812 return closest_node
1814 def _get_closest_nodes(side):
1815 origin_list = self.mesh.getNodesForPhysicalGroup(1, mid_layer)[1].tolist()
1816 reference_list = self.mesh.getNodesForPhysicalGroup(1, el.lines[side])[1].tolist()
1817 self.rc.neighbouring_nodes.groups[group].extend(
1818 [node for node_list in _closest_node_on_reference(origin_list, reference_list) for node in node_list])
1820 logger.info(
1821 f"Info : {self.data.general.magnet_name} - F i n d i n g C l o s e s t N e i g h b o u r s . . .")
1822 logger.info(f"Info : Finding closest reference nodes ...")
1824 self.rc.neighbouring_nodes.groups['1_1'] = []
1825 self.rc.neighbouring_nodes.groups['2_1'] = []
1826 self.rc.neighbouring_nodes.groups['1_2'] = []
1827 self.rc.neighbouring_nodes.groups['2_2'] = []
1829 for blk_nr, blk in self.md.domains.physical_groups.blocks.items():
1830 for ht_nr, el in blk.half_turns.items():
1831 ht_list = list(blk.half_turns.keys())
1832 group = el.group[1] + '_' + el.group[-1]
1833 for line_name, mid_layer in el.mid_layer_lines.inner.items(): _get_closest_nodes('i')
1834 for line_name, mid_layer in el.mid_layer_lines.outer.items(): _get_closest_nodes('o')
1835 for line_name, mid_layer in el.aux_lines.items(): _get_closest_nodes('o')
1836 for line_name, mid_layer in el.mid_pole_lines.items(): _get_closest_nodes(
1837 'l' if ht_list.index(ht_nr) == 0 else 'h')
1838 for line_name, mid_layer in el.mid_winding_lines.items(): _get_closest_nodes(
1839 'l' if ht_list.index(ht_nr) == 0 else 'h')
1840 for line_name, mid_layer in el.mid_turn_lines.items():
1841 high = ht_list.index(ht_nr) == len(ht_list) - 1 if 'w' in line_name \
1842 else int(line_name[:line_name.index('_')]) == ht_nr
1843 _get_closest_nodes('h' if high else 'l')
1844 for wdg_nr, el in self.md.domains.physical_groups.wedges.items():
1845 group = el.group[1] + '_' + el.group[-1]
1846 for line_name, mid_layer in el.mid_layer_lines.inner.items(): _get_closest_nodes('i')
1847 for line_name, mid_layer in el.mid_layer_lines.outer.items(): _get_closest_nodes('o')
1848 for line_name, mid_layer in el.aux_lines.items(): _get_closest_nodes('o')
1849 for line_name, mid_layer in el.mid_turn_lines.items():
1850 _get_closest_nodes('l' if line_name == list(el.mid_turn_lines.keys())[0] else 'h')
1852 logger.info(
1853 f"Info : {self.data.general.magnet_name} - E n d F i n d i n g C l o s e s t N e i g h b o u r s")
1855 def saveClosestNeighboursList_new_TSA(self):
1856 def _closest_node_on_reference(origin_points, reference_points):
1857 closest_node = []
1858 for x in range(0, len(origin_points), 3):
1859 origin_point = origin_points[x:x + 3]
1860 # Compute distance list between origin point and reference point list
1861 dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in
1862 range(0, len(reference_points), 3)]
1863 min_idx = 3 * np.argmin(dist_lst)
1864 closest_node.append(origin_point + reference_points[min_idx:min_idx + 3])
1865 if not closest_node:
1866 raise ValueError("No closest nodes found - check mesh and physical groups!")
1867 return closest_node
1869 def _get_closest_nodes(name, reference_list, origin_list):
1870 self.rc.neighbouring_nodes.groups[name].extend(
1871 [node for node_list in _closest_node_on_reference(origin_list, reference_list) for node in node_list])
1873 logger.info(
1874 f"Info : {self.data.general.magnet_name} - F i n d i n g C l o s e s t N e i g h b o u r s (new TSA) . . .")
1875 logger.info(f"Info : Finding closest reference nodes ...")
1877 # two of these will always be empty, but depending on the layers it will either be a1 or a2
1878 self.rc.neighbouring_nodes.groups['mid2ht_1_1'] = []
1879 self.rc.neighbouring_nodes.groups['mid2ht_2_1'] = []
1880 self.rc.neighbouring_nodes.groups['mid2ht_1_2'] = []
1881 self.rc.neighbouring_nodes.groups['mid2ht_2_2'] = []
1882 # map coils to origin_list: start with outer layer of HT
1883 max_layer = len([k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()])
1885 origin_all = [self.mesh.getNodesForPhysicalGroup(1, line)[1].tolist() for _, line in
1886 self.ins_type['collar'].items()]
1887 origin_all = [node for sublist in origin_all for node in sublist] # flatten the list
1889 for blk_nr, blk in self.md.domains.physical_groups.blocks.items():
1890 for ht_nr, el in blk.half_turns.items():
1891 if str(el.group[-1]) == str(max_layer):
1892 group = el.group[1] + '_' + str((1 + int(el.group[-1])) % 2) # e.g. 1_2
1893 origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['collar'][str(ht_nr) + '_x'])[
1894 1].tolist()
1895 ref_list = np.array(self.mesh.getNodesForPhysicalGroup(1, el.lines['o'])[1]).tolist()
1896 _get_closest_nodes(reference_list=ref_list, name=f'mid2ht_{group}', origin_list=origin)
1897 # collar mid
1898 self.rc.neighbouring_nodes.groups['mid2col'] = []
1899 ref = np.array(self.mesh.getNodesForPhysicalGroup(1, self.md.domains.physical_groups.inner_col)[1]).tolist()
1900 _get_closest_nodes(reference_list=ref, origin_list=origin_all, name='mid2col')
1901 # collar wedges
1902 for wdg_nr, el in self.md.domains.physical_groups.wedges.items(): ## one can conveniently add the wedges to the mid2ht groups
1903 if str(el.group[-1]) == str(max_layer):
1904 group = el.group[1] + '_' + str((1 + int(el.group[-1])) % 2)
1905 origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['collar']['w' + str(wdg_nr) + '_x'])[
1906 1].tolist()
1907 ref_list = np.array(self.mesh.getNodesForPhysicalGroup(1, el.lines['o'])[1]).tolist()
1908 _get_closest_nodes(reference_list=ref_list, name=f'mid2ht_{group}', origin_list=origin)
1910 # POLES
1911 self.rc.neighbouring_nodes.groups['pole_mid2ht_1_2'] = []
1912 self.rc.neighbouring_nodes.groups['pole_mid2ht_1_1'] = []
1913 self.rc.neighbouring_nodes.groups['pole_mid2ht_2_1'] = []
1914 self.rc.neighbouring_nodes.groups['pole_mid2ht_2_2'] = []
1915 self.rc.neighbouring_nodes.groups['mid2pol'] = []
1916 # TSA lines to poles
1917 for tsl_name in self.ins_type['poles'].keys():
1918 # first half: mid2ht (also mid2wedge)
1919 nr = tsl_name[1:tsl_name.index('_')]
1920 origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['poles'][tsl_name])[1].tolist()
1921 if nr.startswith('w'): # this is a mid2wedge
1922 ref_list = None
1923 for i, gr in self.md.domains.physical_groups.wedges.items():
1924 # for name, tag in gr.aux_lines.items():
1925 tag = gr.aux_lines.get(nr)
1926 if tag is not None: # should only be one
1927 ref_list = self.mesh.getNodesForPhysicalGroup(1, tag)[1]
1928 ref_list = [float(x) for x in ref_list]
1929 break
1930 # alignment is always the same here, for naming later
1931 group = gr.group
1932 group = group[1] + '_' + str(1 + int(group[-1]) % 2)
1933 else:
1934 ht_nr = int(nr) # e.g. p1_a and p12_a -> 1 and 12
1935 alignment = tsl_name[-1]
1936 # reference can be the nodes from the half turn, we don't have to specify the line
1937 for blk in self.md.domains.physical_groups.blocks.values():
1938 el = blk.half_turns.get(ht_nr, None)
1939 if el is not None:
1940 ref_list = []
1941 [ref_list.extend(self.mesh.getNodesForPhysicalGroup(1, line)[1]) for line in el.lines.values()]
1942 ref_list = [float(x) for x in ref_list]
1943 break
1944 # alignment : direction of the normal vector, for naming
1945 if alignment == 'r':
1946 # group = el.group[1] + '_' + el.group[-1]
1947 group = el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)
1948 elif alignment == 'a':
1949 # group = el.group[1] + '_' + el.group[-1]
1950 group = str(1 + int(el.group[1]) % 2) + '_' + el.group[-1]
1951 _get_closest_nodes(reference_list=ref_list, name=f'pole_mid2ht_{group}', origin_list=origin)
1953 # second half: mid2pole
1954 origin = [float(x) for x in
1955 self.mesh.getNodesForPhysicalGroup(1, self.ins_type['poles'][tsl_name])[1].tolist()]
1956 pole_bdry = self.md.domains.physical_groups.poles.curves.get("bdry", None)
1957 ###ref_list = [float(x) for x in self.mesh.getNodesForPhysicalGroup(2, self.md.domains.physical_groups.poles.surfaces['SS'])[1].tolist()]
1958 ref_list = [float(x) for x in self.mesh.getNodesForPhysicalGroup(1, pole_bdry)[1].tolist()]
1959 # just use all the nodes
1960 # todo: this can be optimized by selecting only the boundary of the pole
1961 _get_closest_nodes(reference_list=ref_list, name=f'mid2pol', origin_list=origin)
1963 logger.info(
1964 f"Info : {self.data.general.magnet_name} - E n d F i n d i n g C l o s e s t N e i g h b o u r s (new TSA)")
1966 def saveHalfTurnCornerPositions(self):
1967 with open(f"{self.geom_files}.crns", 'r') as f: self.rc.coordinates_per_half_turn = json.load(f)
1969 def selectMeshNodes(self, elements: str):
1971 logger.info(f"Info : {self.data.general.magnet_name} - S e l e c t i n g M e s h N o d e s . . .")
1972 logger.info(f"Info : Selecting a mesh node per isothermal {elements[:-1]} ...")
1974 if elements == 'conductors':
1975 bare_mesh = {'1_1': self.rm.powered['r1_a1'].vol.numbers, '2_1': self.rm.powered['r2_a1'].vol.numbers,
1976 '1_2': self.rm.powered['r1_a2'].vol.numbers, '2_2': self.rm.powered['r2_a2'].vol.numbers}
1977 groups = self.rc.isothermal_nodes.conductors
1979 # dir + robin + mid_layers
1980 # potentially all thin shell lines if easier
1981 line_tags = self.rm.boundaries.thermal.temperature.groups['r1_a1'] + \
1982 self.rm.boundaries.thermal.temperature.groups['r1_a2'] + \
1983 self.rm.boundaries.thermal.temperature.groups['r2_a1'] + \
1984 self.rm.boundaries.thermal.temperature.groups['r2_a2'] + \
1985 self.rm.boundaries.thermal.cooling.groups['r1_a1'] + \
1986 self.rm.boundaries.thermal.cooling.groups['r1_a2'] + \
1987 self.rm.boundaries.thermal.cooling.groups['r2_a1'] + \
1988 self.rm.boundaries.thermal.cooling.groups['r2_a2'] + \
1989 self.rm.thin_shells.mid_turns_layers_poles
1990 for tag in line_tags:
1991 coords = list(self.mesh.getNodesForPhysicalGroup(dim=1, tag=tag)[1])[:3]
1992 self.rc.isothermal_nodes.thin_shells[tag] = [float(coords[0]), float(coords[1]), float(coords[2])]
1994 elif elements == 'wedges':
1995 bare_mesh = {'1_1': self.rm.induced['r1_a1'].vol.numbers, '2_1': self.rm.induced['r2_a1'].vol.numbers,
1996 '1_2': self.rm.induced['r1_a2'].vol.numbers, '2_2': self.rm.induced['r2_a2'].vol.numbers}
1997 groups = self.rc.isothermal_nodes.wedges
1998 else:
1999 bare_mesh = {}
2000 groups = {}
2002 for group, tags_list in bare_mesh.items():
2003 groups[group] = {}
2004 for tag in tags_list:
2005 coords = list(self.mesh.getNodesForPhysicalGroup(dim=2, tag=tag)[1])[:3]
2006 groups[group][tag] = [float(coords[0]), float(coords[1]), float(coords[2])]
2008 for tag in self.rm.boundaries.thermal.cooling.groups['r' + group[0] + '_' + 'a' + group[-1]]:
2009 coords = list(self.mesh.getNodesForPhysicalGroup(dim=1, tag=tag)[1])[:3]
2010 groups[group][tag] = [float(coords[0]), float(coords[1]), float(coords[2])]
2012 logger.info(f"Info : {self.data.general.magnet_name} - E n d S e l e c t i n g M e s h N o d e s")
2013 # import time
2014 # time.sleep(100)
2015 # TODO: add to pro file automatically
2017 # self.occ.synchronize()
2018 # self.gu.launch_interactive_GUI()