Coverage for fiqus/mesh_generators/MeshMultipole.py: 86%

978 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-12-27 02:39 +0100

1import os 

2import gmsh 

3import copy 

4import numpy as np 

5import json 

6import logging 

7 

8from fiqus.utils.Utils import GmshUtils 

9from fiqus.utils.Utils import FilesAndFolders as Util 

10from fiqus.utils.Utils import GeometricFunctions as Func 

11from fiqus.data import DataFiQuS as dF 

12from fiqus.data import DataMultipole as dM 

13from fiqus.data import RegionsModelFiQuS as rM 

14 

15logger = logging.getLogger(__name__) 

16 

17class Mesh: 

18 def __init__(self, data: dF.FDM() = None, mesh_folder: str = None, 

19 verbose: bool = False): 

20 """ 

21 Class to generate mesh 

22 :param data: FiQuS data model 

23 :param verbose: If True more information is printed in python console. 

24 """ 

25 self.data: dF.FDM() = data 

26 self.mesh_folder = mesh_folder 

27 self.verbose: bool = verbose 

28 

29 self.md = dM.MultipoleData() 

30 self.rc = dM.MultipoleRegionCoordinate() 

31 self.rm = rM.RegionsModel() 

32 self.strands = None 

33 

34 self.gu = GmshUtils(self.mesh_folder, self.verbose) 

35 self.gu.initialize(verbosity_Gmsh=self.data.run.verbosity_Gmsh) 

36 self.occ = gmsh.model.occ 

37 self.mesh = gmsh.model.mesh 

38 

39 self.brep_iron_curves = {1: set(), 2: set(), 3: set(), 4: set()} 

40 self.mesh_parameters = dict.fromkeys(['SJ', 'SICN', 'SIGE', 'Gamma', 'nodes']) 

41 self.geom_files = os.path.join(os.path.dirname(self.mesh_folder), self.data.general.magnet_name) 

42 self.model_file = os.path.join(self.mesh_folder, self.data.general.magnet_name) 

43 

44 # Insulation sequence involving cable insulation only (turn-to-turn, outlying conductor edge) 

45 self.ins_type_cond = {} 

46 # Insulation sequence involving quench heaters (outlying or mid-layer/pole) 

47 qh_keys = {key: {} for key in range(1, self.data.quench_protection.quench_heaters.N_strips + 1)} 

48 self.ins_type_qh = {'internal_double': {}, 'internal': copy.deepcopy(qh_keys), 'external': copy.deepcopy(qh_keys)} 

49 # Insulation sequence between blocks (layer-to-layer, pole-to-pole) 

50 self.ins_type = {'mid_pole': {}, 'mid_winding': {}, 'mid_layer': {}, 'aux': {}} 

51 self.qh_data, self.wedge_cond = {}, {} 

52 

53 self.colors = {'wedges': [86, 180, 233], # sky blue 

54 'insul': [119, 136, 153], # light slate grey 

55 'half_turns_pos': [213, 94, 0], # vermilion 

56 'half_turns_neg': [255, 136, 42], # light vermilion 

57 'air': [240, 228, 66], # yellow 

58 'air_inf': [220, 208, 46], # dark yellow 

59 # yoke 

60 'BHiron1': [0, 114, 178], # blue 

61 'BHiron2': [0, 158, 115], # bluish green 

62 'BHiron4': [86, 180, 233], # sky blue 

63 # key 

64 'BHiron3': [220, 208, 46], # dark yellow 

65 # [230, 159, 0], # orange 

66 # collar 

67 'BHiron5': [204, 121, 167], # hopbush 

68 'BHiron6': [0, 114, 178], # blue 

69 'BHiron7': [204, 121, 167]} # reddish purple 

70 

71 def clear(self): 

72 self.md = dM.MultipoleData() 

73 self.rc = dM.MultipoleRegionCoordinate() 

74 self.rm = rM.RegionsModel() 

75 gmsh.clear() 

76 

77 def ending_step(self, gui: bool = False): 

78 if gui: 

79 self.gu.launch_interactive_GUI() 

80 else: 

81 gmsh.clear() 

82 gmsh.finalize() 

83 

84 def loadStrandPositions(self, run_type): 

85 self.strands = json.load(open(f"{self.geom_files}_{run_type}.strs")) 

86 

87 def loadAuxiliaryFile(self, run_type): 

88 self.md = Util.read_data_from_yaml(f"{self.geom_files}_{run_type}.aux", dM.MultipoleData) 

89 

90 def updateAuxiliaryFile(self, run_type): 

91 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.aux', self.md.dict()) 

92 # md2 = Util.read_data_from_yaml(f"{self.geom_files}.aux", dM.MultipoleData) 

93 # md2.domains.physical_groups = self.md.domains.physical_groups 

94 # Util.write_data_to_yaml(f"{self.geom_files}.aux", md2.dict()) 

95 

96 def saveMeshFile(self, run_type): 

97 gmsh.write(f'{self.model_file}_{run_type}.msh') 

98 

99 def saveRegionFile(self, run_type): 

100 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.reg', self.rm.dict()) 

101 

102 def saveRegionCoordinateFile(self, run_type): 

103 Util.write_data_to_yaml(f'{self.model_file}_{run_type}.reco', self.rc.dict()) 

104 

105 def getIronCurvesTags(self): 

106 for quadrant, qq in self.md.geometries.iron.quadrants.items(): 

107 for _, area in qq.areas.items(): 

108 if area.surface: 

109 self.brep_iron_curves[quadrant] |= set(gmsh.model.getAdjacencies(2, area.surface)[1]) 

110 

111 def defineMesh(self, geometry, mesh, run_type): 

112 thresholds = [] 

113 self.occ.synchronize() 

114 

115 if mesh.conductors.field.enabled: 

116 distance_conductors = self.mesh.field.add("Distance") 

117 self.mesh.field.setNumbers(distance_conductors, "CurvesList", 

118 [line for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items() for layer_nr, layer in coil.layers.items() 

119 for _, block_order in enumerate(layer) for _, line in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[ 

120 layer_nr].windings[block_order.winding].blocks[block_order.block].half_turns.lines.items()] 

121 ) 

122 self.mesh.field.setNumber(distance_conductors, "Sampling", 100) 

123 

124 threshold_conductors = self.mesh.field.add("Threshold") 

125 self.mesh.field.setNumber(threshold_conductors, "InField", distance_conductors) 

126 self.mesh.field.setNumber(threshold_conductors, "SizeMin", mesh.conductors.field.SizeMin) 

127 self.mesh.field.setNumber(threshold_conductors, "SizeMax", mesh.conductors.field.SizeMax) 

128 self.mesh.field.setNumber(threshold_conductors, "DistMin", mesh.conductors.field.DistMin) 

129 self.mesh.field.setNumber(threshold_conductors, "DistMax", mesh.conductors.field.DistMax) 

130 self.mesh.field.setNumber(threshold_conductors, "StopAtDistMax", 1) 

131 thresholds.append(threshold_conductors) 

132 

133 if mesh.wedges.field.enabled: 

134 distance_wedges = self.mesh.field.add("Distance") 

135 self.mesh.field.setNumbers(distance_wedges, "CurvesList", 

136 [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()] 

137 ) 

138 self.mesh.field.setNumber(distance_wedges, "Sampling", 100) 

139 

140 # 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()]}") 

141 

142 threshold_wedges = self.mesh.field.add("Threshold") 

143 self.mesh.field.setNumber(threshold_wedges, "InField", distance_wedges) 

144 self.mesh.field.setNumber(threshold_wedges, "SizeMin", mesh.wedges.field.SizeMin) 

145 self.mesh.field.setNumber(threshold_wedges, "SizeMax", mesh.wedges.field.SizeMax) 

146 self.mesh.field.setNumber(threshold_wedges, "DistMin", mesh.wedges.field.DistMin) 

147 self.mesh.field.setNumber(threshold_wedges, "DistMax", mesh.wedges.field.DistMax) 

148 self.mesh.field.setNumber(threshold_wedges, "StopAtDistMax", 1) 

149 thresholds.append(threshold_wedges) 

150 

151 if geometry.with_iron_yoke: 

152 distance_iron = self.mesh.field.add("Distance") 

153 self.mesh.field.setNumbers(distance_iron, "CurvesList", [line for _, qq in self.brep_iron_curves.items() for line in qq]) 

154 self.mesh.field.setNumber(distance_iron, "Sampling", 100) 

155 

156 if mesh.iron_field.enabled: 

157 

158 threshold_iron = self.mesh.field.add("Threshold") 

159 self.mesh.field.setNumber(threshold_iron, "InField", distance_iron) 

160 self.mesh.field.setNumber(threshold_iron, "SizeMin", mesh.iron_field.SizeMin) 

161 self.mesh.field.setNumber(threshold_iron, "SizeMax", mesh.iron_field.SizeMax) 

162 self.mesh.field.setNumber(threshold_iron, "DistMin", mesh.iron_field.DistMin) 

163 self.mesh.field.setNumber(threshold_iron, "DistMax", mesh.iron_field.DistMax) 

164 # the iron field is typically rather coarse so that 'overwriting' other fields is not a problem 

165 # its distMax however would need to be large for StopAtDistMax to be effective 

166 #self.mesh.field.setNumber(threshold_iron, "StopAtDistMax", 1) 

167 thresholds.append(threshold_iron) 

168 

169 if run_type == 'EM' and mesh.bore_field.enabled: 

170 

171 distance_bore = self.mesh.field.add("Distance") 

172 self.mesh.field.setNumbers(distance_bore, "PointsList", [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore' in pnt_name]) 

173 self.mesh.field.setNumber(distance_bore, "Sampling", 100) 

174 

175 threshold_bore = self.mesh.field.add("Threshold") 

176 self.mesh.field.setNumber(threshold_bore, "InField", distance_bore) 

177 self.mesh.field.setNumber(threshold_bore, "SizeMin", mesh.bore_field.SizeMin) 

178 self.mesh.field.setNumber(threshold_bore, "SizeMax", mesh.bore_field.SizeMax) 

179 self.mesh.field.setNumber(threshold_bore, "DistMin", mesh.bore_field.DistMin) 

180 self.mesh.field.setNumber(threshold_bore, "DistMax", mesh.bore_field.DistMax) 

181 self.mesh.field.setNumber(threshold_bore, "StopAtDistMax", 1) 

182 thresholds.append(threshold_bore) 

183 

184 insulation_mesh_fields = [] 

185 if run_type == 'TH': 

186 for coil_nr, coil in self.md.geometries.insulation.coils.items(): 

187 for _, group in coil.group.items(): 

188 # Areas 

189 for area_name, area in group.ins.areas.items(): 

190 if area_name.isdigit(): 

191 insulation_mesh_fields.append(self.mesh.field.add("Constant")) 

192 insulation_mesh_field = insulation_mesh_fields[-1] 

193 self.mesh.field.setNumbers(insulation_mesh_field, "SurfacesList", [area.surface]) 

194 self.mesh.field.setNumber(insulation_mesh_field, "VIn", mesh.insulation.global_size) 

195 

196 background = self.mesh.field.add("Min") 

197 self.mesh.field.setNumbers(background, "FieldsList", thresholds + insulation_mesh_fields) 

198 self.mesh.field.setAsBackgroundMesh(background) 

199 

200 # Apply transfinite curves and potentially surfaces to conductors and wedges 

201 if mesh.conductors.transfinite.enabled_for in ['curves', 'curves_and_surfaces']: 

202 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items(): 

203 for layer_nr, layer in coil.layers.items(): 

204 for _, block_order in enumerate(layer): 

205 winding = self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[ 

206 layer_nr].windings[block_order.winding] 

207 cable = self.data.conductors[winding.conductor_name].cable 

208 for line_key, line in winding.blocks[block_order.block].half_turns.lines.items(): 

209 if mesh.dict().get('isothermal_conductors', False): 

210 elements = 1 

211 elif any([i in line_key for i in ['i', 'o']]): 

212 elements = max(1, round(cable.bare_cable_height_mean / mesh.conductors.transfinite.curve_target_size_height)) 

213 elif any([i in line_key for i in ['l', 'h']]): 

214 elements = max(1, round(cable.bare_cable_width / mesh.conductors.transfinite.curve_target_size_width)) 

215 

216 self.mesh.setTransfiniteCurve(line, elements) 

217 if mesh.conductors.transfinite.enabled_for=='curves_and_surfaces' or mesh.dict().get('isothermal_conductors', False): 

218 for _, area in winding.blocks[block_order.block].half_turns.areas.items(): 

219 self.mesh.setTransfiniteSurface(area.surface) 

220 self.mesh.setRecombine(2, area.surface) 

221 

222 if 'insulation' in mesh.dict() and 'TSA' in mesh.dict()["insulation"]: 

223 # Apply transfinite curves to thin shell lines 

224 if geometry.use_TSA: 

225 gts = self.md.geometries.thin_shells 

226 conductor_target_sizes = {'width': mesh.conductors.transfinite.curve_target_size_width, 'height': mesh.conductors.transfinite.curve_target_size_height} 

227 wedge_target_sizes = {'width': mesh.wedges.transfinite.curve_target_size_width, 'height': mesh.wedges.transfinite.curve_target_size_height} 

228 for ts_group, side in zip([gts.mid_layers_ht_to_ht, gts.mid_layers_ht_to_wdg, gts.mid_layers_wdg_to_ht, gts.mid_layers_wdg_to_wdg, 

229 gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn, gts.mid_layers_aux], 

230 ['height', 'height', 'height', 'height', 'width', 'width', 'width', 'width', 'height']): 

231 for ts_name, ts in ts_group.items(): 

232 for _, line in ts.lines.items() if isinstance(ts, dM.Region) else ts.mid_layers.lines.items(): 

233 if mesh.isothermal_conductors or mesh.isothermal_wedges: 

234 elements = 1 

235 else: 

236 coords = gmsh.model.getValue(1, line, [i[0] for i in gmsh.model.getParametrizationBounds(1, line)]) 

237 target_size = wedge_target_sizes[side] if ts_name.count('w') == 2 else conductor_target_sizes[side] 

238 elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size)) 

239 

240 # it's a wedge 

241 if ts_name.count('w') == 2 and mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']: 

242 self.mesh.setTransfiniteCurve(line, elements) 

243 elif ts_name.count('w') != 2 and mesh.conductors.transfinite.enabled_for in ['curves', 'curves_and_surfaces']: 

244 self.mesh.setTransfiniteCurve(line, elements) 

245 # COMMENTED since this overwrites also the cable transfinite meshes and in general,  

246 # restricting the insulation boundaries to be transfinite seems very restrictive due to their complex geometry 

247 # Apply transfinite curves to insulation boundaries 

248 # else: 

249 # for coil_nr, coil in self.md.geometries.insulation.coils.items(): 

250 # for group_nr, group in coil.group.items(): 

251 # cable_height = self.data.conductors[self.md.geometries.coil.coils[coil_nr].poles[ 

252 # group.blocks[0][0]].layers[group.blocks[0][1]].windings[group.blocks[0][2]].conductor_name].cable.bare_cable_height_mean 

253 # for line in [bnd[1] for bnd in gmsh.model.getBoundary( 

254 # [(2, list(group.ins.areas.values())[0].surface)], # + 

255 # # [(2, ht.surface) for blk_order in group.blocks for ht in 

256 # # 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()] + 

257 # # [(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], 

258 # combined=True, oriented=False)]: 

259 # pnts = gmsh.model.getAdjacencies(1, line)[1] 

260 # length = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], [])) 

261 # self.mesh.setTransfiniteCurve(line,max(1, round(length / (mesh.conductor_target_sizes.width if length > 2 * cable_height else mesh.conductor_target_sizes.height)))) 

262 

263 # Apply transfinite curves to wedges 

264 if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']: 

265 if geometry.with_wedges: 

266 for coil_nr, coil in self.md.geometries.wedges.coils.items(): 

267 for layer_nr, layer in coil.layers.items(): 

268 for _, wedge in layer.wedges.items(): 

269 pnts = gmsh.model.getAdjacencies(1, wedge.lines['i'])[1] 

270 inner_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], [])) 

271 pnts = gmsh.model.getAdjacencies(1, wedge.lines['l'])[1] 

272 width = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], [])) 

273 pnts = gmsh.model.getAdjacencies(1, wedge.lines['o'])[1] 

274 outer_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], [])) 

275 for line_key, line in wedge.lines.items(): 

276 if mesh.dict().get('isothermal_wedges', False): 

277 elements = 1 

278 elif 'i' in line_key: 

279 elements = max(1, round(inner_height / mesh.wedges.transfinite.curve_target_size_height)) 

280 elif 'o' in line_key: 

281 elements = max(1, round((inner_height if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces'] 

282 else outer_height) / mesh.wedges.transfinite.curve_target_size_height)) 

283 elif any([i in line_key for i in ['l', 'h']]): 

284 elements = max(1, round(width / mesh.wedges.transfinite.curve_target_size_width)) 

285 if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']: 

286 self.mesh.setTransfiniteCurve(line, elements) 

287 if mesh.wedges.transfinite.enabled_for=='curves_and_surfaces' or mesh.dict().get('isothermal_wedges', False): 

288 self.mesh.setTransfiniteSurface(list(wedge.areas.values())[0].surface) 

289 self.mesh.setRecombine(2, list(wedge.areas.values())[0].surface) 

290 

291 def createPhysicalGroups(self, geometry): 

292 """ 

293 Creates physical groups by grouping the mirrored entities according to the Roxie domains 

294 """ 

295 offset: int = 1 if 'symmetry' in geometry.dict() else int(1e6) 

296 pg_tag = offset 

297 

298 # Create physical groups of iron yoke regions and block insulation 

299 pg = self.md.domains.physical_groups 

300 for group_name, surfaces in self.md.domains.groups_entities.iron.items(): 

301 pg.iron.surfaces[group_name] = gmsh.model.addPhysicalGroup(2, surfaces, pg_tag) 

302 gmsh.model.setPhysicalName(2, pg.iron.surfaces[group_name], group_name) 

303 color = self.colors[group_name] 

304 gmsh.model.setColor([(2, i) for i in surfaces], color[0], color[1], color[2]) 

305 pg_tag += 1 

306 

307 # Create the physical group of air infinite 

308 if 'symmetry' in geometry.dict(): 

309 gmsh.model.setPhysicalName(0, gmsh.model.addPhysicalGroup( 

310 0, [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore_field' in pnt_name], pg_tag), 'bore_centers') 

311 pg_tag += 1 

312 pg.air_inf_bnd = gmsh.model.addPhysicalGroup(1, [self.md.geometries.air_inf.lines['outer']], pg_tag) 

313 gmsh.model.setPhysicalName(1, pg.air_inf_bnd, 'air_inf_bnd') 

314 pg_tag += 1 

315 pg.air_inf = gmsh.model.addPhysicalGroup(2, [self.md.geometries.air_inf.areas['outer'].surface], pg_tag) 

316 gmsh.model.setPhysicalName(2, pg.air_inf, 'air_inf') 

317 gmsh.model.setColor([(2, self.md.geometries.air_inf.areas['outer'].surface)], self.colors['air_inf'][0], self.colors['air_inf'][1], self.colors['air_inf'][2]) 

318 pg_tag += 1 

319 pg.air = gmsh.model.addPhysicalGroup(2, self.md.domains.groups_entities.air, pg_tag) 

320 gmsh.model.setPhysicalName(2, pg.air, 'air') 

321 gmsh.model.setColor([(2, i) for i in self.md.domains.groups_entities.air], self.colors['air'][0], self.colors['air'][1], self.colors['air'][2]) 

322 pg_tag += 1 

323 

324 # Create physical groups of half turns 

325 lyr_list_group = [] 

326 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items(): 

327 lyr_list_group.extend(['cl' + str(coil_nr) + 'ly' + str(lyr) for lyr in list(coil.layers.keys())]) 

328 for layer_nr, layer in coil.layers.items(): 

329 ht_list_group = [] 

330 for nr, block_order in enumerate(layer): 

331 wnd = self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[ 

332 layer_nr].windings[block_order.winding] 

333 block = wnd.blocks[block_order.block] 

334 block_nr = block_order.block 

335 pg.blocks[block_nr] = dM.PoweredBlock() 

336 blk_pg = pg.blocks[block_nr] 

337 blk_pg.current_sign = block.current_sign 

338 blk_pg.conductor = wnd.conductor_name 

339 color = self.colors['half_turns_pos'] if block.current_sign > 0 else self.colors['half_turns_neg'] 

340 ht_list = list(block.half_turns.areas.keys()) 

341 ht_list_group.extend(ht_list) 

342 if nr + 1 < len(layer): 

343 if layer[nr + 1].pole == block_order.pole and layer[nr + 1].winding != block_order.winding: 

344 ht_list_group.append('w' + str(nr)) 

345 # Create 2D physical groups of half turns 

346 for ht_key, ht in block.half_turns.areas.items(): 

347 blk_pg.half_turns[int(ht_key)] = dM.PoweredGroup() 

348 ht_pg = blk_pg.half_turns[int(ht_key)] 

349 # Create physical group and assign name and color 

350 ht_pg.tag = gmsh.model.addPhysicalGroup(2, [ht.surface], pg_tag) 

351 gmsh.model.setPhysicalName(2, ht_pg.tag, ht_key) 

352 gmsh.model.setColor([(2, ht.surface)], color[0], color[1], color[2]) 

353 pg_tag += 1 

354 # Assign thin-shell group 

355 # the check for reversed block coil is not tested well 

356 if geometry.dict().get('correct_block_coil_tsa_checkered_scheme', False) and self.md.geometries.coil.coils[coil_nr].type == 'reversed-block-coil': 

357 azimuthal = 'a1' if list(wnd.blocks.keys()).index(block_nr) % 2 == 0 else 'a2' 

358 else: 

359 azimuthal = 'a1' if lyr_list_group.index('cl' + str(coil_nr) + 'ly' + str(layer_nr)) % 2 == 0 else 'a2' 

360 radial = 'r1' if ht_list_group.index(ht_key) % 2 == 0 else 'r2' 

361 ht_pg.group = radial + '_' + azimuthal 

362 if geometry.dict().get('use_TSA', False): 

363 # Create 1D physical groups of thin shells 

364 for ht_line_key, ht_line in block.half_turns.lines.items(): 

365 ht_nr = ht_line_key[:-1] 

366 # Create half turn line groups 

367 line_pg = gmsh.model.addPhysicalGroup(1, [ht_line], pg_tag) 

368 gmsh.model.setPhysicalName(1, line_pg, ht_line_key) 

369 color = [0, 0, 0] if blk_pg.half_turns[int(ht_nr)].group[:2] == 'r1' else [150, 150, 150] 

370 gmsh.model.setColor([(1, ht_line)], color[0], color[1], color[2]) 

371 pg_tag += 1 

372 # Store thin shell tags 

373 blk_pg.half_turns[int(ht_nr)].lines[ht_line_key[-1]] = line_pg 

374 

375 # Create points region for projection 

376 if 'use_TSA' in geometry.dict(): 

377 self.md.domains.physical_groups.half_turns_points = gmsh.model.addPhysicalGroup( 

378 0, [gmsh.model.getAdjacencies(1, gmsh.model.getAdjacencies(2, ht.surface)[1][0])[1][0] 

379 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items() 

380 for layer_nr, layer in coil.layers.items() for block_order in layer 

381 for ht_key, ht in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[ 

382 layer_nr].windings[block_order.winding].blocks[block_order.block].half_turns.areas.items()], int(4e6)) 

383 gmsh.model.setPhysicalName(0, self.md.domains.physical_groups.half_turns_points, 'points') 

384 

385 # Create physical groups of insulations 

386 if not geometry.dict().get('use_TSA', True): 

387 for coil_nr, coil in self.md.geometries.insulation.coils.items(): 

388 for group_nr, group in coil.group.items(): 

389 # Areas 

390 for area_name, area in group.ins.areas.items(): 

391 if area_name.isdigit(): 

392 pg.insulations.surfaces[area_name] = gmsh.model.addPhysicalGroup(2, [area.surface], pg_tag) 

393 gmsh.model.setPhysicalName(2, pg.insulations.surfaces[area_name], 'insul_' + area_name) 

394 gmsh.model.setColor([(2, area.surface)], self.colors['insul'][0], self.colors['insul'][1], self.colors['insul'][2]) 

395 pg_tag += 1 

396 

397 # Boundaries 

398 area_name = list(group.ins.areas.keys())[0] # todo: test for Mono 

399 pg.insulations.curves['ext' + area_name] = gmsh.model.addPhysicalGroup( 

400 1, [bnd[1] for bnd in gmsh.model.getBoundary([(2, list(group.ins.areas.values())[0].surface)], combined=False, oriented=False)[:len(group.ins.lines)]], pg_tag) 

401 gmsh.model.setPhysicalName(1, pg.insulations.curves['ext' + area_name], 'insul_ext' + area_name) 

402 pg_tag += 1 

403 # todo: NOT COMPLETED: would work if the tags were updated in the Geometry script after saving and loading brep 

404 # side_lines = {'i': [], 'o': [], 'l': [], 'h': []} 

405 # for line_name, line in group.ins.lines.items(): 

406 # side_lines[line_name[-1] if line_name[-1].isalpha() else sorted(line_name)[-1]].append(line) 

407 # for side, side_line in side_lines.items(): 

408 # pg.insulations.curves[str(group_nr) + side] = gmsh.model.addPhysicalGroup(1, side_line, pg_tag) 

409 # gmsh.model.setPhysicalName(1, pg.insulations.curves[str(group_nr) + side], str(group_nr) + side) 

410 # pg_tag += 1 

411 

412 # Create physical groups of wedges 

413 for coil_nr, coil in self.md.geometries.wedges.coils.items(): 

414 for layer_nr, layer in coil.layers.items(): 

415 for wedge_nr, wedge in layer.wedges.items(): 

416 pg.wedges[wedge_nr] = dM.WedgeGroup() 

417 wedge_pg = pg.wedges[wedge_nr] 

418 wedge_pg.tag = gmsh.model.addPhysicalGroup(2, [wedge.areas[str(wedge_nr)].surface], pg_tag) 

419 gmsh.model.setPhysicalName(2, wedge_pg.tag, 'w' + str(wedge_nr)) 

420 gmsh.model.setColor([(2, wedge.areas[str(wedge_nr)].surface)], 

421 self.colors['wedges'][0], self.colors['wedges'][1], self.colors['wedges'][2]) 

422 pg_tag += 1 

423 # Assign thin-shell group 

424 prev_block_hts = pg.blocks[layer.block_prev[wedge_nr]].half_turns 

425 if len(list(prev_block_hts.keys())) > 1: 

426 wedge_pg.group = prev_block_hts[list(prev_block_hts.keys())[-2]].group 

427 else: 

428 prev_group = prev_block_hts[list(prev_block_hts.keys())[0]].group 

429 wedge_pg.group = ('r1' if prev_group[1] == '2' else 'r2') + prev_group[prev_group.index('_'):] 

430 if geometry.dict().get('use_TSA', False): 

431 # Create 1D physical groups of thin shells 

432 for line_key, line in wedge.lines.items(): 

433 wedge_pg.lines[line_key] = gmsh.model.addPhysicalGroup(1, [line], pg_tag) 

434 gmsh.model.setPhysicalName(1, wedge_pg.lines[line_key], 'w' + str(wedge_nr) + line_key) 

435 color = [0, 0, 0] if wedge_pg.group[:2] == 'r1' else [150, 150, 150] 

436 gmsh.model.setColor([(1, line)], color[0], color[1], color[2]) 

437 pg_tag += 1 

438 

439 # Create physical groups of thin shells 

440 if geometry.dict().get('use_TSA', False): 

441 gts = self.md.geometries.thin_shells 

442 # Create physical groups of block mid-layer lines 

443 block_coil_flag = False 

444 for ts_name, ts in gts.mid_layers_ht_to_ht.items(): 

445 blk_i, blk_o = ts_name[:ts_name.index('_')], ts_name[ts_name.index('_') + 1:] 

446 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items(): 

447 if (any(int(blk_i) == blk_order.block for blk_order in self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1]) and 

448 any(int(blk_o) == blk_order.block for blk_order in self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1])): # block-coil mid-pole case 

449 block_coil_flag = True 

450 for line_name, line in ts.mid_layers.lines.items(): 

451 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag) 

452 gmsh.model.setPhysicalName(1, line_pg, line_name) 

453 pg_tag += 1 

454 ht1, ht2 = int(line_name[:line_name.index('_')]), int(line_name[line_name.index('_') + 1:]) 

455 if ht1 in ts.half_turn_lists[blk_i]: 

456 if block_coil_flag: 

457 pg.blocks[int(blk_i)].half_turns[ht1].mid_layer_lines.inner[line_name] = line_pg 

458 else: 

459 pg.blocks[int(blk_i)].half_turns[ht1].mid_layer_lines.outer[line_name] = line_pg 

460 pg.blocks[int(blk_o)].half_turns[ht2].mid_layer_lines.inner[line_name] = line_pg 

461 else: 

462 pg.blocks[int(blk_o)].half_turns[ht1].mid_layer_lines.inner[line_name] = line_pg 

463 if block_coil_flag: 

464 pg.blocks[int(blk_i)].half_turns[ht2].mid_layer_lines.inner[line_name] = line_pg 

465 else: 

466 pg.blocks[int(blk_i)].half_turns[ht2].mid_layer_lines.outer[line_name] = line_pg 

467 

468 # Create physical groups of wedge-to-block mid-layer lines 

469 for ts_name, ts in gts.mid_layers_wdg_to_ht.items(): 

470 wdg, blk = ts_name.split('_') 

471 for line_name, line in ts.lines.items(): 

472 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag) 

473 gmsh.model.setPhysicalName(1, line_pg, line_name) 

474 pg_tag += 1 

475 pg.wedges[int(wdg[1:])].mid_layer_lines.outer[line_name] = line_pg 

476 ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[line_name.index('_') + 1:] 

477 pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.inner[line_name] = line_pg 

478 

479 # Create physical groups of block-to-wedge mid-layer lines 

480 for ts_name, ts in gts.mid_layers_ht_to_wdg.items(): 

481 wdg, blk = ts_name.split('_') 

482 for line_name, line in ts.lines.items(): 

483 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag) 

484 gmsh.model.setPhysicalName(1, line_pg, line_name) 

485 pg_tag += 1 

486 pg.wedges[int(wdg[1:])].mid_layer_lines.inner[line_name] = line_pg 

487 ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[line_name.index('_') + 1:] 

488 pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.outer[line_name] = line_pg 

489 

490 # Create physical groups of wedge-to-wedge mid-layer lines 

491 for ts_name, ts in gts.mid_layers_wdg_to_wdg.items(): 

492 wdg_i, wdg_o = ts_name[1:ts_name.index('_')], ts_name[ts_name.index('_') + 2:] 

493 line_pg = gmsh.model.addPhysicalGroup(1, [ts.lines[list(ts.lines.keys())[0]]], pg_tag) 

494 gmsh.model.setPhysicalName(1, line_pg, ts_name) 

495 pg_tag += 1 

496 pg.wedges[int(wdg_i)].mid_layer_lines.outer[ts_name] = line_pg 

497 pg.wedges[int(wdg_o)].mid_layer_lines.inner[ts_name] = line_pg 

498 

499 # Create non-mid-layer thin shells 

500 for ts_group_name, ts_group in zip(['mid_pole', 'mid_winding', 'mid_turn', 'mid_turn', 'aux'], 

501 [gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn, gts.mid_layers_aux]): 

502 for ts_name, ts in ts_group.items(): 

503 if '_' in ts_name: 

504 el_name1, el_name2 = ts_name.split('_') 

505 el_name1, el_name2 = el_name1.strip('w'), el_name2.strip('w') 

506 else: # mid_turn_blocks 

507 el_name1, el_name2 = ts_name, ts_name 

508 for line_name, line in ts.lines.items(): 

509 line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag) 

510 gmsh.model.setPhysicalName(1, line_pg, line_name) 

511 pg_tag += 1 

512 if ts_group_name == 'aux': 

513 ht1 = int(list(ts.points.keys())[0][:-1]) 

514 else: 

515 ht1, ht2 = line_name.split('_') 

516 pg.blocks[int(el_name2)].half_turns[int(ht2)].__dict__[ts_group_name + '_lines'][line_name] = line_pg 

517 if ts_name[0] == 'w': 

518 pg.wedges[int(el_name1)].__dict__[ts_group_name + '_lines'][line_name] = line_pg 

519 else: 

520 pg.blocks[int(el_name1)].half_turns[int(ht1)].__dict__[ts_group_name + '_lines'][line_name] = line_pg 

521 

522 # Create physical groups of symmetric boundaries 

523 if geometry.dict().get('symmetry', 'none') != 'none': 

524 line_tags_normal_free, line_tags_tangent_free = [], [] 

525 if geometry.symmetry == 'xy': 

526 if len(self.md.geometries.coil.coils[1].poles) == 2: 

527 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.y 

528 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x 

529 elif len(self.md.geometries.coil.coils[1].poles) == 4: # todo: think about other multi-pole types 

530 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x +\ 

531 self.md.domains.groups_entities.symmetric_boundaries.y 

532 elif geometry.symmetry == 'x': 

533 if 'solenoid' in self.md.geometries.coil.coils[1].type: 

534 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.x 

535 else: 

536 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x 

537 elif geometry.symmetry == 'y': 

538 if len(self.md.geometries.coil.coils[1].poles) == 2: 

539 line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.y 

540 elif len(self.md.geometries.coil.coils[1].poles) == 4: 

541 line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.y 

542 if line_tags_normal_free: 

543 pg.symmetric_boundaries.normal_free = gmsh.model.addPhysicalGroup(1, line_tags_normal_free, pg_tag) 

544 gmsh.model.setPhysicalName(1, pg.symmetric_boundaries.normal_free, 'normal_free_bnd') 

545 pg_tag += 1 

546 if line_tags_tangent_free: 

547 pg.symmetric_boundaries.tangential_free = gmsh.model.addPhysicalGroup(1, line_tags_tangent_free, pg_tag) 

548 gmsh.model.setPhysicalName(1, pg.symmetric_boundaries.tangential_free, 'tangent_free_bnd') 

549 pg_tag += 1 

550 

551 def rearrangeThinShellsData(self): 

552 pg = self.md.domains.physical_groups 

553 ins_th = self.md.geometries.thin_shells.ins_thickness 

554 qh = self.data.quench_protection.quench_heaters 

555 

556 def _store_qh_data(position, thin_shell, ts_tag): 

557 qh_ins = self.ins_type_qh[position][qh.ids[ht_index]] 

558 if thin_shell not in qh_ins: qh_ins[thin_shell] = [] 

559 qh_ins[thin_shell].append(ts_tag) 

560 if thin_shell not in self.qh_data[qh.ids[ht_index]]: 

561 self.qh_data[qh.ids[ht_index]][thin_shell] = {'conductor': blk_pg.conductor, 'ht_side': qh_side} 

562 

563 def _store_ts_tags(pg_el, geom_ts_name='', geom_ts_name2=None, ts_grp='', lines='', lines_side=None): 

564 geom_ts = self.md.geometries.thin_shells.dict()[geom_ts_name] 

565 for ln_name, ln_tag in (pg_el.dict()[lines][lines_side] if lines_side else pg_el.dict()[lines]).items(): 

566 for ts_name in ts_groups[ts_grp]: 

567 if ts_name in geom_ts: 

568 if ln_name in (geom_ts[ts_name][geom_ts_name2]['lines'] if geom_ts_name2 else geom_ts[ts_name]['lines']): 

569 if ts_name not in self.ins_type[ts_grp]: self.ins_type[ts_grp][ts_name] = [] 

570 self.ins_type[ts_grp][ts_name].append(ln_tag) 

571 break 

572 

573 # Collect thin shell tags 

574 # Half turns 

575 for blk_pg_nr, blk_pg in pg.blocks.items(): 

576 ts_groups = {'mid_wedge_turn': [ts_name for ts_name in self.md.geometries.thin_shells.mid_wedge_turn if blk_pg_nr == int(ts_name.split('_')[1])], 

577 'aux': [ts_name for ts_name in ins_th.mid_layer if str(blk_pg_nr) in ts_name.split('_')], 

578 'mid_winding': [ts_name for ts_name in ins_th.mid_winding if blk_pg_nr == int(ts_name.split('_')[0])], 

579 'mid_pole': [ts_name for ts_name in ins_th.mid_pole if blk_pg_nr == int(ts_name.split('_')[0])], 

580 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if ts_name[0] != 'w' and blk_pg_nr == int(ts_name.split('_')[0]) 

581 or ts_name[0] == 'w' and ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(ts_name.split('_')[1])], 

582 'mid_layer_qh_i': [ts_name for ts_name in ins_th.mid_layer if ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(ts_name.split('_')[1])]} 

583 ht_list = list(blk_pg.half_turns.keys()) 

584 self.ins_type_cond[str(blk_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [], 'mid_turn': []} 

585 for ht_nr, ht in blk_pg.half_turns.items(): 

586 if ht_nr in qh.turns and qh.N_strips > 0: # check if a quench heater strip touches the current half-turn 

587 ht_index = qh.turns.index(ht_nr) 

588 qh_side = qh.turns_sides[ht_index] 

589 if qh.ids[ht_index] not in self.qh_data: self.qh_data[qh.ids[ht_index]] = {} 

590 else: qh_side = '' 

591 

592 # find conductor type of ht adjacent to wedge 

593 if ht_list.index(ht_nr) == len(ht_list) - 1 and ht.mid_turn_lines: 

594 for line_name, line_tag in ht.mid_turn_lines.items(): 

595 for ts_name in ts_groups['mid_wedge_turn']: 

596 if line_name in self.md.geometries.thin_shells.mid_wedge_turn[ts_name].lines: 

597 self.wedge_cond[int(ts_name[1:ts_name.index('_')])] = blk_pg.conductor 

598 break 

599 

600 self.ins_type_cond[str(blk_pg_nr)]['mid_turn'].extend(list(ht.mid_turn_lines.values())) # mid-turn insulation 

601 

602 if ht.aux_lines: # outer mid-layer insulation 

603 _store_ts_tags(ht, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines') 

604 

605 if ht.mid_layer_lines.inner and qh_side == 'i': 

606 for line_name, line_tag in ht.mid_layer_lines.inner.items(): 

607 for ts_name in ts_groups['mid_layer_qh_i']: 

608 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht: 

609 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines 

610 elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_ht: 

611 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines 

612 else: ts_lines = [] 

613 if line_name in ts_lines: 

614 _store_qh_data('internal', ts_name, line_tag) 

615 break 

616 elif ht.mid_layer_lines.inner: # block-coil (!) inner mid-layer insulation 

617 _store_ts_tags(ht, geom_ts_name='mid_layers_ht_to_ht', geom_ts_name2='mid_layers', ts_grp='mid_layer', lines='mid_layer_lines', lines_side='inner') 

618 elif not ht.mid_layer_lines.inner and qh_side == 'i': # quench heater inner insulation 

619 _store_qh_data('external', blk_pg_nr, ht.lines['i']) 

620 elif not ht.mid_layer_lines.inner: # inner insulation 

621 self.ins_type_cond[str(blk_pg_nr)]['inner'].append(ht.lines['i']) 

622 

623 if ht.mid_layer_lines.outer and qh_side == 'o': # mid-layer quench heater insulation 

624 for line_name, line_tag in ht.mid_layer_lines.outer.items(): 

625 for ts_name in ts_groups['mid_layer']: 

626 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht: 

627 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines 

628 elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg: 

629 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines 

630 else: ts_lines = [] 

631 if line_name in ts_lines: 

632 _store_qh_data('internal', ts_name, line_tag) 

633 break 

634 elif ht.mid_layer_lines.outer: # mid-layer insulation 

635 for line_name, line_tag in ht.mid_layer_lines.outer.items(): 

636 for ts_name in ts_groups['mid_layer']: 

637 if ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_ht: 

638 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines 

639 elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg: 

640 ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines 

641 else: ts_lines = [] 

642 if line_name in ts_lines: 

643 if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = [] 

644 self.ins_type['mid_layer'][ts_name].append(line_tag) 

645 break 

646 elif not ht.mid_layer_lines.outer and qh_side == 'o': # quench heater outer insulation 

647 _store_qh_data('external', blk_pg_nr, ht.lines['o']) 

648 else: # outer insulation 

649 self.ins_type_cond[str(blk_pg_nr)]['outer'].append(ht.lines['o']) 

650 

651 # mid-pole insulation 

652 _store_ts_tags(ht, geom_ts_name='mid_poles', ts_grp='mid_pole', lines='mid_pole_lines') 

653 

654 # mid-winding insulation 

655 _store_ts_tags(ht, geom_ts_name='mid_windings', ts_grp='mid_winding', lines='mid_winding_lines') 

656 

657 if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # lower angle external side insulation 

658 if qh_side == 'l': _store_qh_data('external', blk_pg_nr, ht.lines['l']) 

659 else: self.ins_type_cond[str(blk_pg_nr)]['lower'].append(ht.lines['l']) 

660 if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # higher angle external side insulation 

661 if qh_side == 'h': _store_qh_data('external', blk_pg_nr, ht.lines['h']) 

662 else: self.ins_type_cond[str(blk_pg_nr)]['higher'].append(ht.lines['h']) 

663 

664 # Wedges 

665 for wdg_pg_nr, wdg_pg in pg.wedges.items(): 

666 ts_groups = {'aux': [ts_name for ts_name in ins_th.mid_layer if 'w' + str(wdg_pg_nr) in ts_name.split('_')], 

667 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if 'w' + str(wdg_pg_nr) == ts_name.split('_')[0]]} 

668 self.ins_type_cond['w' + str(wdg_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [], 'mid_turn': []} 

669 if wdg_pg.aux_lines: _store_ts_tags(wdg_pg, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines') 

670 if not wdg_pg.mid_layer_lines.inner: self.ins_type_cond['w' + str(wdg_pg_nr)]['inner'].append(wdg_pg.lines['i']) 

671 if wdg_pg.mid_layer_lines.outer: 

672 for line_name, line_tag in wdg_pg.mid_layer_lines.outer.items(): 

673 for ts_name in ts_groups['mid_layer']: 

674 if ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_ht: 

675 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines 

676 elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_wdg: 

677 ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_wdg[ts_name].lines 

678 else: ts_lines = [] 

679 if line_name in ts_lines: 

680 if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = [] 

681 self.ins_type['mid_layer'][ts_name].append(line_tag) 

682 break 

683 else: self.ins_type_cond['w' + str(wdg_pg_nr)]['outer'].append(wdg_pg.lines['o']) 

684 

685 # Collect common thin shells for double qh mid-layers 

686 for qh_nr, ts_groups in self.ins_type_qh['internal'].items(): 

687 for qh_nr2, ts_groups2 in self.ins_type_qh['internal'].items(): 

688 if qh_nr != qh_nr2: 

689 common_ts_groups = list(set(ts_groups) & set(ts_groups2)) 

690 for ts in common_ts_groups: 

691 tags, tags2 = ts_groups[ts], ts_groups2[ts] 

692 common_tags = list(set(tags) & set(tags2)) 

693 for tag in common_tags: 

694 tags.remove(tag), tags2.remove(tag) 

695 if self.qh_data[qh_nr2][ts]['ht_side'] == 'i': qh_name = str(qh_nr) + '_' + str(qh_nr2) 

696 else: qh_name = str(qh_nr2) + '_' + str(qh_nr) 

697 if qh_name not in self.ins_type_qh['internal_double']: self.ins_type_qh['internal_double'][qh_name] = {} 

698 qh_ins_id = self.ins_type_qh['internal_double'][qh_name] 

699 if ts not in qh_ins_id: qh_ins_id[ts] = [] 

700 qh_ins_id[ts].append(tag) 

701 

702 def assignRegionsTags(self, geometry, mesh): 

703 def _get_input_insulation_data(i_name, i_type=None): 

704 ow_idx = next((index for index, couple in enumerate(self.data.magnet.solve.thermal.insulation_TSA.block_to_block.blocks_connection_overwrite) 

705 if all(element in couple for element in i_name.split('_'))), None) 

706 if i_type == 'mid_winding': mid_mat, mid_th = [self.data.magnet.solve.wedges.material], [] 

707 elif ow_idx is not None: 

708 mid_mat = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.materials_overwrite[ow_idx] 

709 mid_th = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.thicknesses_overwrite[ow_idx] 

710 else: mid_mat, mid_th = [self.data.magnet.solve.thermal.insulation_TSA.block_to_block.material], [] 

711 return mid_mat, mid_th 

712 

713 def _compute_insulation_thicknesses(tot_th, known_ins_th): 

714 if not mid_thicknesses: 

715 mid_lyrs = [Func.sig_dig(tot_th - known_ins_th) / len(mid_materials)] * len(mid_materials) 

716 elif None in mid_thicknesses: 

717 input_ths = sum([th for th in mid_thicknesses if th is not None]) 

718 mid_lyrs = [th if th is not None else Func.sig_dig(tot_th - known_ins_th - input_ths) / mid_thicknesses.count(None) for th in mid_thicknesses] 

719 else: mid_lyrs = mid_thicknesses 

720 zeros = [nbr for nbr, th in enumerate(mid_lyrs) if th < 1e-8] 

721 if tot_th - known_ins_th - sum(mid_lyrs) < -1e-8: 

722 raise ValueError("Layer-to-layer insulation exceeds the space between blocks: check 'solve'->'insulation_TSA'->'block_to_block'->'thicknesses_overwrite'") 

723 else: return mid_lyrs, zeros 

724 

725 pg = self.md.domains.physical_groups 

726 qh = self.data.quench_protection.quench_heaters 

727 

728 # Air and air far field 

729 if 'bore_field' in mesh.dict(): 

730 self.rm.air_far_field.vol.radius_out = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies( 

731 1, self.md.geometries.air_inf.lines['outer'])[1][0], []), key=abs))) 

732 self.rm.air_far_field.vol.radius_in = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies( 

733 1, self.md.geometries.air_inf.lines['inner'])[1][0], []), key=abs))) 

734 self.rm.air.vol.name = "Air" 

735 self.rm.air.vol.number = pg.air 

736 self.rm.air_far_field.vol.names = ["AirInf"] 

737 self.rm.air_far_field.vol.numbers = [pg.air_inf] 

738 self.rm.air_far_field.surf.name = "Surface_Inf" 

739 self.rm.air_far_field.surf.number = pg.air_inf_bnd 

740 if geometry.dict().get('symmetry', 'none') != 'none': 

741 self.rm.boundaries.symmetry.normal_free.name = 'normal_free_bnd' 

742 self.rm.boundaries.symmetry.normal_free.number = pg.symmetric_boundaries.normal_free 

743 self.rm.boundaries.symmetry.tangential_free.name = 'tangent_free_bnd' 

744 self.rm.boundaries.symmetry.tangential_free.number = pg.symmetric_boundaries.tangential_free 

745 

746 if 'use_TSA' in geometry.dict(): 

747 self.rm.projection_points.name = 'projection_points' 

748 self.rm.projection_points.number = self.md.domains.physical_groups.half_turns_points 

749 

750 # Initialize lists 

751 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']: 

752 self.rm.powered[group] = rM.Powered() 

753 self.rm.powered[group].vol.names = [] 

754 self.rm.powered[group].vol.numbers = [] 

755 for cond_name in self.data.conductors.keys(): self.rm.powered[group].conductors[cond_name] = [] 

756 if geometry.with_wedges: 

757 self.rm.induced[group] = rM.Induced() 

758 self.rm.induced[group].vol.names = [] 

759 self.rm.induced[group].vol.numbers = [] 

760 if 'bore_field' in mesh.dict(): 

761 initial_current = self.data.power_supply.I_initial 

762 self.rm.powered[group].vol.currents = [] 

763 if geometry.dict().get('use_TSA', False): 

764 self.rm.powered[group].surf_in.names = [] 

765 self.rm.powered[group].surf_in.numbers = [] 

766 self.rm.powered[group].surf_out.names = [] 

767 self.rm.powered[group].surf_out.numbers = [] 

768 if geometry.with_wedges: 

769 self.rm.induced[group].surf_in.names = [] 

770 self.rm.induced[group].surf_in.numbers = [] 

771 self.rm.induced[group].surf_out.names = [] 

772 self.rm.induced[group].surf_out.numbers = [] 

773 if geometry.with_iron_yoke: 

774 self.rm.iron.vol.names = [] 

775 self.rm.iron.vol.numbers = [] 

776 if geometry.dict().get('use_TSA', False): 

777 unique_thin_shells = [] 

778 self.rm.thin_shells.second_group_is_next['azimuthally'] = [] 

779 self.rm.thin_shells.second_group_is_next['radially'] = [] 

780 self.rm.thin_shells.normals_directed['azimuthally'] = [] 

781 self.rm.thin_shells.normals_directed['radially'] = [] 

782 else: 

783 self.rm.insulator.vol.names = [] 

784 self.rm.insulator.vol.numbers = [] 

785 self.rm.insulator.surf.names = [] 

786 self.rm.insulator.surf.numbers = [] 

787 

788 if geometry.dict().get('use_TSA', False): 

789 # Categorize insulation types 

790 min_h = mesh.insulation.global_size 

791 # min_h = 1 

792 # for conductor in self.data.conductors.keys(): 

793 # min_h = min([self.data.conductors[conductor].cable.th_insulation_along_height, 

794 # self.data.conductors[conductor].cable.th_insulation_along_width, min_h]) 

795 min_h_QH = mesh.insulation.TSA.global_size_QH if mesh.insulation.TSA.global_size_QH else min_h 

796 

797 # Conductor insulation layers 

798 for el, ins in self.ins_type_cond.items(): 

799 cond = self.data.conductors[self.wedge_cond[int(el[1:])]].cable if 'w' in el else self.data.conductors[pg.blocks[int(el)].conductor].cable 

800 for ins_side, tags in ins.items(): 

801 if tags: 

802 side_ins_type = [cond.material_insulation] 

803 if ins_side in ['inner', 'outer']: side_ins = [cond.th_insulation_along_width] 

804 elif ins_side in ['higher', 'lower']: side_ins = [cond.th_insulation_along_height] 

805 else: # mid_turn 

806 side_ins = [cond.th_insulation_along_height, cond.th_insulation_along_height] 

807 side_ins_type.append(cond.material_insulation) 

808 if ins_side[0] in 'iohl' and el + ins_side[0] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks: 

809 add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(el + ins_side[0]) 

810 side_ins.extend(self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx]) 

811 side_ins_type.extend(self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx]) 

812 if ins_side[0] in 'il': side_ins.reverse(), side_ins_type.reverse() 

813 self.rm.thin_shells.insulation_types.layers_number.append(0) 

814 self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags))) 

815 self.rm.thin_shells.insulation_types.thicknesses.append([]) 

816 self.rm.thin_shells.insulation_types.layers_material.append([]) 

817 for nr, ins_lyr in enumerate(side_ins): 

818 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h)) 

819 self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers 

820 self.rm.thin_shells.insulation_types.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers) 

821 self.rm.thin_shells.insulation_types.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers) 

822 

823 # Mid-pole, mid-winding, and mid-layer insulation layers 

824 ins_th_dict = self.md.geometries.thin_shells.ins_thickness.dict() 

825 for ins_type, ins in self.ins_type.items(): 

826 for ins_name, tags in ins.items(): 

827 # Get conductors insulation 

828 if ins_name.count('w') == 2: 

829 el1, el2 = int(ins_name[1:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 2:]) 

830 cond1 = self.data.conductors[self.wedge_cond[el1]].cable 

831 cond2 = self.data.conductors[self.wedge_cond[el2]].cable 

832 elif ins_name.count('w') == 1: 

833 el1, el2 = int(ins_name[1:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 1:]) 

834 cond1 = self.data.conductors[self.wedge_cond[el1]].cable 

835 cond2 = self.data.conductors[pg.blocks[el2].conductor].cable 

836 if ins_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg: cond1, cond2 = cond2, cond1 

837 else: 

838 el1, el2 = int(ins_name[:ins_name.index('_')]), int(ins_name[ins_name.index('_') + 1:]) 

839 cond1 = self.data.conductors[pg.blocks[el1].conductor].cable 

840 cond2 = self.data.conductors[pg.blocks[el2].conductor].cable 

841 if ins_type in ['mid_layer', 'aux']: 

842 cond_ins1, cond_ins2 = cond1.th_insulation_along_width, cond2.th_insulation_along_width 

843 else: 

844 cond_ins1, cond_ins2 = cond1.th_insulation_along_height, cond2.th_insulation_along_height 

845 # Get insulation layer thickness 

846 mid_materials, mid_thicknesses = _get_input_insulation_data(ins_name, i_type=ins_type) 

847 ins_thickness = ins_th_dict['mid_layer'][ins_name] / 2 if ins_type == 'aux' else ins_th_dict[ins_type][ins_name] 

848 mid_lyr_th, null_idx = _compute_insulation_thicknesses(ins_thickness, cond_ins1 + cond_ins2) 

849 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx) 

850 side_ins = [cond_ins1] + mid_lyr_th + [cond_ins2] 

851 # Get insulation materials 

852 side_ins_type = [cond1.material_insulation] + mid_materials + [cond2.material_insulation] 

853 # Initialize sublists 

854 self.rm.thin_shells.insulation_types.layers_number.append(0) 

855 self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags))) 

856 self.rm.thin_shells.insulation_types.thicknesses.append([]) 

857 self.rm.thin_shells.insulation_types.layers_material.append([]) 

858 for nr, ins_lyr in enumerate(side_ins): 

859 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h)) 

860 self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers 

861 self.rm.thin_shells.insulation_types.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers) 

862 self.rm.thin_shells.insulation_types.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers) 

863 

864 # Quench heater insulation layers 

865 for ins_type, ins in self.ins_type_qh.items(): 

866 for qh_nr, ts_groups in ins.items(): 

867 for ts_name, tags in ts_groups.items(): 

868 if ins_type != 'external': mid_materials, mid_thicknesses = _get_input_insulation_data(ts_name) 

869 if tags: 

870 if ins_type == 'external': 

871 data = self.qh_data[qh_nr] 

872 if str(ts_name) + data[ts_name]['ht_side'] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks: 

873 add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(str(ts_name) + data[ts_name]['ht_side']) 

874 additional_ths = self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx] 

875 additional_mats = self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx] 

876 else: additional_ths, additional_mats = [], [] 

877 cond = self.data.conductors[data[ts_name]['conductor']].cable 

878 ht_ins_th = cond.th_insulation_along_width if data[ts_name]['ht_side'] in 'io' else cond.th_insulation_along_height 

879 side_ins = [ht_ins_th] + [h_ins for h_ins in qh.h_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr - 1]] + additional_ths 

880 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr - 1]] + ['SS'] +\ 

881 [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr - 1]] + additional_mats 

882 if data[ts_name]['ht_side'] in 'il': side_ins.reverse(), side_ins_type.reverse() 

883 qh_list = [qh_nr] 

884 elif ins_type == 'internal': 

885 data = self.qh_data[qh_nr] 

886 cond = self.data.conductors[data[ts_name]['conductor']].cable 

887 cond2 = self.data.conductors[self.wedge_cond[int(ts_name.split('_')[0][1:])] if 'w' in ts_name 

888 else pg.blocks[int(ts_name.split('_')[1]) if data[ts_name]['ht_side'] == 'o' else int(ts_name.split('_')[0])].conductor].cable 

889 side_ins_qh = [h_ins for h_ins in qh.h_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr - 1]] 

890 mid_lyr_th, null_idx = _compute_insulation_thicknesses( 

891 ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width, cond2.th_insulation_along_width] + side_ins_qh)) 

892 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx) 

893 side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + [cond2.th_insulation_along_width] 

894 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr - 1]] + ['SS'] +\ 

895 [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr - 1]] + mid_materials + [cond2.material_insulation] 

896 if data[ts_name]['ht_side'] == 'i': side_ins.reverse(), side_ins_type.reverse() 

897 qh_list = [qh_nr] 

898 elif ins_type == 'internal_double': 

899 qh_nr1, qh_nr2 = int(qh_nr.split('_')[0]), int(qh_nr.split('_')[1]) 

900 data, data2 = self.qh_data[qh_nr1], self.qh_data[qh_nr2] 

901 cond = self.data.conductors[data[ts_name]['conductor']].cable 

902 cond2 = self.data.conductors[data2[ts_name]['conductor']].cable 

903 side_ins_qh = [h_ins for h_ins in qh.h_ins[qh_nr1 - 1]] + [qh.h[qh_nr1 - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr1 - 1]] 

904 side_ins_qh2 = [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr2 - 1][::-1]] + [qh.h[qh_nr2 - 1]] + [h_ins for h_ins in qh.h_ins[qh_nr2 - 1][::-1]] 

905 mid_lyr_th, null_idx = _compute_insulation_thicknesses( 

906 ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width, cond2.th_insulation_along_width] + side_ins_qh + side_ins_qh2)) 

907 for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx) 

908 side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + side_ins_qh2 + [cond2.th_insulation_along_width] 

909 side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr1 - 1]] + ['SS'] +\ 

910 [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr1 - 1]] + mid_materials +\ 

911 [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr2 - 1][::-1]] + ['SS'] +\ 

912 [type_ins for type_ins in qh.type_ins[qh_nr2 - 1][::-1]] + [cond2.material_insulation] 

913 qh_list = [qh_nr1, qh_nr2] 

914 qh_labels = [1 if m == 'SS' else None for m in side_ins_type] 

915 ss_indexes = [index for index, value in enumerate(qh_labels) if value == 1] 

916 for nr, idx in enumerate(ss_indexes): qh_labels[idx] = qh_list[nr] 

917 self.rm.thin_shells.quench_heaters.layers_number.append(0) 

918 self.rm.thin_shells.quench_heaters.thin_shells.append(list(set(tags))) 

919 self.rm.thin_shells.quench_heaters.thicknesses.append([]) 

920 self.rm.thin_shells.quench_heaters.layers_material.append([]) 

921 self.rm.thin_shells.quench_heaters.label.append([]) 

922 for nr, ins_lyr in enumerate(side_ins): 

923 tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_QH, round(ins_lyr / min_h_QH)) 

924 self.rm.thin_shells.quench_heaters.layers_number[-1] += tsa_layers 

925 self.rm.thin_shells.quench_heaters.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers) 

926 self.rm.thin_shells.quench_heaters.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers) 

927 self.rm.thin_shells.quench_heaters.label[-1].extend([qh_labels[nr]] * tsa_layers) 

928 

929 # Powered 

930 for blk_nr, blk in pg.blocks.items(): 

931 ht_list = list(blk.half_turns.keys()) 

932 for ht_nr, ht in blk.half_turns.items(): 

933 ht_name = f"ht{ht_nr}_{'EM' if 'bore_field' in mesh.dict() else 'TH'}" 

934 self.rm.powered[ht.group].conductors[blk.conductor].append(ht_name) 

935 self.rm.powered[ht.group].vol.names.append(ht_name) 

936 self.rm.powered[ht.group].vol.numbers.append(ht.tag) 

937 if 'bore_field' in mesh.dict(): 

938 self.rm.powered[ht.group].vol.currents.append(initial_current * (1 if blk.current_sign > 0 else -1)) 

939 if geometry.dict().get('use_TSA', False): 

940 for line in ['l', 'i', 'o', 'h']: 

941 # Bare edges 

942 self.rm.powered[ht.group].surf_in.names.append(ht_name + line) 

943 self.rm.powered[ht.group].surf_in.numbers.append(ht.lines[line]) 

944 if line in 'io': self.rm.thin_shells.normals_directed['radially'].append(ht.lines[line]) 

945 else: self.rm.thin_shells.normals_directed['azimuthally'].append(ht.lines[line]) 

946 # Auxiliary thin shells 

947 for line_name, line_tag in ht.aux_lines.items(): 

948 self.rm.powered[ht.group].surf_in.names.append(line_name) 

949 self.rm.powered[ht.group].surf_in.numbers.append(line_tag) 

950 self.rm.thin_shells.normals_directed['radially'].append(line_tag) 

951 # Thin shells 

952 for line_name, line_tag in dict(ht.mid_layer_lines.inner, **ht.mid_layer_lines.outer).items(): 

953 self.rm.powered[ht.group].surf_out.names.append(line_name) 

954 self.rm.powered[ht.group].surf_out.numbers.append(line_tag) 

955 self.rm.thin_shells.normals_directed['radially'].append(line_tag) 

956 for line_name, line_tag in dict(ht.mid_pole_lines, **ht.mid_winding_lines, **ht.mid_turn_lines).items(): 

957 self.rm.powered[ht.group].surf_out.names.append(line_name) 

958 self.rm.powered[ht.group].surf_out.numbers.append(line_tag) 

959 self.rm.thin_shells.normals_directed['azimuthally'].append(line_tag) 

960 

961 # Which thin shells or exterior conductor edges precede a second group (r2 or a2) 

962 if ht.group[1] == '2': 

963 # mid-turn thin shells precede r2 

964 for line_name, line_tag in ht.mid_turn_lines.items(): 

965 if (ht_list.index(ht_nr) != 0 and int(line_name[line_name.index('_') + 1:]) == ht_nr) or\ 

966 (ht_list.index(ht_nr) == 0 and 'w' in line_name): 

967 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag) 

968 # mid-pole thin shells precede r2 

969 if ht_list.index(ht_nr) == 0 and ht.mid_pole_lines: 

970 self.rm.thin_shells.second_group_is_next['azimuthally'].append(list(ht.mid_pole_lines.values())[0]) 

971 # conductor edges precede r2 

972 elif ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: 

973 self.rm.thin_shells.second_group_is_next['azimuthally'].append(ht.lines['l']) 

974 # mid-winding thin shells precede r2 

975 for line_name, line_tag in ht.mid_winding_lines.items(): 

976 if int(line_name[line_name.index('_') + 1:]) == ht_nr: 

977 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag) 

978 elif ht_list.index(ht_nr) == len(ht_list) - 1: 

979 # mid-turn thin shells precede r2 

980 for line_name, line_tag in ht.mid_turn_lines.items(): 

981 if 'w' in line_name: 

982 self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag) 

983 # conductor edges precede r2 

984 if len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: 

985 self.rm.thin_shells.second_group_is_next['azimuthally'].append(ht.lines['h']) 

986 if ht.group[4] == '2': 

987 # mid-layer thin shells precede a2 

988 for line_name, line_tag in ht.mid_layer_lines.inner.items(): 

989 self.rm.thin_shells.second_group_is_next['radially'].append(line_tag) 

990 elif not ht.mid_layer_lines.outer: 

991 # conductor edges precede a2 

992 self.rm.thin_shells.second_group_is_next['radially'].append(ht.lines['o']) 

993 

994 if geometry.dict().get('use_TSA', False): 

995 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']: 

996 unique_thin_shells.extend(self.rm.powered[group].surf_out.numbers) 

997 

998 # Wedges 

999 if geometry.with_wedges: 

1000 for wdg_nr, wdg in pg.wedges.items(): 

1001 wdg_name = f"w{wdg_nr}_{'EM' if 'bore_field' in mesh.dict() else 'TH'}" 

1002 self.rm.induced[wdg.group].vol.names.append(wdg_name) 

1003 self.rm.induced[wdg.group].vol.numbers.append(wdg.tag) 

1004 if geometry.dict().get('use_TSA', False): 

1005 # Bare edges 

1006 for line in ['l', 'i', 'o', 'h']: 

1007 self.rm.induced[wdg.group].surf_in.names.append(wdg_name + line) 

1008 self.rm.induced[wdg.group].surf_in.numbers.append(wdg.lines[line]) 

1009 if line in 'io': 

1010 self.rm.thin_shells.normals_directed['radially'].append(wdg.lines[line]) 

1011 else: 

1012 self.rm.thin_shells.normals_directed['azimuthally'].append(wdg.lines[line]) 

1013 # Auxiliary thin shells 

1014 for line_name, line_tag in wdg.aux_lines.items(): 

1015 self.rm.induced[wdg.group].surf_in.names.append(line_name) 

1016 self.rm.induced[wdg.group].surf_in.numbers.append(line_tag) 

1017 self.rm.thin_shells.normals_directed['radially'].append(line_tag) 

1018 # Thin shells 

1019 for line_name, line_tag in dict(wdg.mid_layer_lines.inner, **wdg.mid_layer_lines.outer).items(): 

1020 self.rm.induced[wdg.group].surf_out.names.append(line_name) 

1021 self.rm.induced[wdg.group].surf_out.numbers.append(line_tag) 

1022 self.rm.thin_shells.normals_directed['radially'].append(line_tag) 

1023 for line_name, line_tag in wdg.mid_turn_lines.items(): 

1024 self.rm.induced[wdg.group].surf_out.names.append(line_name) 

1025 self.rm.induced[wdg.group].surf_out.numbers.append(line_tag) 

1026 self.rm.thin_shells.normals_directed['azimuthally'].append(line_tag) 

1027 # Which thin shells or exterior conductor edges precede a second group (r2 or a2) 

1028 if wdg.group[4] == '2': 

1029 for line_name, line_tag in wdg.mid_layer_lines.inner.items(): 

1030 if line_name.count('w') == 2: 

1031 self.rm.thin_shells.second_group_is_next['radially'].append(line_tag) 

1032 elif not wdg.mid_layer_lines.outer: 

1033 self.rm.thin_shells.second_group_is_next['radially'].append(wdg.lines['o']) 

1034 if geometry.dict().get('use_TSA', False): 

1035 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']: 

1036 unique_thin_shells.extend(self.rm.induced[group].surf_out.numbers) 

1037 

1038 # Unique mid layers 

1039 if geometry.dict().get('use_TSA', False): 

1040 self.rm.thin_shells.mid_turns_layers_poles = list(set(unique_thin_shells)) 

1041 

1042 # Insulation 

1043 for group_name, surface in pg.insulations.surfaces.items(): 

1044 self.rm.insulator.vol.names.append('ins' + group_name) 

1045 self.rm.insulator.vol.numbers.append(surface) 

1046 if 'insulation' in mesh.dict() and 'TSA' in mesh.dict()["insulation"]: 

1047 for group_name, curve in pg.insulations.curves.items(): 

1048 self.rm.insulator.surf.names.append('ins' + group_name) 

1049 self.rm.insulator.surf.numbers.append(curve) 

1050 

1051 # Iron 

1052 for group_name, surface in pg.iron.surfaces.items(): 

1053 self.rm.iron.vol.names.append(group_name) 

1054 self.rm.iron.vol.numbers.append(surface) 

1055 

1056 # Boundary conditions 

1057 if 'insulation' in mesh.dict() and 'TSA' in mesh.dict()["insulation"]: 

1058 # Initialize lists 

1059 for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions, self.rm.boundaries.thermal): # b.c. type 

1060 bc_rm[1].bc.names = [] 

1061 bc_rm[1].bc.numbers = [] 

1062 if bc_data[0] == 'cooling': 

1063 bc_rm[1].bc.values = [] 

1064 for group in ['1_r1_a1', '2_r1_a1', '1_r2_a1', '2_r2_a1', '1_r1_a2', '2_r1_a2', '1_r2_a2', '2_r2_a2']: 

1065 bc_rm[1].groups[group] = [] 

1066 else: 

1067 bc_rm[1].bc.value = [] 

1068 for group in ['r1_a1', 'r2_a1', 'r1_a2', 'r2_a2']: 

1069 bc_rm[1].groups[group] = [] 

1070 

1071 # Apply general cooling and adiabatic 

1072 if self.data.magnet.solve.thermal.He_cooling.enabled: 

1073 cooling_side = {'i': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in ['inner', 'external']), 

1074 'o': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in ['outer', 'external']), 

1075 'hl': self.data.magnet.solve.thermal.He_cooling.sides == 'external'} 

1076 else: 

1077 cooling_side = {'i': False, 'o': False, 'hl': False} 

1078 

1079 def __assign_bnd_tag(el, name, side, bc_type, tag=None): 

1080 line_tag = tag if tag else el.lines[side] 

1081 bnd_list_names[bc_type].append(name + side) 

1082 bnd_list_numbers[bc_type].append(line_tag) 

1083 if side in 'io': new_group = el.group[:3] + 'a1' if el.group[4] == '2' else el.group[:3] + 'a2' 

1084 else: new_group = 'r1' + el.group[2:] if el.group[1] == '2' else 'r2' + el.group[2:] 

1085 bc_rm[bc_type].groups[new_group].append(line_tag) 

1086 for group_name, group in self.rm.thin_shells.groups.items(): 

1087 if line_tag in group: 

1088 bc_rm[bc_type].groups[el.group[0] + '_' + new_group].append(line_tag) 

1089 break 

1090 

1091 bc_rm = {'Robin': self.rm.boundaries.thermal.cooling, 'Neumann': self.rm.boundaries.thermal.heat_flux} 

1092 bnd_list_names = {'Robin': [], 'Neumann': []} 

1093 bnd_list_numbers = {'Robin': [], 'Neumann': []} 

1094 if geometry.dict().get('use_TSA', False): 

1095 # Half turn boundaries 

1096 for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items(): 

1097 for lyr_nr, orders in coil.layers.items(): 

1098 for order in orders: 

1099 ht_list = list(pg.blocks[order.block].half_turns.keys()) 

1100 for ht_nr, ht in pg.blocks[order.block].half_turns.items(): 

1101 if not ht.mid_layer_lines.inner: 

1102 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'i', 'Robin' if cooling_side['i'] else 'Neumann') 

1103 if not ht.mid_layer_lines.outer: 

1104 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann') 

1105 if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: 

1106 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'l', 'Robin' if cooling_side['hl'] else 'Neumann') 

1107 if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: 

1108 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'h', 'Robin' if cooling_side['hl'] else 'Neumann') 

1109 if ht.aux_lines: 

1110 __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann', list(ht.aux_lines.values())[0]) 

1111 # Wedge boundaries 

1112 for wdg_nr, wdg in pg.wedges.items(): 

1113 if not wdg.mid_layer_lines.inner: 

1114 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'i', 'Robin' if cooling_side['i'] else 'Neumann') 

1115 if not wdg.mid_layer_lines.outer: 

1116 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann') 

1117 if wdg.aux_lines: 

1118 __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann', list(wdg.aux_lines.values())[0]) 

1119 else: # insulation case 

1120 for curves_group, tag in pg.insulations.curves.items(): 

1121 if self.data.magnet.solve.thermal.He_cooling.enabled: 

1122 if self.data.magnet.solve.thermal.He_cooling.sides == 'external': bc_type = 'Robin' 

1123 else: 

1124 raise ValueError(f"Cooling side '{self.data.magnet.solve.thermal.He_cooling.sides}' is not supported for meshed insulation models.") 

1125 # bc_type = 'Robin' if (self.data.magnet.solve.thermal.He_cooling.sides == 'external' or 

1126 # ('inner' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'i') or 

1127 # ('outer' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'o')) else 'Neumann' 

1128 else: 

1129 bc_type = 'Neumann' 

1130 bnd_list_names[bc_type].append('ins' + curves_group) 

1131 bnd_list_numbers[bc_type].append(tag) 

1132 if self.data.magnet.solve.thermal.He_cooling.enabled: 

1133 bc_rm['Robin'].bc.names.append(bnd_list_names['Robin']) 

1134 bc_rm['Robin'].bc.numbers.append(bnd_list_numbers['Robin']) 

1135 bc_rm['Robin'].bc.values.append([self.data.magnet.solve.thermal.He_cooling.heat_transfer_coefficient, 

1136 self.data.magnet.solve.thermal.init_temperature]) 

1137 if bnd_list_names['Neumann']: 

1138 bc_rm['Neumann'].bc.names.append(bnd_list_names['Neumann']) 

1139 bc_rm['Neumann'].bc.numbers.append(bnd_list_numbers['Neumann']) 

1140 bc_rm['Neumann'].bc.value.append(0.) 

1141 

1142 # Apply specific boundary conditions 

1143 for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions, self.rm.boundaries.thermal): # b.c. type 

1144 # bc_data is a tuple like: ('temperature', {'const_T1': boundaries, value)}) 

1145 # bc_rm is a tuple like: ('temperature', DirichletCondition(names, numbers, value)) 

1146 

1147 for _, bc in bc_data[1].items(): # all boundary conditions of one b.c. type (e.g., Dirichlet with different temperatures) 

1148 bnd_list_names = [] 

1149 bnd_list_numbers = [] 

1150 if geometry.dict().get('use_TSA', False): 

1151 for bnd in bc.boundaries: # all boundaries of one boundary condition 

1152 if bnd[0] == 'w': 

1153 if not geometry.with_wedges: 

1154 raise Exception('Wedge regions are disabled.') 

1155 # Fetch the physical group of the wedge 

1156 pg_el = pg.wedges[int(bnd[1:-1])] 

1157 name = 'wd' + bnd[1:] 

1158 else: 

1159 # Fetch the physical group of the half turn 

1160 ht_index = self.strands['ht'].index(int(bnd[:-1])) 

1161 pg_el = pg.blocks[self.strands['block'][ht_index]].half_turns[int(bnd[:-1])] 

1162 name = 'ht' + bnd 

1163 line_pg_tag = pg_el.lines[bnd[-1]] 

1164 bnd_list_names.append(name) 

1165 bnd_list_numbers.append(line_pg_tag) 

1166 # Find the half turn group this boundary is assigned to and take the complementary 

1167 if bnd[-1] in 'io': 

1168 new_group = pg_el.group[:3] + 'a1' if pg_el.group[4] == '2' else pg_el.group[:3] + 'a2' 

1169 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 

1170 new_group = 'r1' + pg_el.group[2:] if pg_el.group[1] == '2' else 'r2' + pg_el.group[2:] 

1171 # Overwrite general cooling and adiabatic condition 

1172 if self.data.magnet.solve.thermal.He_cooling.enabled: 

1173 if name in self.rm.boundaries.thermal.cooling.bc.names[0]: 

1174 bnd_idx = self.rm.boundaries.thermal.cooling.bc.names[0].index(name) 

1175 self.rm.boundaries.thermal.cooling.bc.names[0].pop(bnd_idx) 

1176 bnd_idx = self.rm.boundaries.thermal.cooling.bc.numbers[0].pop(bnd_idx) 

1177 self.rm.boundaries.thermal.cooling.groups[new_group].pop( 

1178 self.rm.boundaries.thermal.cooling.groups[new_group].index(bnd_idx)) 

1179 if self.data.magnet.solve.thermal.He_cooling.sides != 'external': 

1180 if name in self.rm.boundaries.thermal.heat_flux.bc.names[0]: 

1181 bnd_idx = self.rm.boundaries.thermal.heat_flux.bc.names[0].index(name) 

1182 self.rm.boundaries.thermal.heat_flux.bc.names[0].pop(bnd_idx) 

1183 bnd_idx = self.rm.boundaries.thermal.heat_flux.bc.numbers[0].pop(bnd_idx) 

1184 self.rm.boundaries.thermal.heat_flux.groups[new_group].pop( 

1185 self.rm.boundaries.thermal.heat_flux.groups[new_group].index(bnd_idx)) 

1186 # Assign the tag 

1187 bc_rm[1].groups[new_group].append(line_pg_tag) 

1188 # Extra grouping for Robin virtual shells 

1189 if bc_data[0] == 'cooling': 

1190 for group_name, group in self.rm.thin_shells.groups.items(): 

1191 if line_pg_tag in group: 

1192 bc_rm[1].groups[group_name[0] + '_' + new_group].append(line_pg_tag) 

1193 break 

1194 else: # the b.c. are assigned to insulation boundaries instead 

1195 pass # todo: not supported yet 

1196 bc_rm[1].bc.names.append(bnd_list_names) 

1197 bc_rm[1].bc.numbers.append(bnd_list_numbers) 

1198 if bc_data[0] == 'cooling': 

1199 bc_rm[1].bc.values.append([bc.heat_transfer_coefficient, self.data.magnet.solve.thermal.init_temperature]) 

1200 elif bc_data[0] == 'temperature': 

1201 bc_rm[1].bc.value.append(bc.const_temperature) 

1202 elif bc_data[0] == 'heat_flux': 

1203 bc_rm[1].bc.value.append(bc.const_heat_flux) 

1204 

1205 def setMeshOptions(self, run_type): 

1206 """ 

1207 Meshes the generated domain 

1208 """ 

1209 mesh = self.data.magnet.mesh 

1210 gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0) 

1211 gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0) 

1212 gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0) 

1213 gmsh.option.setNumber("Mesh.Algorithm", 6) 

1214 gmsh.option.setNumber("Mesh.Optimize", 1) 

1215 gmsh.option.setNumber("Mesh.ElementOrder", 1) 

1216 

1217 def generateMesh(self): 

1218 gmsh.option.setNumber("General.Terminal", self.verbose) 

1219 self.mesh.generate(2) 

1220 # self.mesh.removeDuplicateNodes() 

1221 # self.occ.synchronize() 

1222 # self.gu.launch_interactive_GUI() 

1223 

1224 def checkMeshQuality(self): 

1225 tags = self.mesh.getElements(2)[1][0] 

1226 

1227 self.mesh_parameters['SJ'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSJ')) 

1228 self.mesh_parameters['SICN'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSICN')) 

1229 self.mesh_parameters['SIGE'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='minSIGE')) 

1230 self.mesh_parameters['Gamma'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='gamma')) 

1231 self.mesh_parameters['nodes'] = len(self.mesh.getNodes()[0]) 

1232 

1233 # gmsh.plugin.setNumber("AnalyseMeshQuality", "JacobianDeterminant", 1) 

1234 # gmsh.plugin.setNumber("AnalyseMeshQuality", "CreateView", 100) 

1235 # test = gmsh.plugin.run("AnalyseMeshQuality") 

1236 # test2 = gmsh.view.getModelData(test, test) 

1237 

1238 # gmsh.logger.getLastError() 

1239 # gmsh.logger.get() 

1240 

1241 def saveClosestNeighboursList(self): 

1242 

1243 def _closest_node_on_reference(origin_points, reference_points): 

1244 """ 

1245 Compute list of lists with each list representing one original node (3 entries for 3 coordinates) and the closest 

1246 reference mesh node (3 entries for 3 coordinates). 

1247 :param origin_points: list of origin point as list of 3 floats per origin node 

1248 :param reference_points: list of coordinates of reference mesh points as list of 3 floats per reference mesh node 

1249 :return: returns list of lists with the closest reference mesh node per origin node 

1250 """ 

1251 closest_node = [] 

1252 for x in range(0, len(origin_points), 3): 

1253 origin_point = origin_points[x:x + 3] 

1254 # Compute distance list between origin point and reference point list 

1255 dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in range(0, len(reference_points), 3)] 

1256 min_idx = 3 * np.argmin(dist_lst) 

1257 closest_node.append(origin_point + reference_points[min_idx:min_idx + 3]) 

1258 return closest_node 

1259 

1260 def _get_closest_nodes(side): 

1261 origin_list = self.mesh.getNodesForPhysicalGroup(1, mid_layer)[1].tolist() 

1262 reference_list = self.mesh.getNodesForPhysicalGroup(1, el.lines[side])[1].tolist() 

1263 self.rc.neighbouring_nodes.groups[group].extend( 

1264 [node for node_list in _closest_node_on_reference(origin_list, reference_list) for node in node_list]) 

1265 

1266 logger.info(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 . . .") 

1267 logger.info(f"Info : Finding closest reference nodes ...") 

1268 

1269 self.rc.neighbouring_nodes.groups['1_1'] = [] 

1270 self.rc.neighbouring_nodes.groups['2_1'] = [] 

1271 self.rc.neighbouring_nodes.groups['1_2'] = [] 

1272 self.rc.neighbouring_nodes.groups['2_2'] = [] 

1273 

1274 for blk_nr, blk in self.md.domains.physical_groups.blocks.items(): 

1275 for ht_nr, el in blk.half_turns.items(): 

1276 ht_list = list(blk.half_turns.keys()) 

1277 group = el.group[1] + '_' + el.group[-1] 

1278 for line_name, mid_layer in el.mid_layer_lines.inner.items(): _get_closest_nodes('i') 

1279 for line_name, mid_layer in el.mid_layer_lines.outer.items(): _get_closest_nodes('o') 

1280 for line_name, mid_layer in el.aux_lines.items(): _get_closest_nodes('o') 

1281 for line_name, mid_layer in el.mid_pole_lines.items(): _get_closest_nodes('l' if ht_list.index(ht_nr) == 0 else 'h') 

1282 for line_name, mid_layer in el.mid_winding_lines.items(): _get_closest_nodes('l' if ht_list.index(ht_nr) == 0 else 'h') 

1283 for line_name, mid_layer in el.mid_turn_lines.items(): 

1284 high = ht_list.index(ht_nr) == len(ht_list) - 1 if 'w' in line_name\ 

1285 else int(line_name[:line_name.index('_')]) == ht_nr 

1286 _get_closest_nodes('h' if high else 'l') 

1287 for wdg_nr, el in self.md.domains.physical_groups.wedges.items(): 

1288 group = el.group[1] + '_' + el.group[-1] 

1289 for line_name, mid_layer in el.mid_layer_lines.inner.items(): _get_closest_nodes('i') 

1290 for line_name, mid_layer in el.mid_layer_lines.outer.items(): _get_closest_nodes('o') 

1291 for line_name, mid_layer in el.aux_lines.items(): _get_closest_nodes('o') 

1292 for line_name, mid_layer in el.mid_turn_lines.items(): 

1293 _get_closest_nodes('l' if line_name == list(el.mid_turn_lines.keys())[0] else 'h') 

1294 

1295 logger.info(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") 

1296 

1297 def selectMeshNodes(self, elements: str): 

1298 

1299 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 . . .") 

1300 logger.info(f"Info : Selecting a mesh node per isothermal {elements[:-1]} ...") 

1301 

1302 if elements == 'conductors': 

1303 bare_mesh = {'1_1': self.rm.powered['r1_a1'].vol.numbers, '2_1': self.rm.powered['r2_a1'].vol.numbers, 

1304 '1_2': self.rm.powered['r1_a2'].vol.numbers, '2_2': self.rm.powered['r2_a2'].vol.numbers} 

1305 groups = self.rc.isothermal_nodes.conductors 

1306 

1307 # dir + robin + mid_layers 

1308 # potentially all thin shell lines if easier 

1309 line_tags = self.rm.boundaries.thermal.temperature.groups['r1_a1'] + \ 

1310 self.rm.boundaries.thermal.temperature.groups['r1_a2'] + \ 

1311 self.rm.boundaries.thermal.temperature.groups['r2_a1'] + \ 

1312 self.rm.boundaries.thermal.temperature.groups['r2_a2'] + \ 

1313 self.rm.boundaries.thermal.cooling.groups['r1_a1'] + \ 

1314 self.rm.boundaries.thermal.cooling.groups['r1_a2'] + \ 

1315 self.rm.boundaries.thermal.cooling.groups['r2_a1'] + \ 

1316 self.rm.boundaries.thermal.cooling.groups['r2_a2'] + \ 

1317 self.rm.thin_shells.mid_turns_layers_poles 

1318 for tag in line_tags: 

1319 coords = list(self.mesh.getNodesForPhysicalGroup(dim=1, tag=tag)[1])[:3] 

1320 self.rc.isothermal_nodes.thin_shells[tag] = [float(coords[0]), float(coords[1]), float(coords[2])] 

1321 

1322 elif elements == 'wedges': 

1323 bare_mesh = {'1_1': self.rm.induced['r1_a1'].vol.numbers, '2_1': self.rm.induced['r2_a1'].vol.numbers, 

1324 '1_2': self.rm.induced['r1_a2'].vol.numbers, '2_2': self.rm.induced['r2_a2'].vol.numbers} 

1325 groups = self.rc.isothermal_nodes.wedges 

1326 else: 

1327 bare_mesh = {} 

1328 groups = {} 

1329 

1330 for group, tags_list in bare_mesh.items(): 

1331 groups[group] = {} 

1332 for tag in tags_list: 

1333 coords = list(self.mesh.getNodesForPhysicalGroup(dim=2, tag=tag)[1])[:3] 

1334 groups[group][tag] = [float(coords[0]), float(coords[1]), float(coords[2])] 

1335 

1336 for tag in self.rm.boundaries.thermal.cooling.groups['r' + group[0] + '_' + 'a' + group[-1]]: 

1337 coords = list(self.mesh.getNodesForPhysicalGroup(dim=1, tag=tag)[1])[:3] 

1338 groups[group][tag] = [float(coords[0]), float(coords[1]), float(coords[2])] 

1339 

1340 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") 

1341 # import time 

1342 # time.sleep(100) 

1343 # TODO: add to pro file automatically 

1344 

1345 # self.occ.synchronize() 

1346 # self.gu.launch_interactive_GUI() 

1347