Coverage for fiqus/mesh_generators/MeshHomogenizedConductor.py: 8%

151 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-11-29 01:35 +0000

1import os, json, gmsh 

2 

3from fiqus.data import RegionsModelFiQuS 

4from fiqus.utils.Utils import GmshUtils, FilesAndFolders 

5from fiqus.data.RegionsModelFiQuS import RegionsModel 

6 

7class Mesh: 

8 def __init__(self, fdm, verbose=True): 

9 """ 

10 A base-class used to manage the mesh for HomogenizedConductor model. 

11 

12 :ivar fdm: The fiqus data model for input parameters. 

13 :vartype fdm: dict 

14 """ 

15 self.fdm = fdm 

16 self.mesh_folder = os.path.join(os.getcwd()) 

17 self.geom_folder = os.path.dirname(self.mesh_folder) 

18 self.mesh_file = os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}.msh") 

19 self.regions_file = os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}.regions") 

20 self.vi_file = os.path.join(self.geom_folder, f'{self.fdm.general.magnet_name}.vi') 

21 self.verbose = verbose 

22 

23 # dictionaries for physical groups 

24 self.dimTags_physical_surfaces = {} 

25 self.dimTags_physical_boundaries = {} 

26 self.dimTags_physical_cuts = {} 

27 self.dimTags_physical_points ={} 

28 

29 # Read volume information file: 

30 with open(self.vi_file, "r") as f: 

31 self.dimTags = json.load(f) 

32 

33 for key, value in self.dimTags.items(): 

34 self.dimTags[key] = tuple(value) # dimTags contains all surfaces 

35 

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

37 self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh) 

38 

39 gmsh.option.setNumber("General.Terminal", verbose) 

40 

41 def generate_mesh(self): 

42 """ This function generates a mesh based on the volume information (vi) file created in the geometry step and the yaml input. """ 

43 

44 self.generate_physical_groups() 

45 

46 # scale by domain size 

47 domain_size = self.fdm.magnet.geometry.air.radius 

48 # Mesh size field for cables: 

49 strand_field = gmsh.model.mesh.field.add("Constant") 

50 gmsh.model.mesh.field.setNumbers(strand_field, "SurfacesList", [value[1] for key, value in self.dimTags.items() if 'Cable' in key]) 

51 gmsh.model.mesh.field.setNumber(strand_field, "VIn", self.fdm.magnet.mesh.cable_mesh_size_ratio * domain_size) 

52 # Mesh size field for excitation coils: 

53 coil_field = gmsh.model.mesh.field.add("Constant") 

54 gmsh.model.mesh.field.setNumbers(coil_field, "SurfacesList", [value[1] for key, value in self.dimTags.items() if 'Coil' in key]) 

55 gmsh.model.mesh.field.setNumber(coil_field, "VIn", self.fdm.magnet.mesh.cable_mesh_size_ratio * domain_size) 

56 # Mesh size field for air: 

57 air_field = gmsh.model.mesh.field.add("Constant") 

58 gmsh.model.mesh.field.setNumbers(air_field, "SurfacesList", [self.dimTags['Air'][1]]) 

59 gmsh.model.mesh.field.setNumber(air_field, "VIn", self.fdm.magnet.mesh.air_boundary_mesh_size_ratio * domain_size) 

60 

61 total_meshSize_field = gmsh.model.mesh.field.add("Min") 

62 gmsh.model.mesh.field.setNumbers(total_meshSize_field, "FieldsList", [strand_field, coil_field, air_field]) 

63 gmsh.model.mesh.field.setAsBackgroundMesh(total_meshSize_field) 

64 

65 gmsh.option.setNumber("Mesh.MeshSizeFactor", self.fdm.magnet.mesh.scaling_global) 

66 gmsh.model.mesh.generate(2) 

67 

68 

69 def generate_physical_groups(self): 

70 """ This function generates the physical groups within the mesh based on the volume information (vi) file and stores their tags in dictionaries. 

71 

72 :raises ValueError: For unknown volume names in the vi-file 

73 """ 

74 gmsh.model.occ.synchronize() 

75 

76 for key, value in self.dimTags.items(): 

77 # surface 

78 surf_tag = gmsh.model.addPhysicalGroup(dim=value[0], tags=[value[1]], name=key) 

79 self.dimTags_physical_surfaces.update({str(key):(2, surf_tag)}) 

80 # (outer) boundary curves 

81 if 'Air' in key: 

82 boundary_curves = gmsh.model.get_boundary([tuple(val) for val in self.dimTags.values()], combined=True) 

83 elif 'Cable' in key: 

84 boundary_curves = gmsh.model.get_boundary([tuple(value)]) 

85 elif 'Coil' in key: 

86 boundary_curves = gmsh.model.get_boundary([tuple(value)]) 

87 else: 

88 raise ValueError('Unknown volume in declaration in VI file.') 

89 bnd_tag = gmsh.model.addPhysicalGroup(dim=1, tags=[dimTag[1] for dimTag in boundary_curves], name=key+'Boundary' ) 

90 self.dimTags_physical_boundaries.update({str(key+'Boundary' ):(1, bnd_tag)}) 

91 # arbitrary boundary point 

92 point_tag = gmsh.model.addPhysicalGroup(dim=0, tags=[gmsh.model.get_boundary(boundary_curves, combined=False)[0][1]], name=key+'BoundaryPoint' ) 

93 self.dimTags_physical_points.update({str(key+'BoundaryPoint' ):(1, point_tag)}) 

94 

95 

96 def generate_cuts(self): 

97 """ This function generates and orients the domain cuts for the cable and coil regions through the gmsh internal homology module.""" 

98 

99 dimTags_physical_cable_boundaries = {k: v for k, v in self.dimTags_physical_boundaries.items() if 'Cable' in k} 

100 dimTags_physical_excitation_coil_boundaries = {k: v for k, v in self.dimTags_physical_boundaries.items() if 'Coil' in k} 

101 

102 tags_physical_cable_surfaces = [v[1] for k, v in self.dimTags_physical_surfaces.items() if 'Cable' in k] 

103 tags_physical_excitation_coil_surfaces = [v[1] for k, v in self.dimTags_physical_surfaces.items() if 'Coil' in k] 

104 

105 # print(dimTags_physical_cable_boundaries) 

106 # print(dimTags_physical_excitation_coil_boundaries) 

107 # print(tags_physical_cable_surfaces) 

108 # print(tags_physical_excitation_coil_surfaces) 

109 

110 # cohomology cuts for all cables 

111 for _, value in dimTags_physical_cable_boundaries.items(): 

112 gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[value[1]], dims=[1]) 

113 for _, value in dimTags_physical_excitation_coil_boundaries.items(): 

114 gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[value[1]], dims=[1]) 

115 

116 gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[self.dimTags_physical_surfaces['Air'][1]]+tags_physical_excitation_coil_surfaces, dims=[1]) 

117 for coil_surface in tags_physical_excitation_coil_surfaces: 

118 tags_physical_excitation_other_coil_surfaces = [other_coil for other_coil in tags_physical_excitation_coil_surfaces if other_coil != coil_surface] 

119 gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[self.dimTags_physical_surfaces['Air'][1]]+tags_physical_cable_surfaces+tags_physical_excitation_other_coil_surfaces, dims=[1]) 

120 

121 

122 

123 dimTags_homology = gmsh.model.mesh.computeHomology() 

124 # print(dimTags_homology) 

125 dimTags_bnds = dimTags_homology[:int(len(dimTags_homology)/2)] # first half are dimTags are the cut boundaries 

126 dimTags_cuts = dimTags_homology[int(len(dimTags_homology)/2):] # second half are the actual cut edges 

127 gmsh.model.mesh.clearHomologyRequests() 

128 

129 # post process homology cuts 

130 bnd_tags = [] 

131 cut_tags = [] 

132 for i in range(len(dimTags_physical_cable_boundaries)): 

133 self.dimTags_physical_cuts.update({'Cable'+str(i+1)+'Cut':(1, dimTags_cuts[-1][1]+(i+1))}) # +1 tag shift for post processed cuts 

134 bnd_tags.append(dimTags_bnds[i][1]) 

135 cut_tags.append(dimTags_cuts[i][1]) 

136 for i in range(len(dimTags_physical_excitation_coil_boundaries)): 

137 self.dimTags_physical_cuts.update({'Coil'+str(i+1)+'Cut':(1, dimTags_cuts[-1][1]+(len(dimTags_physical_cable_boundaries)+i+1))}) # +1 tag shift for post processed cuts 

138 bnd_tags.append(dimTags_bnds[len(dimTags_physical_cable_boundaries)+i][1]) 

139 cut_tags.append(dimTags_cuts[len(dimTags_physical_cable_boundaries)+i][1]) 

140 gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", ','.join(map(str,bnd_tags))) 

141 gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", ','.join(map(str,cut_tags))) 

142 gmsh.plugin.run("HomologyPostProcessing") 

143 

144 

145 def generate_regions_file(self): 

146 """ 

147 Generates a .regions file for the GetDP solver, containing all necessary information about the model. 

148 The regions model contains data about physical surfaces, boundaries, points and cuts and is stored in the mesh folder. 

149 

150 :raises ValueError: For unknown volumes in the vi-file 

151 """ 

152 regions_model = RegionsModel() 

153 

154 # The region Cables will include the homogenized surfaces 

155 regions_model.powered['Cables'] = RegionsModelFiQuS.Powered() 

156 regions_model.powered['Cables'].vol.names = [] 

157 regions_model.powered['Cables'].vol.numbers = [] 

158 regions_model.powered['Cables'].surf.names = [] 

159 regions_model.powered['Cables'].surf.numbers = [] 

160 regions_model.powered['Cables'].curve.names = [] 

161 regions_model.powered['Cables'].curve.numbers = [] 

162 regions_model.powered['Cables'].cochain.names = [] 

163 regions_model.powered['Cables'].cochain.numbers = [] 

164 # Excitation coil regions 

165 regions_model.powered['ExcitationCoils'] = RegionsModelFiQuS.Powered() 

166 regions_model.powered['ExcitationCoils'].vol.names = [] 

167 regions_model.powered['ExcitationCoils'].vol.numbers = [] 

168 regions_model.powered['ExcitationCoils'].surf.names = [] 

169 regions_model.powered['ExcitationCoils'].surf.numbers = [] 

170 regions_model.powered['ExcitationCoils'].curve.names = [] 

171 regions_model.powered['ExcitationCoils'].curve.numbers = [] 

172 regions_model.powered['ExcitationCoils'].cochain.names = [] 

173 regions_model.powered['ExcitationCoils'].cochain.numbers = [] 

174 

175 gmsh.model.occ.synchronize() 

176 for name in self.dimTags.keys(): 

177 cut_name = name+'Cut' 

178 boundary_name = name+'Boundary' 

179 point_name = boundary_name+'Point' 

180 if 'Air' in name: 

181 regions_model.air.vol.number = self.dimTags_physical_surfaces[name][1] 

182 regions_model.air.vol.name = "Air" 

183 regions_model.air.surf.number = self.dimTags_physical_boundaries[boundary_name][1] 

184 regions_model.air.surf.name = boundary_name 

185 regions_model.air.point.names = [point_name] 

186 regions_model.air.point.numbers = [self.dimTags_physical_points[point_name][1]] 

187 elif 'Cable' in name: 

188 regions_model.powered['Cables'].vol.names.append(name) 

189 regions_model.powered['Cables'].vol.numbers.append(self.dimTags_physical_surfaces[name][1]) 

190 regions_model.powered['Cables'].surf.names.append(boundary_name) 

191 regions_model.powered['Cables'].surf.numbers.append(self.dimTags_physical_boundaries[boundary_name][1]) 

192 regions_model.powered['Cables'].curve.names.append(point_name) 

193 regions_model.powered['Cables'].curve.numbers.append(self.dimTags_physical_points[point_name][1]) 

194 regions_model.powered['Cables'].cochain.names.append(cut_name) 

195 regions_model.powered['Cables'].cochain.numbers.append(self.dimTags_physical_cuts[cut_name][1]) 

196 elif 'Coil' in name: 

197 regions_model.powered['ExcitationCoils'].vol.names.append(name) 

198 regions_model.powered['ExcitationCoils'].vol.numbers.append(self.dimTags_physical_surfaces[name][1]) 

199 regions_model.powered['ExcitationCoils'].surf.names.append(boundary_name) 

200 regions_model.powered['ExcitationCoils'].surf.numbers.append(self.dimTags_physical_boundaries[boundary_name][1]) 

201 regions_model.powered['ExcitationCoils'].curve.names.append(point_name) 

202 regions_model.powered['ExcitationCoils'].curve.numbers.append(self.dimTags_physical_points[point_name][1]) 

203 regions_model.powered['ExcitationCoils'].cochain.names.append(cut_name) 

204 regions_model.powered['ExcitationCoils'].cochain.numbers.append(self.dimTags_physical_cuts[cut_name][1]) 

205 else: 

206 raise ValueError('Unknown physical region') 

207 

208 FilesAndFolders.write_data_to_yaml(self.regions_file, regions_model.model_dump()) 

209 

210 def save_mesh(self, gui: bool = False): 

211 """ Saves the mesh to a .msh file. If gui is True, the mesh is also loaded in the gmsh GUI. """ 

212 

213 gmsh.write(self.mesh_file) 

214 if gui: 

215 self.gu.launch_interactive_GUI() 

216 else: 

217 if gmsh.isInitialized(): 

218 gmsh.clear() 

219 gmsh.finalize() 

220 

221 def load_mesh(self, gui : bool = False): 

222 """ Loads a previously generated mesh. """ 

223 

224 gmsh.clear() 

225 gmsh.open(self.mesh_file) 

226 

227 if gui: 

228 self.gu.launch_interactive_GUI() 

229