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
« prev ^ index » next coverage.py v7.4.4, created at 2025-11-29 01:35 +0000
1import os, json, gmsh
3from fiqus.data import RegionsModelFiQuS
4from fiqus.utils.Utils import GmshUtils, FilesAndFolders
5from fiqus.data.RegionsModelFiQuS import RegionsModel
7class Mesh:
8 def __init__(self, fdm, verbose=True):
9 """
10 A base-class used to manage the mesh for HomogenizedConductor model.
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
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 ={}
29 # Read volume information file:
30 with open(self.vi_file, "r") as f:
31 self.dimTags = json.load(f)
33 for key, value in self.dimTags.items():
34 self.dimTags[key] = tuple(value) # dimTags contains all surfaces
36 self.gu = GmshUtils(self.mesh_folder, self.verbose)
37 self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
39 gmsh.option.setNumber("General.Terminal", verbose)
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. """
44 self.generate_physical_groups()
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)
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)
65 gmsh.option.setNumber("Mesh.MeshSizeFactor", self.fdm.magnet.mesh.scaling_global)
66 gmsh.model.mesh.generate(2)
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.
72 :raises ValueError: For unknown volume names in the vi-file
73 """
74 gmsh.model.occ.synchronize()
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)})
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."""
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}
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]
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)
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])
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])
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()
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")
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.
150 :raises ValueError: For unknown volumes in the vi-file
151 """
152 regions_model = RegionsModel()
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 = []
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')
208 FilesAndFolders.write_data_to_yaml(self.regions_file, regions_model.model_dump())
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. """
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()
221 def load_mesh(self, gui : bool = False):
222 """ Loads a previously generated mesh. """
224 gmsh.clear()
225 gmsh.open(self.mesh_file)
227 if gui:
228 self.gu.launch_interactive_GUI()