Coverage for steam_pysigma\domain_generator\GeometryMultipole.py: 97%

181 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-12-16 17:09 +0100

1# STEAM PySigma is a python wrapper of STEAM-SIGMA written in Java. 

2# Copyright (C) 2023, CERN, Switzerland. All rights reserved. 

3# 

4# This program is free software: you can redistribute it and/or modify 

5# it under the terms of the GNU General Public License as published by 

6# the Free Software Foundation, version 3 of the License. 

7# 

8# This program is distributed in the hope that it will be useful, 

9# but WITHOUT ANY WARRANTY; without even the implied warranty of 

10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

11# GNU General Public License for more details. 

12# 

13# You should have received a copy of the GNU General Public License 

14# along with this program. If not, see <https://www.gnu.org/licenses/>. 

15 

16from steam_pysigma.utils.Utils import arcCenter 

17import steam_pysigma.sigma.pysigma as ps 

18 

19 

20class GeometryMultipole: 

21 """ 

22 Class creates SIGMA domain objects 

23 IMPROVMENTS: 

24 No dependency on config sigma 

25 """ 

26 

27 def __init__(self, g, input_roxie_data, input_model_data, bh_curve_database, input_conductor_params, settings_dict, COMSOL_VERSION, flag_build=True, 

28 verbose=False): 

29 

30 self.model_data = input_model_data 

31 self.roxie_data = input_roxie_data 

32 self.input_conductor_params = input_conductor_params 

33 self.bh_curve_database = bh_curve_database 

34 self.settings_dict: dict = settings_dict 

35 self.COMSOL_version = COMSOL_VERSION 

36 self.verbose = verbose 

37 if (not self.model_data or not self.roxie_data) and flag_build: 

38 raise Exception('Cannot build model without providing DataModelMagnet and RoxieData') 

39 elif flag_build: 

40 self.g = g 

41 self.a = ps.ArraysSIGMA 

42 self.cfg = self.g.ConfigSigma() 

43 self.cfg.setComsolVersion(self.COMSOL_version) 

44 self.max_r: float = 0.1 

45 self.conductor_name = str 

46 self.cableParameters = {'cable': {}, 'strand': {}, 'Jc_fit': {}} # dM.Conductor #self.cablesParameters 

47 # self.wedge_elements = 

48 # self.model = 

49 self.coil = [] 

50 self.elements = {} 

51 self.coil_areas = [] 

52 self.wedge_areas = [] 

53 self.iron_yoke_areas = [] 

54 

55 

56 def build_magnet(self): 

57 """ 

58 This function builds a magnet and creates the different domains. 

59 

60 :return: None 

61 """ 

62 if self.verbose: print(f"SIGMA started generating {self.model_data.GeneralParameters.magnet_name}") 

63 self.air_ff_domain() 

64 self.air_domain() 

65 self.coil_domain() 

66 self.iron_yoke_domain() 

67 self.wedge_domain() 

68 return self.get_all_domains() 

69 # self.bh_curve() 

70 

71 def coil_domain(self): 

72 """ 

73 Function creates winding blocks domain 

74 :return: None 

75 """ 

76 poles = () 

77 for coil_nr, coil in self.roxie_data.coil.coils.items(): 

78 for pole_nr, pole in coil.poles.items(): 

79 windings = () 

80 for layer_nr, layer in pole.layers.items(): 

81 for winding_key, winding in layer.windings.items(): 

82 self.conductor_name = winding.conductor_name 

83 # self.cableParameters = self.roxie_data.conductor.conductor[winding.conductor_name].parameters 

84 self.cable_domain(conductor=winding.conductor_name) # Overwritten 

85 areas = () 

86 currents = () 

87 for block_key, block in winding.blocks.items(): 

88 currents += (block.current_sign,) 

89 # kp0 = self.g.Point.ofCartesian(coil.bore_center.x, coil.bore_center.y) 

90 bore = coil.bore_center 

91 if ( 

92 block.block_corners.iH.y > 0.0 and block.block_corners.oH.y > 0.0 and block.block_corners.iL.y > 0.0 and block.block_corners.oL.y > 0.0): # ? 

93 inner, outer = arcCenter(bore, block.block_corners.iH, block.block_corners.oH 

94 , block.block_corners.iL, block.block_corners.oL, 

95 diff_radius=None) 

96 else: 

97 inner, outer = arcCenter(bore, block.block_corners.iL, block.block_corners.oL, 

98 block.block_corners.iH, 

99 block.block_corners.oH, diff_radius=None) 

100 arg = [self.g.Point.ofCartesian(block.block_corners.iH.x, block.block_corners.iH.y), 

101 self.g.Point.ofCartesian(block.block_corners.iL.x, block.block_corners.iL.y), 

102 self.g.Point.ofCartesian(block.block_corners.oL.x, block.block_corners.oL.y), 

103 self.g.Point.ofCartesian(block.block_corners.oH.x, block.block_corners.oH.y)] 

104 kp0_inner = self.g.Point.ofCartesian(inner[0], inner[1]) 

105 kp0_outer = self.g.Point.ofCartesian(outer[0], outer[1]) 

106 areas += (self.g.Area.ofHyperLines( 

107 self.a.create_hyper_line_array(self.g.gateway, 

108 (self.g.Arc.ofEndPointsCenter(arg[1], arg[0], kp0_inner), 

109 self.g.Line.ofEndPoints(arg[1], arg[3]), 

110 self.g.Arc.ofEndPointsCenter(arg[2], arg[3], kp0_outer), 

111 self.g.Line.ofEndPoints(arg[0], arg[2])))),) 

112 # self.coil_areas += [areas[0], areas[1]] 

113 windings += (self.g.Winding.ofAreas(self.a.create_area_array(self.g.gateway, areas), 

114 self.a.create_int_array(self.g.gateway, currents), 

115 winding.conductors_number, winding.conductors_number, 

116 self.g.cable),) # Parse block_index and halfturn index 

117 

118 poles += (self.g.Pole.ofWindings(self.a.create_winding_array(self.g.gateway, windings)),) 

119 

120 self.coil = self.g.Coil.ofPoles(self.a.create_pole_array(self.g.gateway, poles)) 

121 

122 def cable_domain(self, conductor: str = None): 

123 """ 

124 Function sets cable characteristics and conductor properties. 

125 :return: None 

126 """ 

127 self.g.cable = self.g.Cable() 

128 self.g.cable.setLabel(conductor) 

129 self.g.cable.setTop(self.model_data.GeneralParameters.T_initial) 

130 i = 0 

131 # while conductor != self.input_conductor_params.Model_Data_GS.conductors[conductor]: 

132 # i += 1 

133 for entry in self.input_conductor_params.Model_Data_GS.conductors[conductor]: 

134 if entry[0] == 'cable' or entry[0] == 'strand' or entry[0] == 'Jc_fit': 

135 for key in entry[1]: 

136 self.cableParameters[entry[0]][key[0]] = key[1] if key[1] else 0. 

137 if self.cableParameters['cable']['type'] == "Ribbon": 

138 raise ValueError("SIGMA does not support Ribbon cables, please enter another magnet.") 

139 self.g.cable.setwInsulNarrow(self.cableParameters['cable']['th_insulation_along_height']) 

140 self.g.cable.setwInsulWide(self.cableParameters['cable']['th_insulation_along_width']) 

141 self.g.cable.setRc(self.cableParameters['cable']['Rc']) 

142 self.g.cable.setRa(self.cableParameters['cable']['Ra']) 

143 self.g.cable.setwBare(self.cableParameters['cable']['bare_cable_width']) 

144 self.g.cable.sethInBare(self.cableParameters['cable']['bare_cable_height_low']) 

145 self.g.cable.sethOutBare(self.cableParameters['cable']['bare_cable_height_high']) 

146 

147 self.g.cable.setNoOfStrands(self.cableParameters['cable']['n_strands']) 

148 self.g.cable.setNoOfStrandsPerLayer(self.cableParameters['cable']['n_strands_per_layers']) 

149 self.g.cable.setNoOfLayers(self.cableParameters['cable']['n_strand_layers']) 

150 

151 self.g.cable.setlTpStrand(self.cableParameters['cable']['strand_twist_pitch']) 

152 self.g.cable.setwCore(self.cableParameters['cable']['width_core']) 

153 self.g.cable.sethCore(self.cableParameters['cable']['height_core']) 

154 self.g.cable.setThetaTpStrand(self.cableParameters['cable']['strand_twist_pitch_angle']) 

155 self.g.cable.setFracFillInnerVoids(self.cableParameters['cable']['f_inner_voids']) 

156 self.g.cable.setFractFillOuterVoids(self.cableParameters['cable']['f_outer_voids']) 

157 

158 self.g.cable.setdFilament(self.cableParameters['strand']['filament_diameter']) 

159 self.g.cable.setDstrand(self.cableParameters['strand']['diameter']) 

160 self.g.cable.setfRhoEff(self.cableParameters['strand']['f_Rho_effective']) 

161 self.g.cable.setlTp(self.cableParameters['strand']['fil_twist_pitch']) 

162 self.g.cable.setRRR(self.cableParameters['strand']['RRR']) 

163 self.g.cable.setTupRRR(self.cableParameters['strand']['T_ref_RRR_high']) 

164 self.g.cable.setFracHe(0.) 

165 self.g.cable.setFracCu(self.cableParameters['strand']['Cu_noCu_in_strand'] / 

166 (1 + self.cableParameters['strand']['Cu_noCu_in_strand'])) 

167 self.g.cable.setFracSc(1 / (1 + self.cableParameters['strand']['Cu_noCu_in_strand'])) 

168 if self.cableParameters['Jc_fit']['type'][:4] == 'CUDI': 

169 self.g.cable.setC1(self.cableParameters['Jc_fit']['C1_' + self.cableParameters['Jc_fit']['type']]) 

170 self.g.cable.setC2(self.cableParameters['Jc_fit']['C2_' + self.cableParameters['Jc_fit']['type']]) 

171 else: 

172 self.g.cable.setC1(0.) 

173 self.g.cable.setC2(0.) 

174 

175 self.g.cable.setCriticalSurfaceFit(self.g.Cable.CriticalSurfaceFitEnum.Ic_NbTi_GSI) 

176 self.g.cable.setInsulationMaterial(self.g.MatDatabase.MAT_KAPTON) 

177 self.g.cable.setMaterialInnerVoids(self.g.MatDatabase.MAT_VOID) 

178 self.g.cable.setMaterialOuterVoids(self.g.MatDatabase.MAT_VOID) 

179 self.g.cable.setMaterialCore(self.g.MatDatabase.MAT_VOID) 

180 self.g.cable.setResitivityCopperFit(self.g.Cable.ResitivityCopperFitEnum.rho_Cu_CUDI) 

181 

182 def wedge_domain(self): 

183 """ 

184 Function creates inter-block wedges domain 

185 :return: None 

186 """ 

187 wedges = self.roxie_data.wedges 

188 

189 elements = [] 

190 for i in wedges: 

191 kp0_inner = self.g.Point.ofCartesian(wedges[i].corrected_center.inner.x, wedges[i].corrected_center.inner.y) 

192 kp0_outer = self.g.Point.ofCartesian(wedges[i].corrected_center.outer.x, wedges[i].corrected_center.outer.y) 

193 arg = [self.g.Point.ofCartesian(wedges[i].corners.iH.x, wedges[i].corners.iH.y), 

194 self.g.Point.ofCartesian(wedges[i].corners.iL.x, wedges[i].corners.iL.y), 

195 self.g.Point.ofCartesian(wedges[i].corners.oH.x, wedges[i].corners.oH.y), 

196 self.g.Point.ofCartesian(wedges[i].corners.oL.x, wedges[i].corners.oL.y)] 

197 

198 area = self.g.Area.ofHyperLines(self.a.create_hyper_line_array( 

199 self.g.gateway, (self.g.Arc.ofEndPointsCenter(arg[0], arg[1], kp0_inner), 

200 self.g.Line.ofEndPoints(arg[1], arg[3]), 

201 self.g.Arc.ofEndPointsCenter(arg[3], arg[2], kp0_outer), 

202 self.g.Line.ofEndPoints(arg[0], arg[2])))) 

203 self.wedge_areas += [area] 

204 

205 elements.append(self.g.Element(f"Wedge_El{i}", area)) 

206 

207 self.wedge_elements = self.a.create_element_array(self.g.gateway, tuple(elements)) 

208 

209 def mirrorXY(self, area): 

210 """ 

211 This function mirrors a SIGMA area object in x, y and xy. Returns all possible mirroring options. 

212 :param area: 

213 :return: area, ar2, ar2.mirrorX(), area.mirrorX() 

214 """ 

215 ar2 = area.mirrorY() 

216 return area, ar2, ar2.mirrorX(), area.mirrorX() 

217 

218 def air_ff_domain(self): 

219 """ 

220 Function creates air far field domain 

221 :return: None 

222 """ 

223 

224 iron = self.roxie_data.iron 

225 

226 kpc = self.g.Point.ofCartesian(0.0, 0.0) 

227 for i in iron.key_points: 

228 max_i = max(iron.key_points[i].x, iron.key_points[i].y) 

229 if max_i > self.max_r: 

230 self.max_r = max_i 

231 

232 kp1 = self.g.Point.ofCartesian(self.max_r * 2 * 0.95, 0.0) 

233 kp2 = self.g.Point.ofCartesian(0.0, self.max_r * 2 * 0.95) 

234 kp1_out = self.g.Point.ofCartesian(self.max_r * 2, 0.0) 

235 kp2_out = self.g.Point.ofCartesian(0.0, self.max_r * 2) 

236 ln1 = self.g.Line.ofEndPoints(kpc, kp1_out) 

237 ln2 = self.g.Arc.ofEndPointsCenter(kp1_out, kp2_out, kpc) 

238 ln3 = self.g.Line.ofEndPoints(kp2_out, kp2) 

239 ln4 = self.g.Arc.ofEndPointsCenter(kp2, kp1, kpc) 

240 

241 hyper_areas = self.mirrorXY(self.g.Area.ofHyperLines( 

242 self.a.create_hyper_line_array(self.g.gateway, tuple([ln1, ln2, ln3, ln4])))) 

243 

244 arg = [] # elements 

245 for i, ar in enumerate(hyper_areas): 

246 arg.append(self.g.Element(f"AFF_El{i}", ar)) 

247 

248 self.air_far_field = self.a.create_element_array(self.g.gateway, tuple(arg)) 

249 

250 def air_domain(self): 

251 """ 

252 Function creates air domain 

253 :return: None 

254 """ 

255 kpc = self.g.Point.ofCartesian(0.0, 0.0) 

256 

257 self.air = self.a.create_element_array(self.g.gateway, self.g.Element('Air', self.g.Area.ofHyperLines( 

258 self.a.create_hyper_line_array(self.g.gateway, 

259 self.g.Circumference.ofCenterRadius(kpc, self.max_r * 2 * 0.95))))) 

260 

261 def iron_yoke_domain(self): 

262 """ 

263 Function creates iron yoke domain 

264 :return: None 

265 """ 

266 iron = self.roxie_data.iron 

267 

268 keyPointsCOMSOL = {} 

269 hyperLinesCOMSOL = {} 

270 hyperAreasCOMSOL = {} 

271 

272 for i in iron.key_points: 

273 keyPointsCOMSOL[i] = self.g.Point.ofCartesian(iron.key_points[i].x, iron.key_points[i].y) 

274 

275 for i in iron.hyper_lines: 

276 if iron.hyper_lines[i].type == 'line': 

277 hyperLinesCOMSOL[i] = self.g.Line.ofEndPoints(keyPointsCOMSOL[iron.hyper_lines[i].kp1], 

278 keyPointsCOMSOL[iron.hyper_lines[i].kp2]) 

279 

280 elif iron.hyper_lines[i].type == 'arc': 

281 hyperLinesCOMSOL[i] = self.g.Arc.ofThreePoints(keyPointsCOMSOL[iron.hyper_lines[i].kp1], 

282 keyPointsCOMSOL[iron.hyper_lines[i].kp3], 

283 keyPointsCOMSOL[iron.hyper_lines[i].kp2]) 

284 

285 elif iron.hyper_lines[i].type == 'ellipticArc': 

286 hyperLinesCOMSOL[i] = self.g.EllipticArc.ofEndPointsAxes(keyPointsCOMSOL[iron.hyper_lines[i].kp1], 

287 keyPointsCOMSOL[iron.hyper_lines[i].kp2], 

288 iron.hyper_lines[i].arg1, 

289 iron.hyper_lines[i].arg2) 

290 

291 elif iron.hyper_lines[i].type == 'circle': 

292 hyperLinesCOMSOL[i] = self.g.Circumference.ofDiameterEndPoints(keyPointsCOMSOL[iron.hyper_lines[i].kp1], 

293 keyPointsCOMSOL[iron.hyper_lines[i].kp2]) 

294 else: 

295 raise ValueError('Hyper line {} not supported'.format(iron.hyper_lines[i].type)) 

296 

297 for i in iron.hyper_areas: 

298 arg = [hyperLinesCOMSOL[j] for j in iron.hyper_areas[i].lines] # lines for areas 

299 hyperAreasCOMSOL[i] = self.mirrorXY(self.g.Area.ofHyperLines( 

300 self.a.create_hyper_line_array(self.g.gateway, tuple(arg)))) 

301 

302 for i in hyperAreasCOMSOL: 

303 arg = [] # elements 

304 for j, ar in enumerate(hyperAreasCOMSOL[i]): 

305 arg.append(self.g.Element(f"IY{iron.hyper_areas[i].material[2:]}_{i}_El{j}", ar)) 

306 

307 self.elements[i] = self.a.create_element_array(self.g.gateway, tuple(arg)) 

308 

309 self.iron_yoke_areas += [ar] 

310 def get_all_domains(self): 

311 """ 

312 This function saves a Java file to be used in COMSOL simulation software. 

313 

314 :return: None 

315 """ 

316 domains = [self.g.AirFarFieldDomain("AFF", self.g.MatDatabase.MAT_AIR, self.air_far_field)] 

317 domains += [self.g.AirDomain("AIR", self.g.MatDatabase.MAT_AIR, self.air)] 

318 

319 orderedElements = list(self.elements) 

320 # change order of generated domains to override correctly 

321 

322 for hyper_hole_key, hyper_hole in self.roxie_data.iron.hyper_holes.items(): 

323 index = [orderedElements.index(hyper_hole.areas[0]), 

324 orderedElements.index(hyper_hole.areas[1])] 

325 if index[0] < index[1]: 

326 orderedElements.insert(index[1], orderedElements.pop(index[0])) 

327 

328 # MAT_AIR, MAT_COIL, MAT_COPPER, MAT_KAPTON, MAT_GLASSFIBER, MAT_INSULATION_TEST, MAT_STEEL, MAT_IRON1, 

329 # MAT_IRON2, MAT_VOID, MAT_NULL, MAT_COIL_TEST, MAT_G10 

330 self.iron_yoke = [ 

331 self.g.IronDomain(self.cfg, i, str(self.bh_curve_database), 

332 self.roxie_data.iron.hyper_areas[i].material, self.elements[i]) 

333 for i in orderedElements] # domains 

334 #print(self.iron_yoke) 

335 domains += self.iron_yoke 

336 domains.append(self.g.CoilDomain("CO", self.g.MatDatabase.MAT_COIL, self.coil)) 

337 domains.append(self.g.WedgeDomain("W", self.g.MatDatabase.MAT_COPPER, self.wedge_elements)) 

338 return domains