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
« 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/>.
16from steam_pysigma.utils.Utils import arcCenter
17import steam_pysigma.sigma.pysigma as ps
20class GeometryMultipole:
21 """
22 Class creates SIGMA domain objects
23 IMPROVMENTS:
24 No dependency on config sigma
25 """
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):
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 = []
56 def build_magnet(self):
57 """
58 This function builds a magnet and creates the different domains.
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()
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
118 poles += (self.g.Pole.ofWindings(self.a.create_winding_array(self.g.gateway, windings)),)
120 self.coil = self.g.Coil.ofPoles(self.a.create_pole_array(self.g.gateway, poles))
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'])
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'])
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'])
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.)
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)
182 def wedge_domain(self):
183 """
184 Function creates inter-block wedges domain
185 :return: None
186 """
187 wedges = self.roxie_data.wedges
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)]
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]
205 elements.append(self.g.Element(f"Wedge_El{i}", area))
207 self.wedge_elements = self.a.create_element_array(self.g.gateway, tuple(elements))
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()
218 def air_ff_domain(self):
219 """
220 Function creates air far field domain
221 :return: None
222 """
224 iron = self.roxie_data.iron
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
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)
241 hyper_areas = self.mirrorXY(self.g.Area.ofHyperLines(
242 self.a.create_hyper_line_array(self.g.gateway, tuple([ln1, ln2, ln3, ln4]))))
244 arg = [] # elements
245 for i, ar in enumerate(hyper_areas):
246 arg.append(self.g.Element(f"AFF_El{i}", ar))
248 self.air_far_field = self.a.create_element_array(self.g.gateway, tuple(arg))
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)
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)))))
261 def iron_yoke_domain(self):
262 """
263 Function creates iron yoke domain
264 :return: None
265 """
266 iron = self.roxie_data.iron
268 keyPointsCOMSOL = {}
269 hyperLinesCOMSOL = {}
270 hyperAreasCOMSOL = {}
272 for i in iron.key_points:
273 keyPointsCOMSOL[i] = self.g.Point.ofCartesian(iron.key_points[i].x, iron.key_points[i].y)
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])
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])
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)
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))
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))))
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))
307 self.elements[i] = self.a.create_element_array(self.g.gateway, tuple(arg))
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.
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)]
319 orderedElements = list(self.elements)
320 # change order of generated domains to override correctly
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]))
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