fiqus 2024.7.0__py3-none-any.whl → 2024.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fiqus/MainFiQuS.py +290 -134
- fiqus/data/DataConductor.py +301 -301
- fiqus/data/DataFiQuS.py +128 -84
- fiqus/data/DataFiQuSCCT.py +150 -150
- fiqus/data/DataFiQuSConductor.py +84 -84
- fiqus/data/DataFiQuSConductorAC_Strand.py +565 -565
- fiqus/data/DataFiQuSMultipole.py +716 -42
- fiqus/data/DataFiQuSPancake3D.py +737 -278
- fiqus/data/DataMultipole.py +180 -15
- fiqus/data/DataRoxieParser.py +90 -51
- fiqus/data/DataSettings.py +121 -0
- fiqus/data/DataWindingsCCT.py +37 -37
- fiqus/data/RegionsModelFiQuS.py +18 -6
- fiqus/geom_generators/GeometryCCT.py +905 -905
- fiqus/geom_generators/GeometryConductorAC_Strand.py +1391 -1391
- fiqus/geom_generators/GeometryMultipole.py +1827 -227
- fiqus/geom_generators/GeometryPancake3D.py +316 -117
- fiqus/geom_generators/GeometryPancake3DUtils.py +549 -0
- fiqus/getdp_runners/RunGetdpCCT.py +4 -4
- fiqus/getdp_runners/RunGetdpConductorAC_Strand.py +201 -201
- fiqus/getdp_runners/RunGetdpMultipole.py +115 -42
- fiqus/getdp_runners/RunGetdpPancake3D.py +28 -6
- fiqus/mains/MainCCT.py +2 -2
- fiqus/mains/MainConductorAC_Strand.py +132 -132
- fiqus/mains/MainMultipole.py +113 -62
- fiqus/mains/MainPancake3D.py +63 -23
- fiqus/mesh_generators/MeshCCT.py +209 -209
- fiqus/mesh_generators/MeshConductorAC_Strand.py +656 -656
- fiqus/mesh_generators/MeshMultipole.py +1243 -181
- fiqus/mesh_generators/MeshPancake3D.py +275 -192
- fiqus/parsers/ParserCOND.py +825 -0
- fiqus/parsers/ParserDAT.py +16 -16
- fiqus/parsers/ParserGetDPOnSection.py +212 -212
- fiqus/parsers/ParserGetDPTimeTable.py +134 -134
- fiqus/parsers/ParserMSH.py +53 -53
- fiqus/parsers/ParserPOS.py +214 -214
- fiqus/parsers/ParserRES.py +142 -142
- fiqus/plotters/PlotPythonCCT.py +133 -133
- fiqus/plotters/PlotPythonConductorAC.py +855 -855
- fiqus/plotters/PlotPythonMultipole.py +18 -18
- fiqus/post_processors/PostProcessCCT.py +440 -440
- fiqus/post_processors/PostProcessConductorAC.py +49 -49
- fiqus/post_processors/PostProcessMultipole.py +353 -229
- fiqus/post_processors/PostProcessPancake3D.py +8 -13
- fiqus/pre_processors/PreProcessCCT.py +175 -175
- fiqus/pro_assemblers/ProAssembler.py +14 -6
- fiqus/pro_material_functions/ironBHcurves.pro +246 -246
- fiqus/pro_templates/combined/CCT_template.pro +274 -274
- fiqus/pro_templates/combined/ConductorAC_template.pro +1025 -1025
- fiqus/pro_templates/combined/Multipole_template.pro +1694 -126
- fiqus/pro_templates/combined/Pancake3D_template.pro +2294 -1103
- fiqus/pro_templates/combined/TSA_materials.pro +162 -0
- fiqus/pro_templates/combined/materials.pro +36 -18
- fiqus/utils/Utils.py +508 -110
- fiqus/utils/update_data_settings.py +33 -0
- fiqus-2024.12.1.dist-info/METADATA +132 -0
- fiqus-2024.12.1.dist-info/RECORD +84 -0
- {fiqus-2024.7.0.dist-info → fiqus-2024.12.1.dist-info}/WHEEL +1 -1
- tests/test_FiQuS.py +1 -1
- tests/test_geometry_generators.py +101 -2
- tests/test_mesh_generators.py +154 -1
- tests/test_solvers.py +115 -21
- tests/utils/fiqus_test_classes.py +85 -21
- tests/utils/generate_reference_files_ConductorAC.py +57 -57
- tests/utils/generate_reference_files_Pancake3D.py +4 -5
- tests/utils/helpers.py +97 -97
- fiqus-2024.7.0.dist-info/METADATA +0 -103
- fiqus-2024.7.0.dist-info/RECORD +0 -79
- {fiqus-2024.7.0.dist-info → fiqus-2024.12.1.dist-info}/top_level.txt +0 -0
|
@@ -1,905 +1,905 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import copy
|
|
3
|
-
import math
|
|
4
|
-
import collections
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import timeit
|
|
9
|
-
import numpy as np
|
|
10
|
-
import gmsh
|
|
11
|
-
|
|
12
|
-
from fiqus.data import RegionsModelFiQuS as Reg_Mod_FiQ
|
|
13
|
-
from fiqus.data.DataWindingsCCT import WindingsInformation # for winding information
|
|
14
|
-
from fiqus.utils.Utils import FilesAndFolders as uff
|
|
15
|
-
from fiqus.utils.Utils import GmshUtils
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
class Hexahedrons:
|
|
19
|
-
|
|
20
|
-
def __init__(self):
|
|
21
|
-
"""
|
|
22
|
-
Points generator for CCT windings to give hexahedron volumes for hexahedron meshing in Gmsh.
|
|
23
|
-
"""
|
|
24
|
-
self.vertices_to_surf = [[0, 4, 7, 3], [1, 5, 6, 2], [1, 5, 4, 0], [2, 6, 7, 3], [0, 1, 2, 3], [4, 5, 6, 7]]
|
|
25
|
-
# Node ordering following Gmsh for hexahedron https://gmsh.info/doc/texinfo/gmsh.html#Legacy-formats --> node ordering
|
|
26
|
-
# above _vertices_to_surf is only used for manipulation of the hexahedra points on the data model level. Gmsh uses points connections and line connections for creating surfaces
|
|
27
|
-
|
|
28
|
-
self.hexes = {}
|
|
29
|
-
self._corner_points = {}
|
|
30
|
-
self._sur_names = ['x-', 'x+', 'y-', 'y+', 'z-', 'z+']
|
|
31
|
-
self._op_sign = {'+': '-', '-': '+'}
|
|
32
|
-
self._sign = {1: '+', -1: '-'}
|
|
33
|
-
|
|
34
|
-
def _add_corner_points(self, corner_num, x, y, z, ct):
|
|
35
|
-
self._corner_points[corner_num] = {'x': x, 'y': y, 'z': z, 'ct': ct} # ct is channel turn
|
|
36
|
-
|
|
37
|
-
def _generate_hexes(self):
|
|
38
|
-
for h in range(len(self._corner_points[0]['x'])):
|
|
39
|
-
self.hexes[h] = {}
|
|
40
|
-
for p_num, p_coor in self._corner_points.items():
|
|
41
|
-
self.hexes[h][p_num] = {}
|
|
42
|
-
for coor in ['x', 'y', 'z']:
|
|
43
|
-
self.hexes[h][p_num][coor] = p_coor[coor][h]
|
|
44
|
-
self.hexes[h]['ct'] = p_coor['ct'][h]
|
|
45
|
-
return p_coor['ct'][h]
|
|
46
|
-
|
|
47
|
-
def _add_elem_to_elem(self, start_elem, dir_str, dist):
|
|
48
|
-
"""
|
|
49
|
-
:param start_elem: idx of start element
|
|
50
|
-
:param dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
51
|
-
:param dist: length of new element in the direction specified above [m]
|
|
52
|
-
:return: index of created element
|
|
53
|
-
"""
|
|
54
|
-
op_dir = dir_str[0] + self._op_sign[dir_str[1]]
|
|
55
|
-
new_elem = start_elem + math.copysign(1, start_elem)
|
|
56
|
-
self.hexes[new_elem] = {}
|
|
57
|
-
source_points = self.vertices_to_surf[self._sur_names.index(dir_str)]
|
|
58
|
-
destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)]
|
|
59
|
-
for d_p, s_p in zip(destination_points + source_points, source_points + source_points):
|
|
60
|
-
self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[start_elem][s_p])
|
|
61
|
-
for s_p in source_points: # modif_points:
|
|
62
|
-
self.hexes[new_elem][s_p][dir_str[0]] = self.hexes[new_elem][s_p][dir_str[0]] + dist
|
|
63
|
-
self.hexes[new_elem]['ct'] = self.hexes[start_elem]['ct']
|
|
64
|
-
return new_elem
|
|
65
|
-
|
|
66
|
-
def _reorder_and_renumber_hexes(self):
|
|
67
|
-
self.hexes = collections.OrderedDict(sorted(self.hexes.items()))
|
|
68
|
-
first_hex_num = list(self.hexes.keys())[0]
|
|
69
|
-
temp_dict = {}
|
|
70
|
-
for h_num, h_dict in self.hexes.items():
|
|
71
|
-
temp_dict[h_num - first_hex_num + 1] = h_dict
|
|
72
|
-
self.hexes = temp_dict
|
|
73
|
-
for h_num, h_dict in self.hexes.items():
|
|
74
|
-
turn = h_dict.pop('ct', None)
|
|
75
|
-
self.hexes[h_num] = collections.OrderedDict(sorted(h_dict.items()))
|
|
76
|
-
self.hexes[h_num]['ct'] = turn
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
class Winding(Hexahedrons):
|
|
80
|
-
|
|
81
|
-
def __init__(self, cctdm, layer, post_proc=False):
|
|
82
|
-
|
|
83
|
-
super(Winding, self).__init__()
|
|
84
|
-
self.cctdm = cctdm
|
|
85
|
-
self.layer = layer
|
|
86
|
-
self.post_proc = post_proc
|
|
87
|
-
self.z_corr = 0
|
|
88
|
-
self._populate_corners()
|
|
89
|
-
self.z_corr = self._center_z_and_offset()
|
|
90
|
-
self._generate_hexes()
|
|
91
|
-
|
|
92
|
-
def _populate_corners(self):
|
|
93
|
-
r"""
|
|
94
|
-
Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn.
|
|
95
|
-
y (v)
|
|
96
|
-
3----------2
|
|
97
|
-
|\ ^ |\
|
|
98
|
-
| \ | | \
|
|
99
|
-
| \ | | \
|
|
100
|
-
| 7------+---6
|
|
101
|
-
| | +-- |-- | -> x (u)
|
|
102
|
-
0---+---\--1 |
|
|
103
|
-
\ | \ \ |
|
|
104
|
-
\ | \ \ |
|
|
105
|
-
\| z(w)\|
|
|
106
|
-
4----------5
|
|
107
|
-
|
|
108
|
-
:return: none
|
|
109
|
-
"""
|
|
110
|
-
self._sign_of_rot = math.copysign(1, self.cctdm.geometry.windings.alphas[self.layer])
|
|
111
|
-
if self._sign_of_rot > 0:
|
|
112
|
-
corner_seq = {
|
|
113
|
-
0: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf'
|
|
114
|
-
1: {'w_s': -1, 'h_s': 1, 'far': False}, # 'brcf'
|
|
115
|
-
2: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf'
|
|
116
|
-
3: {'w_s': 1, 'h_s': -1, 'far': False}, # 'tlcf'
|
|
117
|
-
4: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc'
|
|
118
|
-
5: {'w_s': -1, 'h_s': 1, 'far': True}, # bottom right corner close #'brcc'
|
|
119
|
-
6: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
120
|
-
7: {'w_s': 1, 'h_s': -1, 'far': True}, # top right corner close #'tlcc'
|
|
121
|
-
}
|
|
122
|
-
else:
|
|
123
|
-
corner_seq = {
|
|
124
|
-
0: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf'
|
|
125
|
-
1: {'w_s': 1, 'h_s': -1, 'far': False}, # 'brcf'
|
|
126
|
-
2: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf'
|
|
127
|
-
3: {'w_s': -1, 'h_s': 1, 'far': False}, # 'tlcf'
|
|
128
|
-
4: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
129
|
-
5: {'w_s': 1, 'h_s': -1, 'far': True}, # bottom right corner close #'brcc'
|
|
130
|
-
6: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc'
|
|
131
|
-
7: {'w_s': -1, 'h_s': 1, 'far': True}, # top left corner close #'tlcc'
|
|
132
|
-
}
|
|
133
|
-
for corner_num, corner_dict in corner_seq.items():
|
|
134
|
-
self._add_corner_points(corner_num, *self.calc_turns_corner_coords(corner_dict['w_s'], corner_dict['h_s'], far=corner_dict['far']))
|
|
135
|
-
|
|
136
|
-
def calc_turns_corner_coords(self, w_s=1, h_s=1, far=False):
|
|
137
|
-
"""
|
|
138
|
-
Based on https://doi.org/10.1016/j.cryogenics.2020.103041
|
|
139
|
-
:param h_s: wire height sign
|
|
140
|
-
:param w_s: wire width sign
|
|
141
|
-
:param far: for far surface set to True, for close surface set to False. This refers to walls of hexagon.
|
|
142
|
-
:return:
|
|
143
|
-
"""
|
|
144
|
-
r_wm = self.cctdm.geometry.windings.r_wms[self.layer] # radius of winding layer, in the middle of the conductor groove [m]
|
|
145
|
-
w_i = self.cctdm.geometry.windings.lps[self.layer] # layer pitch [m]
|
|
146
|
-
n_turns = self.cctdm.geometry.windings.n_turnss[self.layer] # number of turns [-]
|
|
147
|
-
nept = self.cctdm.geometry.windings.ndpts[self.layer] - 1 # number of elements per turn [-]
|
|
148
|
-
alpha = self.cctdm.geometry.windings.alphas[self.layer] * np.pi / 180 # inclination of the winding [deg]
|
|
149
|
-
theta_0 = 0 # offset start at theta_0, i.e. angular position of the beginning of the 1st turn[deg]
|
|
150
|
-
z_0 = 0 # offset start at z_0, i.e. axial position of the beginning of the 1st turn [m]
|
|
151
|
-
hh = 0.5 * h_s * self.cctdm.geometry.windings.wwhs[self.layer] # half of h (groove height)
|
|
152
|
-
hw = 0.5 * w_s * self.cctdm.geometry.windings.wwws[self.layer] # half of w (groove width)
|
|
153
|
-
tot_angle = 2 * np.pi * (n_turns + 1 / nept)
|
|
154
|
-
tot_points = int(nept * n_turns) + 1
|
|
155
|
-
if alpha > 0:
|
|
156
|
-
theta = np.linspace(0, tot_angle, tot_points, endpoint=False)
|
|
157
|
-
else:
|
|
158
|
-
theta = np.linspace(0 + np.pi, tot_angle + np.pi, tot_points, endpoint=False) # this rotates terminals for opposite inclination to be on the opposite side
|
|
159
|
-
if far:
|
|
160
|
-
theta = theta[1:] # give all points but the first one, so the far surface can be created
|
|
161
|
-
else:
|
|
162
|
-
theta = theta[:-1] # give all the points but the last one so a near surface can be created
|
|
163
|
-
if theta.size < 2:
|
|
164
|
-
raise ValueError(f'Combination of number of division points per turn (ndpts) and number of turns (n_turnss) must result in at least 2 Hexahedrons, but inputs result in only {theta.size}!')
|
|
165
|
-
C = r_wm * np.arctan(alpha) * np.cos(theta - theta_0) + w_i / (2 * np.pi)
|
|
166
|
-
D = np.sqrt(r_wm ** 2 + np.square(C))
|
|
167
|
-
# -- x, y, z points coordinates
|
|
168
|
-
xs = (r_wm + hh) * np.cos(theta) - np.sin(theta) * hw * C / D
|
|
169
|
-
ys = (r_wm + hh) * np.sin(theta) + np.cos(theta) * hw * C / D
|
|
170
|
-
zs = r_wm * np.sin(theta - theta_0) / np.tan(alpha) + w_i * (theta - theta_0) / (2 * np.pi) + z_0 - hw * r_wm / D # original formula
|
|
171
|
-
if alpha > 0:
|
|
172
|
-
channel_turn = np.ceil((theta - 2 * np.pi / (nept + 1)) / (2 * np.pi))
|
|
173
|
-
# channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn.
|
|
174
|
-
else:
|
|
175
|
-
channel_turn = np.ceil((theta - np.pi - 2 * np.pi / (nept + 1)) / (2 * np.pi))
|
|
176
|
-
channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn.
|
|
177
|
-
return xs.tolist(), ys.tolist(), zs.tolist(), list(map(int, channel_turn))
|
|
178
|
-
|
|
179
|
-
def _center_z_and_offset(self, offset=0):
|
|
180
|
-
z_mins = []
|
|
181
|
-
z_maxs = []
|
|
182
|
-
for c_val in self._corner_points.values():
|
|
183
|
-
z_mins.append(np.min(c_val['z']))
|
|
184
|
-
z_maxs.append(np.max(c_val['z']))
|
|
185
|
-
z_min_wind = np.min(np.array(z_mins))
|
|
186
|
-
z_max_wind = np.max(np.array(z_maxs))
|
|
187
|
-
z_centre = (z_min_wind + z_max_wind) / 2
|
|
188
|
-
if not self.post_proc:
|
|
189
|
-
for c_key, c_val in self._corner_points.items():
|
|
190
|
-
self._corner_points[c_key]['z'] = (c_val['z'] - z_centre + offset).tolist()
|
|
191
|
-
z_mins = []
|
|
192
|
-
z_maxs = []
|
|
193
|
-
for c_val in self._corner_points.values():
|
|
194
|
-
z_mins.append(np.min(c_val['z']))
|
|
195
|
-
z_maxs.append(np.max(c_val['z']))
|
|
196
|
-
self.z_min_winding_layer = np.min(np.array(z_mins))
|
|
197
|
-
self.z_max_winding_layer = np.max(np.array(z_maxs))
|
|
198
|
-
return z_centre + offset
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
class FQPL(Hexahedrons):
|
|
202
|
-
def __init__(self, cctdm, layer):
|
|
203
|
-
"""
|
|
204
|
-
FQPL hex generator
|
|
205
|
-
:param cctdm: cct data model object parsed from yaml input file
|
|
206
|
-
:param layer: loop number, integer
|
|
207
|
-
"""
|
|
208
|
-
super(FQPL, self).__init__()
|
|
209
|
-
self.cctdm = cctdm
|
|
210
|
-
self.layer = layer
|
|
211
|
-
self._calc_z_unit_len()
|
|
212
|
-
self._populate_corners()
|
|
213
|
-
self._generate_hexes()
|
|
214
|
-
self._generate_fqpl()
|
|
215
|
-
self._rotate_fqpl()
|
|
216
|
-
self._reorder_and_renumber_hexes()
|
|
217
|
-
self._clean_attr()
|
|
218
|
-
|
|
219
|
-
def _calc_z_unit_len(self):
|
|
220
|
-
if self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_min':
|
|
221
|
-
self.near = self.cctdm.geometry.air.z_min
|
|
222
|
-
self.z_unit_len = (self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer]
|
|
223
|
-
|
|
224
|
-
elif self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_max':
|
|
225
|
-
self.near = self.cctdm.geometry.air.z_max
|
|
226
|
-
self.z_unit_len = (self.near - self.cctdm.geometry.fqpls.z_ends[self.layer] - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer]
|
|
227
|
-
else:
|
|
228
|
-
raise ValueError(f'fqpl.z_starts parameter must be a string equal to z_min or z_max, but {self.cctdm.geometry.fqpls.z_starts[self.layer]} was given!')
|
|
229
|
-
|
|
230
|
-
self.extrusion_sign = math.copysign(1, self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near)
|
|
231
|
-
|
|
232
|
-
def _calc_first_corner_coords(self, w_s, h_s, far, offset):
|
|
233
|
-
x = [self.cctdm.geometry.fqpls.r_ins[self.layer] + self.cctdm.geometry.fqpls.fwhs[self.layer] * h_s] # not rotated coordinate
|
|
234
|
-
y = [self.cctdm.geometry.fqpls.fwws[self.layer] * w_s] # not rotated coordinate
|
|
235
|
-
if far:
|
|
236
|
-
z = [self.near - self.z_unit_len + offset]
|
|
237
|
-
else:
|
|
238
|
-
z = [self.near + offset]
|
|
239
|
-
return x, y, z, [1]
|
|
240
|
-
|
|
241
|
-
def _populate_corners(self):
|
|
242
|
-
r"""
|
|
243
|
-
Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn.
|
|
244
|
-
y (v)
|
|
245
|
-
3----------2
|
|
246
|
-
|\ ^ |\
|
|
247
|
-
| \ | | \
|
|
248
|
-
| \ | | \
|
|
249
|
-
| 7------+---6
|
|
250
|
-
| | +-- |-- | -> x (u)
|
|
251
|
-
0---+---\--1 |
|
|
252
|
-
\ | \ \ |
|
|
253
|
-
\ | \ \ |
|
|
254
|
-
\| z(w)\|
|
|
255
|
-
4----------5
|
|
256
|
-
|
|
257
|
-
:return: none
|
|
258
|
-
"""
|
|
259
|
-
if self.extrusion_sign > 0:
|
|
260
|
-
offset = self.z_unit_len
|
|
261
|
-
corner_seq = {
|
|
262
|
-
0: {'w_s': -0.5, 'h_s': 0, 'far': True}, # 'blcf'
|
|
263
|
-
1: {'w_s': -0.5, 'h_s': 1, 'far': True}, # 'tlcf'
|
|
264
|
-
2: {'w_s': 0.5, 'h_s': 1, 'far': True}, # 'trcf'
|
|
265
|
-
3: {'w_s': 0.5, 'h_s': 0, 'far': True}, # 'brcf'
|
|
266
|
-
4: {'w_s': -0.5, 'h_s': 0, 'far': False}, # bottom left corner close #'blcc'
|
|
267
|
-
5: {'w_s': -0.5, 'h_s': 1, 'far': False}, # top left corner close #'tlcc'
|
|
268
|
-
6: {'w_s': 0.5, 'h_s': 1, 'far': False}, # top right corner close #'trcc'
|
|
269
|
-
7: {'w_s': 0.5, 'h_s': 0, 'far': False}, # bottom right corner close #'brcc'
|
|
270
|
-
}
|
|
271
|
-
else:
|
|
272
|
-
offset = 0
|
|
273
|
-
corner_seq = {
|
|
274
|
-
0: {'w_s': -0.5, 'h_s': 0, 'far': False}, # 'blcf'
|
|
275
|
-
1: {'w_s': -0.5, 'h_s': 1, 'far': False}, # 'tlcf'
|
|
276
|
-
2: {'w_s': 0.5, 'h_s': 1, 'far': False}, # 'trcf'
|
|
277
|
-
3: {'w_s': 0.5, 'h_s': 0, 'far': False}, # 'brcf'
|
|
278
|
-
4: {'w_s': -0.5, 'h_s': 0, 'far': True}, # bottom left corner close #'blcc'
|
|
279
|
-
5: {'w_s': -0.5, 'h_s': 1, 'far': True}, # top left corner close #'tlcc'
|
|
280
|
-
6: {'w_s': 0.5, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
281
|
-
7: {'w_s': 0.5, 'h_s': 0, 'far': True}, # bottom right corner close #'brcc'
|
|
282
|
-
}
|
|
283
|
-
for corner_num, corner_dict in corner_seq.items():
|
|
284
|
-
self._add_corner_points(corner_num, *self._calc_first_corner_coords(corner_dict['w_s'], corner_dict['h_s'], corner_dict['far'], offset))
|
|
285
|
-
|
|
286
|
-
def _get_ref_point(self, elem_number, surf_n_dir_str, coord, dist):
|
|
287
|
-
"""
|
|
288
|
-
Gets center point for a surface 'pointing' in the direction surf_n_dir_str of hex with elem_number. It then moves that point into along coordinate coord and by distance dist
|
|
289
|
-
:param elem_number: hex number to take the surface from
|
|
290
|
-
:param surf_n_dir_str: 'direction of surface' (kind of normal), options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
291
|
-
:param coord: direction to more the point in, options are: 'x', 'y', 'z'
|
|
292
|
-
:param dist: distance to move the point, this is positive or negative float
|
|
293
|
-
:return:
|
|
294
|
-
"""
|
|
295
|
-
source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)] # get the indexes 4 points for the surface points in direction surf_n_dir_str
|
|
296
|
-
points_coor = [self.hexes[elem_number][point] for point in source_points] # get the points, they contain coordinates
|
|
297
|
-
xs = []
|
|
298
|
-
ys = []
|
|
299
|
-
zs = []
|
|
300
|
-
for point in points_coor: # get coordinates into list for each axis
|
|
301
|
-
for cor_list, coor in zip([xs, ys, zs], ['x', 'y', 'z']):
|
|
302
|
-
cor_list.append(point[coor])
|
|
303
|
-
p = {'x': np.mean(xs), 'y': np.mean(ys), 'z': np.mean(zs)} # get averages of each axis coordinate, giving the center of the surface
|
|
304
|
-
p[coord] = p[coord] + dist
|
|
305
|
-
return p
|
|
306
|
-
|
|
307
|
-
def _add_elem_with_rot(self, elem_number, surf_n_dir_str, add_ax, angle, ref_p_coor):
|
|
308
|
-
"""
|
|
309
|
-
:param elem_number: idx of start element
|
|
310
|
-
:param surf_n_dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
311
|
-
:param ref_p_coor: length of new element in the direction specified above [m]
|
|
312
|
-
:return: index of created element
|
|
313
|
-
"""
|
|
314
|
-
mod_coor = [surf_n_dir_str[0], add_ax]
|
|
315
|
-
# mod_coor.remove(surf_n_dir_str[0]) # get the other two coordinates that arenet the direction of the surface to take
|
|
316
|
-
|
|
317
|
-
angle_rad = np.deg2rad(angle)
|
|
318
|
-
op_dir = surf_n_dir_str[0] + self._op_sign[surf_n_dir_str[1]]
|
|
319
|
-
new_elem = elem_number + math.copysign(1, elem_number)
|
|
320
|
-
self.hexes[new_elem] = {}
|
|
321
|
-
source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)]
|
|
322
|
-
destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)]
|
|
323
|
-
for d_p, s_p in zip(destination_points + source_points, source_points + source_points):
|
|
324
|
-
self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[elem_number][s_p])
|
|
325
|
-
for s_p in source_points: # modif_points:
|
|
326
|
-
v1 = self.hexes[new_elem][s_p][mod_coor[0]]
|
|
327
|
-
v2 = self.hexes[new_elem][s_p][mod_coor[1]]
|
|
328
|
-
v1_ofset = ref_p_coor[mod_coor[0]]
|
|
329
|
-
v2_ofset = ref_p_coor[mod_coor[1]]
|
|
330
|
-
v1 = v1 - v1_ofset
|
|
331
|
-
v2 = v2 - v2_ofset
|
|
332
|
-
v1_new = np.cos(angle_rad) * v1 - np.sin(angle_rad) * v2
|
|
333
|
-
v2_new = np.sin(angle_rad) * v1 + np.cos(angle_rad) * v2
|
|
334
|
-
v1 = v1_new + v1_ofset
|
|
335
|
-
v2 = v2_new + v2_ofset
|
|
336
|
-
self.hexes[new_elem][s_p][mod_coor[0]] = v1
|
|
337
|
-
self.hexes[new_elem][s_p][mod_coor[1]] = v2
|
|
338
|
-
self.hexes[new_elem]['ct'] = self.hexes[elem_number]['ct']
|
|
339
|
-
return new_elem
|
|
340
|
-
|
|
341
|
-
def _generate_fqpl(self):
|
|
342
|
-
new_elem = self._add_elem_to_elem(0, 'z+', self.extrusion_sign * self.z_unit_len)
|
|
343
|
-
for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer] - 2):
|
|
344
|
-
new_elem = self._add_elem_to_elem(new_elem, 'z+', self.extrusion_sign * self.z_unit_len)
|
|
345
|
-
coord = 'x'
|
|
346
|
-
ref_point = self._get_ref_point(new_elem, 'z+', coord, self.cctdm.geometry.fqpls.r_bs[self.layer]) # centre of rotation pint.
|
|
347
|
-
n_seg = 10
|
|
348
|
-
angle = 180 / n_seg
|
|
349
|
-
for _ in range(n_seg):
|
|
350
|
-
new_elem = self._add_elem_with_rot(new_elem, 'z+', coord, self.extrusion_sign * angle, ref_point)
|
|
351
|
-
for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer]):
|
|
352
|
-
new_elem = self._add_elem_to_elem(new_elem, 'z+', -self.extrusion_sign * self.z_unit_len)
|
|
353
|
-
|
|
354
|
-
def _rotate_fqpl(self):
|
|
355
|
-
for h_num, h_dict in self.hexes.items():
|
|
356
|
-
channel_turn = h_dict.pop('ct', None)
|
|
357
|
-
for p_num, p_dict in h_dict.items():
|
|
358
|
-
xx = p_dict['x']
|
|
359
|
-
yy = p_dict['y']
|
|
360
|
-
x = xx * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) - yy * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate
|
|
361
|
-
y = xx * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) + yy * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate
|
|
362
|
-
self.hexes[h_num][p_num]['x'] = x
|
|
363
|
-
self.hexes[h_num][p_num]['y'] = y
|
|
364
|
-
self.hexes[h_num]['ct'] = channel_turn
|
|
365
|
-
|
|
366
|
-
def _clean_attr(self):
|
|
367
|
-
del self.cctdm
|
|
368
|
-
del self.layer
|
|
369
|
-
del self.near
|
|
370
|
-
del self.z_unit_len
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
class Generate_BREPs:
|
|
374
|
-
def __init__(self, fdm, verbose=True):
|
|
375
|
-
"""
|
|
376
|
-
Class to generate (build) Windings and Formers (WF) of a canted cosine theta (CCT) magnet and save them to brep files
|
|
377
|
-
:param fdm: fiqus data model
|
|
378
|
-
:param verbose: If True more information is printed in python console.
|
|
379
|
-
"""
|
|
380
|
-
self.cctdm = fdm.magnet
|
|
381
|
-
self.model_folder = os.path.join(os.getcwd())
|
|
382
|
-
self.magnet_name = fdm.general.magnet_name
|
|
383
|
-
|
|
384
|
-
self.verbose = verbose
|
|
385
|
-
self.cctwi = WindingsInformation()
|
|
386
|
-
self.cctwi.magnet_name = self.magnet_name
|
|
387
|
-
self.transl_dict = {'vol': '', 'surf': '_bd', 'surf_in': '_in', 'surf_out': '_out', 'cochain': '_cut'}
|
|
388
|
-
self.gu = GmshUtils(self.model_folder, self.verbose)
|
|
389
|
-
self.gu.initialize()
|
|
390
|
-
self.formers = []
|
|
391
|
-
self.air = []
|
|
392
|
-
gmsh.option.setString('Geometry.OCCTargetUnit', 'M')
|
|
393
|
-
|
|
394
|
-
def generate_windings_or_fqpls(self, type_str):
|
|
395
|
-
"""
|
|
396
|
-
Generates windings brep files, as many as number of entries in the yaml file for windings.
|
|
397
|
-
:param type_str: What to generate, options are: 'windings' or 'fqpls'
|
|
398
|
-
:return: None, saves brep to disk
|
|
399
|
-
"""
|
|
400
|
-
if self.verbose:
|
|
401
|
-
print(f'Generating {type_str} started')
|
|
402
|
-
start_time = timeit.default_timer()
|
|
403
|
-
|
|
404
|
-
def generate_geom(worf_in, names_in):
|
|
405
|
-
worf_tags_dict_in = {}
|
|
406
|
-
for worf_num, worf_in in enumerate(worf_in):
|
|
407
|
-
worf_tags_dict_in[worf_num] = self.generate_hex_geom(worf_in)
|
|
408
|
-
gmsh.model.occ.synchronize()
|
|
409
|
-
gmsh.write(os.path.join(self.model_folder, f'{names_in[worf_num]}.brep'))
|
|
410
|
-
gmsh.clear()
|
|
411
|
-
return worf_tags_dict_in
|
|
412
|
-
|
|
413
|
-
if self.cctdm.geometry.windings.names and type_str == 'windings':
|
|
414
|
-
worf = [Winding(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.windings.names)] # worf = winding or fqpl
|
|
415
|
-
names = [name for name in self.cctdm.geometry.windings.names]
|
|
416
|
-
for w in worf:
|
|
417
|
-
if self.cctdm.geometry.air.z_min > w.z_min_winding_layer or w.z_max_winding_layer > self.cctdm.geometry.air.z_max:
|
|
418
|
-
raise ValueError(f'{self.cctdm.geometry.air.name} region dimensions are too s mall to fit the windings turns. Please extend z_min and/or z_max')
|
|
419
|
-
else:
|
|
420
|
-
pass
|
|
421
|
-
worf_tags_dict = generate_geom(worf, names)
|
|
422
|
-
|
|
423
|
-
if self.cctdm.geometry.fqpls.names and type_str == 'fqpls':
|
|
424
|
-
worf = [FQPL(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.fqpls.names)] # worf = winding or fqpl
|
|
425
|
-
names = [name for name in self.cctdm.geometry.fqpls.names]
|
|
426
|
-
worf_tags_dict = generate_geom(worf, names)
|
|
427
|
-
|
|
428
|
-
w_z_maxs = []
|
|
429
|
-
w_z_mins = []
|
|
430
|
-
if type_str == 'windings':
|
|
431
|
-
for winding in worf:
|
|
432
|
-
w_z_maxs.append(winding.z_max_winding_layer)
|
|
433
|
-
w_z_mins.append(winding.z_min_winding_layer)
|
|
434
|
-
self.cctwi.windings_avg_length = float(np.mean(w_z_maxs) - np.mean(w_z_mins))
|
|
435
|
-
self.cctwi.windings.names = names
|
|
436
|
-
self.cctwi.windings.t_in.vol_st = []
|
|
437
|
-
self.cctwi.windings.t_in.surf_st = []
|
|
438
|
-
self.cctwi.windings.t_out.vol_st = []
|
|
439
|
-
self.cctwi.windings.t_out.surf_st = []
|
|
440
|
-
self.cctwi.windings.t_in.vol_et = []
|
|
441
|
-
self.cctwi.windings.t_in.surf_et = []
|
|
442
|
-
self.cctwi.windings.t_out.vol_et = []
|
|
443
|
-
self.cctwi.windings.t_out.surf_et = []
|
|
444
|
-
self.cctwi.windings.t_in.lc_et = []
|
|
445
|
-
self.cctwi.windings.t_out.lc_et = []
|
|
446
|
-
self.cctwi.windings.t_in.ndpterms = self.cctdm.geometry.windings.ndpt_ins
|
|
447
|
-
self.cctwi.windings.t_out.ndpterms = self.cctdm.geometry.windings.ndpt_outs
|
|
448
|
-
self.cctwi.windings.t_in.z_air = self.cctdm.geometry.air.z_min
|
|
449
|
-
self.cctwi.windings.t_out.z_air = self.cctdm.geometry.air.z_max
|
|
450
|
-
for winding_num, winding_dict in worf_tags_dict.items():
|
|
451
|
-
first_vol = list(winding_dict.keys())[0]
|
|
452
|
-
last_vol = list(winding_dict.keys())[-1]
|
|
453
|
-
self.cctwi.windings.t_in.vol_st.append(winding_dict[first_vol]['v_t'])
|
|
454
|
-
self.cctwi.windings.t_in.surf_st.append(winding_dict[first_vol]['sf_ts'][0])
|
|
455
|
-
self.cctwi.windings.t_out.vol_st.append(winding_dict[last_vol]['v_t'])
|
|
456
|
-
self.cctwi.windings.t_out.surf_st.append(winding_dict[last_vol]['sf_ts'][2])
|
|
457
|
-
self.cctwi.windings.t_in.vol_et.append(winding_dict[first_vol]['v_t'])
|
|
458
|
-
self.cctwi.windings.t_in.surf_et.append(winding_dict[first_vol]['sf_ts'][0])
|
|
459
|
-
self.cctwi.windings.t_out.vol_et.append(winding_dict[last_vol]['v_t'])
|
|
460
|
-
self.cctwi.windings.t_out.surf_et.append(winding_dict[last_vol]['sf_ts'][2])
|
|
461
|
-
|
|
462
|
-
lc_st_neg_alpha = [[1, 0], [0, 3], [3, 2], [2, 1], [6, 4], [4, 5], [5, 7], [7, 6], [1, 6], [0, 4], [3, 5], [2, 7]]
|
|
463
|
-
lc_st_pos_alpha = [[3, 1], [1, 0], [0, 2], [2, 3], [7, 4], [4, 5], [5, 6], [6, 7], [3, 7], [1, 4], [0, 5], [2, 6]]
|
|
464
|
-
lc_et_neg_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]]
|
|
465
|
-
lc_et_pos_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]]
|
|
466
|
-
# [[1, 2], [2, 3], [3, 4], [4, 1], [5, 6], [6, 7], [7, 8], [8, 5], [1, 5], [2, 6], [3, 7], [4, 8]]
|
|
467
|
-
# [[2, 1], [1, 4], [4, 3], [3, 2], [6, 5], [5, 8], [8, 7], [7, 6], [2, 6], [1, 5], [4, 8], [3, 7]]
|
|
468
|
-
|
|
469
|
-
lc_et_pos_corr = [[1, 0], [0, 3], [3, 2], [2, 1], [5, 4], [4, 7], [7, 6], [6, 5], [1, 5], [0, 4], [3, 7], [2, 6]]
|
|
470
|
-
|
|
471
|
-
self.cctwi.windings.t_in.lc_st = []
|
|
472
|
-
self.cctwi.windings.t_out.lc_st = []
|
|
473
|
-
for alpha in self.cctdm.geometry.windings.alphas:
|
|
474
|
-
if alpha > 0:
|
|
475
|
-
self.cctwi.windings.t_in.lc_st.append(lc_st_pos_alpha)
|
|
476
|
-
self.cctwi.windings.t_out.lc_st.append(lc_st_pos_alpha)
|
|
477
|
-
self.cctwi.windings.t_in.lc_et.append(lc_et_pos_corr)
|
|
478
|
-
self.cctwi.windings.t_out.lc_et.append(lc_et_pos_alpha)
|
|
479
|
-
else:
|
|
480
|
-
self.cctwi.windings.t_in.lc_st.append(lc_st_neg_alpha)
|
|
481
|
-
self.cctwi.windings.t_out.lc_st.append(lc_st_neg_alpha)
|
|
482
|
-
self.cctwi.windings.t_in.lc_et.append(lc_et_neg_alpha)
|
|
483
|
-
self.cctwi.windings.t_out.lc_et.append(lc_et_pos_corr)
|
|
484
|
-
|
|
485
|
-
if self.verbose:
|
|
486
|
-
print(f'Generating {type_str} took {timeit.default_timer() - start_time:.2f} s')
|
|
487
|
-
|
|
488
|
-
def save_volume_info(self):
|
|
489
|
-
if self.cctdm.geometry.fqpls.names:
|
|
490
|
-
fqpls = self.cctdm.geometry.fqpls.names
|
|
491
|
-
else:
|
|
492
|
-
fqpls = []
|
|
493
|
-
|
|
494
|
-
self.cctwi.w_names = self.cctdm.geometry.windings.names
|
|
495
|
-
self.cctwi.f_names = fqpls
|
|
496
|
-
self.cctwi.formers = self.cctdm.geometry.formers.names
|
|
497
|
-
self.cctwi.air = self.cctdm.geometry.air.name
|
|
498
|
-
|
|
499
|
-
volume_info_file = os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.wi')
|
|
500
|
-
uff.write_data_to_yaml(volume_info_file, self.cctwi.dict())
|
|
501
|
-
|
|
502
|
-
def generate_formers(self):
|
|
503
|
-
if self.cctdm.geometry.formers.r_ins:
|
|
504
|
-
if self.verbose:
|
|
505
|
-
print('Generating Formers Started')
|
|
506
|
-
start_time = timeit.default_timer()
|
|
507
|
-
|
|
508
|
-
for f_i, _ in enumerate(self.cctdm.geometry.formers.r_ins):
|
|
509
|
-
z = self.cctdm.geometry.formers.z_mins[f_i]
|
|
510
|
-
dz = self.cctdm.geometry.formers.z_maxs[f_i] - self.cctdm.geometry.formers.z_mins[f_i]
|
|
511
|
-
cylin_out = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_outs[f_i], angle=2 * math.pi)) # add cylinder to align to existing bodies in the pro file
|
|
512
|
-
cylin_in = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_ins[f_i], angle=2 * math.pi)) # add another cylinder to subtract from the first one to make a tube
|
|
513
|
-
cylinder = gmsh.model.occ.cut([cylin_out], [cylin_in], removeObject=True) # subtract cylinders to make a tube
|
|
514
|
-
self.formers.append(cylinder[0][0]) # keep just the tag and append
|
|
515
|
-
gmsh.model.occ.synchronize()
|
|
516
|
-
gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.formers.names[f_i]}.brep'))
|
|
517
|
-
gmsh.clear()
|
|
518
|
-
if self.verbose:
|
|
519
|
-
print(f'Generating formers took {timeit.default_timer() - start_time:.2f} s')
|
|
520
|
-
|
|
521
|
-
def generate_air(self):
|
|
522
|
-
if self.cctdm.geometry.air.ar:
|
|
523
|
-
if self.verbose:
|
|
524
|
-
print('Generating air started')
|
|
525
|
-
start_time = timeit.default_timer()
|
|
526
|
-
if self.cctdm.geometry.air.sh_type == 'cylinder':
|
|
527
|
-
self.air = [(3, gmsh.model.occ.addCylinder(0, 0, self.cctdm.geometry.air.z_min, 0, 0, self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, angle=2 * math.pi))]
|
|
528
|
-
elif self.cctdm.geometry.air.sh_type == 'cuboid':
|
|
529
|
-
air_box_size = [-self.cctdm.geometry.air.ar / 2, -self.cctdm.geometry.air.ar / 2, self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, self.cctdm.geometry.air.ar,
|
|
530
|
-
self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min] # list of box size with: x, y, z, dx, dy, dz
|
|
531
|
-
self.air = [(3, gmsh.model.occ.addBox(*air_box_size))]
|
|
532
|
-
else:
|
|
533
|
-
raise ValueError(f'Shape type: {self.cctdm.geometry.air.sh_type} is not supported!')
|
|
534
|
-
gmsh.model.occ.synchronize()
|
|
535
|
-
gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.air.name}.brep'))
|
|
536
|
-
gmsh.clear()
|
|
537
|
-
if self.verbose:
|
|
538
|
-
print(f'Generating air took {timeit.default_timer() - start_time:.2f} s')
|
|
539
|
-
|
|
540
|
-
def generate_regions_file(self):
|
|
541
|
-
if self.verbose:
|
|
542
|
-
print('Generating Regions File Started')
|
|
543
|
-
start_time = timeit.default_timer()
|
|
544
|
-
cctrm = Reg_Mod_FiQ.RegionsModel()
|
|
545
|
-
vrt = 1000000 # volume region tag start
|
|
546
|
-
srt = 2000000 # surface region tag start
|
|
547
|
-
lrt = 3000000 # line region tag start
|
|
548
|
-
# -------- powered ----------
|
|
549
|
-
# volumes
|
|
550
|
-
cctrm.powered['cct'] = Reg_Mod_FiQ.Powered()
|
|
551
|
-
cctrm.powered['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
552
|
-
cctrm.powered['cct'].vol.currents = self.cctdm.solve.windings.currents + self.cctdm.solve.fqpls.currents
|
|
553
|
-
cctrm.powered['cct'].vol.sigmas = self.cctdm.solve.windings.sigmas + self.cctdm.solve.fqpls.sigmas
|
|
554
|
-
cctrm.powered['cct'].vol.mu_rs = self.cctdm.solve.windings.mu_rs + self.cctdm.solve.fqpls.mu_rs
|
|
555
|
-
reg = []
|
|
556
|
-
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
557
|
-
reg.append(vrt)
|
|
558
|
-
vrt += 1
|
|
559
|
-
cctrm.powered['cct'].vol.numbers = reg
|
|
560
|
-
# surfaces
|
|
561
|
-
cctrm.powered['cct'].surf_in.names = [name + self.transl_dict['surf_in'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
562
|
-
cctrm.powered['cct'].surf_out.names = [name + self.transl_dict['surf_out'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
563
|
-
reg = []
|
|
564
|
-
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
565
|
-
reg.append(srt)
|
|
566
|
-
srt += 1
|
|
567
|
-
cctrm.powered['cct'].surf_in.numbers = reg
|
|
568
|
-
reg = []
|
|
569
|
-
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
570
|
-
reg.append(srt)
|
|
571
|
-
srt += 1
|
|
572
|
-
cctrm.powered['cct'].surf_out.numbers = reg
|
|
573
|
-
|
|
574
|
-
# -------- induced ----------
|
|
575
|
-
# volumes
|
|
576
|
-
cctrm.induced['cct'] = Reg_Mod_FiQ.Induced()
|
|
577
|
-
cctrm.induced['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.formers.names]
|
|
578
|
-
cctrm.induced['cct'].vol.sigmas = self.cctdm.solve.formers.sigmas
|
|
579
|
-
cctrm.induced['cct'].vol.mu_rs = self.cctdm.solve.formers.mu_rs
|
|
580
|
-
reg = []
|
|
581
|
-
for _ in self.cctdm.geometry.formers.names:
|
|
582
|
-
reg.append(vrt)
|
|
583
|
-
vrt += 1
|
|
584
|
-
cctrm.induced['cct'].vol.numbers = reg
|
|
585
|
-
|
|
586
|
-
# -------- air ----------
|
|
587
|
-
# volumes
|
|
588
|
-
cctrm.air.vol.name = self.cctdm.geometry.air.name + self.transl_dict['vol']
|
|
589
|
-
cctrm.air.vol.sigma = self.cctdm.solve.air.sigma
|
|
590
|
-
cctrm.air.vol.mu_r = self.cctdm.solve.air.mu_r
|
|
591
|
-
cctrm.air.vol.number = vrt
|
|
592
|
-
vrt += 1
|
|
593
|
-
|
|
594
|
-
# surface
|
|
595
|
-
cctrm.air.surf.name = self.cctdm.geometry.air.name + self.transl_dict['surf']
|
|
596
|
-
cctrm.air.surf.number = srt
|
|
597
|
-
srt += 1
|
|
598
|
-
|
|
599
|
-
# # center line
|
|
600
|
-
# cctrm.air.line.name = 'Center_line'
|
|
601
|
-
# cctrm.air.line.number = lrt
|
|
602
|
-
# lrt += 1
|
|
603
|
-
lrt = srt
|
|
604
|
-
|
|
605
|
-
# --------- cuts -------
|
|
606
|
-
# these need to be done at the end with the highest surface tags
|
|
607
|
-
cctrm.powered['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
608
|
-
reg = []
|
|
609
|
-
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
610
|
-
reg.append(lrt)
|
|
611
|
-
lrt += 1
|
|
612
|
-
cctrm.powered['cct'].cochain.numbers = reg
|
|
613
|
-
# induced cuts
|
|
614
|
-
cctrm.induced['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.formers.names]
|
|
615
|
-
reg = []
|
|
616
|
-
for _ in self.cctdm.geometry.formers.names:
|
|
617
|
-
reg.append(lrt)
|
|
618
|
-
lrt += 1
|
|
619
|
-
cctrm.induced['cct'].cochain.numbers = reg
|
|
620
|
-
|
|
621
|
-
uff.write_data_to_yaml(os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.regions'), cctrm.dict())
|
|
622
|
-
|
|
623
|
-
if self.verbose:
|
|
624
|
-
print(f'Generating Regions File Took {timeit.default_timer() - start_time:.2f} s')
|
|
625
|
-
|
|
626
|
-
@staticmethod
|
|
627
|
-
def generate_hex_geom(hexes_dict,
|
|
628
|
-
cons_for_lines=[[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]],
|
|
629
|
-
vol_tag=-1):
|
|
630
|
-
"""
|
|
631
|
-
Generates hexahedra volumes from data in hexes dict
|
|
632
|
-
:param hexes_dict: dictionary of data generated by either Winding or FQPL class in Hexahedrons.py
|
|
633
|
-
:param cons_for_lines: line connection defined as list of list in terms of point numbers to connect in the hexahedron
|
|
634
|
-
:param vol_tag: volume tag to use for generated volume of hexahedron
|
|
635
|
-
:return: dictionary with tags of created volumes, surface loops, surface fillings, closed loops, lines, points and channel turn (i.e. turn number of CCT winding).
|
|
636
|
-
"""
|
|
637
|
-
tags_dict = {}
|
|
638
|
-
for h_num, h_dict in hexes_dict.hexes.items():
|
|
639
|
-
p_ts = [] # Point tags
|
|
640
|
-
channel_turn = h_dict.pop('ct', None)
|
|
641
|
-
for p_num, p_dict in h_dict.items():
|
|
642
|
-
p_ts.append(gmsh.model.occ.addPoint(p_dict['x'], p_dict['y'], p_dict['z']))
|
|
643
|
-
|
|
644
|
-
l_ts = [] # Line tags
|
|
645
|
-
for p_c in cons_for_lines: # point connections to make a line
|
|
646
|
-
l_ts.append(gmsh.model.occ.addLine(p_ts[p_c[0]], p_ts[p_c[1]]))
|
|
647
|
-
|
|
648
|
-
cl_ts = [] # Curved Loops tags
|
|
649
|
-
for l_c in [[0, 1, 2, 3], [4, 5, 6, 7], [3, 8, 7, 11], [1, 9, 5, 10], [0, 8, 4, 9], [2, 11, 6, 10]]: # line connections to make a curved loop
|
|
650
|
-
cl_ts.append(gmsh.model.occ.addCurveLoop([l_ts[l_c[0]], l_ts[l_c[1]], l_ts[l_c[2]], l_ts[l_c[3]]]))
|
|
651
|
-
sf_ts = [] # Surface Filling tags
|
|
652
|
-
for cl_t in cl_ts:
|
|
653
|
-
sf_ts.append(gmsh.model.occ.addSurfaceFilling(cl_t, degree=3, tol2d=0.000001, tol3d=0.00001, tolAng=0.001, tolCurv=0.05))
|
|
654
|
-
sl_t = gmsh.model.occ.addSurfaceLoop(sf_ts, sewing=True)
|
|
655
|
-
v_t = gmsh.model.occ.addVolume([sl_t], vol_tag)
|
|
656
|
-
tags_dict[int(h_num)] = {'v_t': v_t, 'sl_t': sl_t, 'sf_ts': sf_ts, 'cl_ts': cl_ts, 'l_ts': l_ts, 'p_ts': p_ts, 'ct': channel_turn}
|
|
657
|
-
return tags_dict
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
class Prepare_BREPs:
|
|
661
|
-
def __init__(self, fdm, verbose=True) -> object:
|
|
662
|
-
"""
|
|
663
|
-
Class to preparing brep files by adding terminals.
|
|
664
|
-
:param fdm: FiQuS data model
|
|
665
|
-
:param verbose: If True more information is printed in python console.
|
|
666
|
-
"""
|
|
667
|
-
|
|
668
|
-
self.cctdm = fdm.magnet
|
|
669
|
-
self.model_folder = os.path.join(os.getcwd())
|
|
670
|
-
self.magnet_name = fdm.general.magnet_name
|
|
671
|
-
|
|
672
|
-
self.verbose = verbose
|
|
673
|
-
self.winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi')
|
|
674
|
-
self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation)
|
|
675
|
-
self.gu = GmshUtils(self.model_folder, self.verbose)
|
|
676
|
-
self.gu.initialize()
|
|
677
|
-
self.model_file = os.path.join(self.model_folder, f'{self.magnet_name}.brep')
|
|
678
|
-
|
|
679
|
-
@staticmethod
|
|
680
|
-
def get_pts_for_sts(surface_tags):
|
|
681
|
-
"""
|
|
682
|
-
Get point tags for surface tags
|
|
683
|
-
:param surface_tags: list of surface tags to get point tags
|
|
684
|
-
:return: list of point tags belonging to the surfaces in the list
|
|
685
|
-
"""
|
|
686
|
-
vol_line_tags = []
|
|
687
|
-
vol_line_tags_i = 0
|
|
688
|
-
vol_line_tags_idx = []
|
|
689
|
-
for surf_tag in surface_tags:
|
|
690
|
-
line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1]
|
|
691
|
-
for line_tag in line_tags_new:
|
|
692
|
-
vol_line_tags_i += 1
|
|
693
|
-
if line_tag not in vol_line_tags:
|
|
694
|
-
vol_line_tags.append(line_tag)
|
|
695
|
-
vol_line_tags_idx.append(vol_line_tags_i)
|
|
696
|
-
point_tags = []
|
|
697
|
-
point_tags_i = 0
|
|
698
|
-
point_tags_idx = []
|
|
699
|
-
for line_tag in vol_line_tags:
|
|
700
|
-
point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1]
|
|
701
|
-
for point_tag in point_tags_new:
|
|
702
|
-
point_tags_i += 1
|
|
703
|
-
if point_tag not in point_tags:
|
|
704
|
-
point_tags.append(point_tag)
|
|
705
|
-
point_tags_idx.append(point_tags_i)
|
|
706
|
-
return point_tags, vol_line_tags_idx, point_tags_idx
|
|
707
|
-
|
|
708
|
-
def straighten_terminal(self, gui=False):
|
|
709
|
-
"""
|
|
710
|
-
Extends winding geom_generators to the air region boundary by extending the geom_generators with 'terminals' geom_generators up to the air boundary surfaces
|
|
711
|
-
By default this extends along the z axis and makes the final surface normal to the z axis.
|
|
712
|
-
:return: None, saves breps with straighten terminals to disk
|
|
713
|
-
"""
|
|
714
|
-
|
|
715
|
-
for i, name in enumerate(self.cctwi.windings.names):
|
|
716
|
-
if self.verbose:
|
|
717
|
-
print(f'Straightening terminals of {name} started')
|
|
718
|
-
start_time = timeit.default_timer()
|
|
719
|
-
gmsh.open(os.path.join(self.model_folder, f'{name}.brep'))
|
|
720
|
-
for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
|
|
721
|
-
vol_tag = terminal.vol_st[i]
|
|
722
|
-
vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
|
|
723
|
-
surf_tag = terminal.surf_st[i]
|
|
724
|
-
if surf_tag not in vol_surf_tags:
|
|
725
|
-
raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
|
|
726
|
-
# surf_point_tags = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
|
|
727
|
-
surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
|
|
728
|
-
vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags) # all tags of hex
|
|
729
|
-
vol_point_dict = {}
|
|
730
|
-
for p_tag in vol_point_tags:
|
|
731
|
-
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
732
|
-
vol_point_dict[p_tag] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
|
|
733
|
-
z = []
|
|
734
|
-
y = []
|
|
735
|
-
x = []
|
|
736
|
-
for p_tag in surf_point_tags: # calculate average z position for the terminal surface
|
|
737
|
-
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
738
|
-
x.append((xmin + xmax) / 2)
|
|
739
|
-
y.append((ymin + ymax) / 2)
|
|
740
|
-
z.append((zmin + zmax) / 2)
|
|
741
|
-
x_avg = np.mean(x)
|
|
742
|
-
y_avg = np.mean(y)
|
|
743
|
-
z_avg = np.mean(z)
|
|
744
|
-
sign_x = [math.copysign(1, x_i - x_avg) for x_i in x]
|
|
745
|
-
sign_y = [math.copysign(1, y_i - y_avg) for y_i in y]
|
|
746
|
-
dist_x = []
|
|
747
|
-
dist_y = []
|
|
748
|
-
for j in range(len(x) - 1):
|
|
749
|
-
dist_x.append(math.sqrt((x[j] - x[j + 1]) ** 2))
|
|
750
|
-
dist_y.append(math.sqrt((y[j] - y[j + 1]) ** 2))
|
|
751
|
-
eq_len = self.cctdm.geometry.windings.wwhs[i] / 2
|
|
752
|
-
y_shift_sign = math.copysign(1, -terminal.z_air * self.cctdm.geometry.windings.alphas[i])
|
|
753
|
-
new_x = [x_avg + s_x * eq_len for s_x in sign_x]
|
|
754
|
-
new_y = [y_avg + s_y * eq_len + y_shift_sign * eq_len - y_shift_sign * dist_y[0] / 2 for s_y in sign_y]
|
|
755
|
-
for p_tag, x_n, y_n in zip(surf_point_tags, new_x, new_y): # assign z_avg to only points on the terminal surface
|
|
756
|
-
vol_point_dict[p_tag]['x'] = x_n
|
|
757
|
-
vol_point_dict[p_tag]['y'] = y_n
|
|
758
|
-
vol_point_dict[p_tag]['z'] = z_avg
|
|
759
|
-
gmsh.model.occ.remove([(3, terminal.vol_st[i])], recursive=True)
|
|
760
|
-
gmsh.model.occ.synchronize()
|
|
761
|
-
hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
|
|
762
|
-
hexes.hexes[0] = {} # only one winding layer is involved per iteration, so hardcoded 0 index
|
|
763
|
-
for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
|
|
764
|
-
hexes.hexes[0][p_i] = p_dict
|
|
765
|
-
Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_st[i], vol_tag=terminal.vol_st[i])
|
|
766
|
-
gmsh.model.occ.synchronize()
|
|
767
|
-
gmsh.write(os.path.join(self.model_folder, f'{name}.brep'))
|
|
768
|
-
if self.verbose:
|
|
769
|
-
print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
|
|
770
|
-
# uff.write_data_to_yaml(self.winding_info_yaml_path, self.cctvi.dict())
|
|
771
|
-
if gui:
|
|
772
|
-
gmsh.model.occ.synchronize()
|
|
773
|
-
self.gu.launch_interactive_GUI()
|
|
774
|
-
else:
|
|
775
|
-
gmsh.clear()
|
|
776
|
-
|
|
777
|
-
def extend_terms(self, operation='extend', gui=False, ):
|
|
778
|
-
self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation) # this is repeated as the operation over the data for the case add, so an original data for extend operation needs to be loaded.
|
|
779
|
-
if operation == 'add':
|
|
780
|
-
for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
|
|
781
|
-
terminal.ndpterms = [1] * len(terminal.ndpterms)
|
|
782
|
-
terminal.z_air = terminal.z_add
|
|
783
|
-
elif operation == 'extend':
|
|
784
|
-
pass
|
|
785
|
-
file_in_postfix = ''
|
|
786
|
-
file_out_postfix = ''
|
|
787
|
-
for i, name in enumerate(self.cctwi.windings.names):
|
|
788
|
-
if self.verbose:
|
|
789
|
-
print(f'Extending terminals of {name} started')
|
|
790
|
-
start_time = timeit.default_timer()
|
|
791
|
-
gmsh.open(os.path.join(self.model_folder, f'{name}{file_in_postfix}.brep'))
|
|
792
|
-
volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
|
|
793
|
-
for oper, terminal in enumerate([self.cctwi.windings.t_in, self.cctwi.windings.t_out]):
|
|
794
|
-
vol_tag = terminal.vol_et[i]
|
|
795
|
-
vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
|
|
796
|
-
surf_tag = terminal.surf_et[i]
|
|
797
|
-
if surf_tag not in vol_surf_tags:
|
|
798
|
-
raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
|
|
799
|
-
surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag])
|
|
800
|
-
surf_point_tags = sorted(surf_point_tags)
|
|
801
|
-
vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags)
|
|
802
|
-
vol_point_tags = sorted(vol_point_tags)
|
|
803
|
-
surf_point_tags_idx_pw = [i for i, e in enumerate(vol_point_tags) if e in set(surf_point_tags)] # powered
|
|
804
|
-
surf_point_tags_idx_oth = [i for i, e in enumerate(vol_point_tags) if e not in set(surf_point_tags)] # other (opposite powered)
|
|
805
|
-
vol_point_dict = {}
|
|
806
|
-
for idx_pw, p_tag in zip(surf_point_tags_idx_pw, surf_point_tags):
|
|
807
|
-
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
808
|
-
vol_point_dict[idx_pw] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
|
|
809
|
-
z_ext = round((terminal.z_air - zmin) / terminal.ndpterms[i], 8)
|
|
810
|
-
for idx_oth, p_tag in zip(surf_point_tags_idx_oth, surf_point_tags):
|
|
811
|
-
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
812
|
-
vol_point_dict[idx_oth] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2 + z_ext}
|
|
813
|
-
hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
|
|
814
|
-
for h in range(terminal.ndpterms[i]):
|
|
815
|
-
hexes.hexes[h] = {}
|
|
816
|
-
for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
|
|
817
|
-
hexes.hexes[h][p_i] = copy.deepcopy(p_dict)
|
|
818
|
-
for p_t, p_dict in vol_point_dict.items():
|
|
819
|
-
p_dict['z'] = p_dict['z'] + z_ext
|
|
820
|
-
for idx_oth in range(4, 8):
|
|
821
|
-
z_air = terminal.z_air
|
|
822
|
-
hexes.hexes[h][idx_oth]['z'] = z_air
|
|
823
|
-
tags_dict = Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_et[i], vol_tag=-1)
|
|
824
|
-
new_volumes = []
|
|
825
|
-
for _, hex_dict in tags_dict.items():
|
|
826
|
-
new_volumes.append(hex_dict['v_t'])
|
|
827
|
-
gmsh.model.occ.synchronize()
|
|
828
|
-
if oper == 0: # this vol_in is needed for reordering volumes later
|
|
829
|
-
vol_in = new_volumes
|
|
830
|
-
renumbered_vols = []
|
|
831
|
-
other_vols = list(set(volumes) - set(vol_in))
|
|
832
|
-
max_tag = gmsh.model.occ.getMaxTag(3)
|
|
833
|
-
for vol in other_vols:
|
|
834
|
-
gmsh.model.setTag(3, vol, vol + max_tag)
|
|
835
|
-
renumbered_vols.append(vol + max_tag)
|
|
836
|
-
for n_tag, vol in enumerate(reversed(vol_in)):
|
|
837
|
-
gmsh.model.setTag(3, vol, n_tag + 1)
|
|
838
|
-
max_term_tag = len(vol_in) + 1
|
|
839
|
-
spiral_volumes = []
|
|
840
|
-
for n_tag, vol in enumerate(renumbered_vols):
|
|
841
|
-
gmsh.model.setTag(3, vol, n_tag + max_term_tag)
|
|
842
|
-
spiral_volumes.append(n_tag + max_term_tag)
|
|
843
|
-
gmsh.write(os.path.join(self.model_folder, f'{name}{file_out_postfix}.brep'))
|
|
844
|
-
if self.verbose:
|
|
845
|
-
print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
|
|
846
|
-
print(f'Writing volume information file: {name}.vi ')
|
|
847
|
-
vi = {'export': spiral_volumes, 'all': [vol[1] for vol in gmsh.model.getEntities(dim=3)]}
|
|
848
|
-
json.dump(vi, open(f"{os.path.join(self.model_folder, name)}.vi", 'w'))
|
|
849
|
-
if self.verbose:
|
|
850
|
-
print(f'Done writing volume information file: {name}.vi ')
|
|
851
|
-
if gui:
|
|
852
|
-
self.gu.launch_interactive_GUI()
|
|
853
|
-
else:
|
|
854
|
-
gmsh.clear()
|
|
855
|
-
|
|
856
|
-
def save_fqpl_vi(self):
|
|
857
|
-
for f_name in self.cctdm.geometry.fqpls.names:
|
|
858
|
-
if self.verbose:
|
|
859
|
-
print(f'Writing volume information file: {f_name}.vi ')
|
|
860
|
-
gmsh.open(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
861
|
-
volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
|
|
862
|
-
export = volumes
|
|
863
|
-
vi = {'export': export, 'all': volumes}
|
|
864
|
-
json.dump(vi, open(f"{os.path.join(self.model_folder, f_name)}.vi", 'w'))
|
|
865
|
-
if self.verbose:
|
|
866
|
-
print(f'Done writing volume information file: {f_name}.vi ')
|
|
867
|
-
gmsh.clear()
|
|
868
|
-
|
|
869
|
-
def fragment(self, gui=False):
|
|
870
|
-
if self.verbose:
|
|
871
|
-
print('Loading files in preparation for fragment operation')
|
|
872
|
-
for f_name in self.cctwi.w_names + self.cctwi.f_names:
|
|
873
|
-
gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
874
|
-
num_vol = np.max([vol[1] for vol in gmsh.model.getEntities(dim=3)])
|
|
875
|
-
for f_name in self.cctwi.formers:
|
|
876
|
-
gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
877
|
-
num_vol += 1
|
|
878
|
-
gmsh.merge(os.path.join(self.model_folder, f'{self.cctwi.air}.brep'))
|
|
879
|
-
num_vol += 1
|
|
880
|
-
entities = gmsh.model.getEntities(dim=3)
|
|
881
|
-
if len(entities) != num_vol:
|
|
882
|
-
raise ValueError('Not consistent volumes numbers in brep and json files!')
|
|
883
|
-
objectDimTags = [entities[-1]]
|
|
884
|
-
toolDimTags = entities[:-1]
|
|
885
|
-
# central_line = [(1, 3898)]
|
|
886
|
-
# toolDimTags = toolDimTags+central_line
|
|
887
|
-
if self.verbose:
|
|
888
|
-
print(f'Fragmenting {self.magnet_name} started')
|
|
889
|
-
start_time = timeit.default_timer()
|
|
890
|
-
gmsh.model.occ.fragment(objectDimTags, toolDimTags, removeObject=True, removeTool=True)
|
|
891
|
-
gmsh.model.occ.synchronize()
|
|
892
|
-
if self.verbose:
|
|
893
|
-
print(f'Fragmenting {self.magnet_name} took {timeit.default_timer() - start_time:.2f} s')
|
|
894
|
-
|
|
895
|
-
gmsh.write(self.model_file)
|
|
896
|
-
if gui:
|
|
897
|
-
self.gu.launch_interactive_GUI()
|
|
898
|
-
else:
|
|
899
|
-
gmsh.clear()
|
|
900
|
-
gmsh.finalize()
|
|
901
|
-
|
|
902
|
-
def load_geometry(self, gui=False):
|
|
903
|
-
gmsh.open(self.model_file)
|
|
904
|
-
if gui:
|
|
905
|
-
self.gu.launch_interactive_GUI()
|
|
1
|
+
import os
|
|
2
|
+
import copy
|
|
3
|
+
import math
|
|
4
|
+
import collections
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import timeit
|
|
9
|
+
import numpy as np
|
|
10
|
+
import gmsh
|
|
11
|
+
|
|
12
|
+
from fiqus.data import RegionsModelFiQuS as Reg_Mod_FiQ
|
|
13
|
+
from fiqus.data.DataWindingsCCT import WindingsInformation # for winding information
|
|
14
|
+
from fiqus.utils.Utils import FilesAndFolders as uff
|
|
15
|
+
from fiqus.utils.Utils import GmshUtils
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
class Hexahedrons:
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""
|
|
22
|
+
Points generator for CCT windings to give hexahedron volumes for hexahedron meshing in Gmsh.
|
|
23
|
+
"""
|
|
24
|
+
self.vertices_to_surf = [[0, 4, 7, 3], [1, 5, 6, 2], [1, 5, 4, 0], [2, 6, 7, 3], [0, 1, 2, 3], [4, 5, 6, 7]]
|
|
25
|
+
# Node ordering following Gmsh for hexahedron https://gmsh.info/doc/texinfo/gmsh.html#Legacy-formats --> node ordering
|
|
26
|
+
# above _vertices_to_surf is only used for manipulation of the hexahedra points on the data model level. Gmsh uses points connections and line connections for creating surfaces
|
|
27
|
+
|
|
28
|
+
self.hexes = {}
|
|
29
|
+
self._corner_points = {}
|
|
30
|
+
self._sur_names = ['x-', 'x+', 'y-', 'y+', 'z-', 'z+']
|
|
31
|
+
self._op_sign = {'+': '-', '-': '+'}
|
|
32
|
+
self._sign = {1: '+', -1: '-'}
|
|
33
|
+
|
|
34
|
+
def _add_corner_points(self, corner_num, x, y, z, ct):
|
|
35
|
+
self._corner_points[corner_num] = {'x': x, 'y': y, 'z': z, 'ct': ct} # ct is channel turn
|
|
36
|
+
|
|
37
|
+
def _generate_hexes(self):
|
|
38
|
+
for h in range(len(self._corner_points[0]['x'])):
|
|
39
|
+
self.hexes[h] = {}
|
|
40
|
+
for p_num, p_coor in self._corner_points.items():
|
|
41
|
+
self.hexes[h][p_num] = {}
|
|
42
|
+
for coor in ['x', 'y', 'z']:
|
|
43
|
+
self.hexes[h][p_num][coor] = p_coor[coor][h]
|
|
44
|
+
self.hexes[h]['ct'] = p_coor['ct'][h]
|
|
45
|
+
return p_coor['ct'][h]
|
|
46
|
+
|
|
47
|
+
def _add_elem_to_elem(self, start_elem, dir_str, dist):
|
|
48
|
+
"""
|
|
49
|
+
:param start_elem: idx of start element
|
|
50
|
+
:param dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
51
|
+
:param dist: length of new element in the direction specified above [m]
|
|
52
|
+
:return: index of created element
|
|
53
|
+
"""
|
|
54
|
+
op_dir = dir_str[0] + self._op_sign[dir_str[1]]
|
|
55
|
+
new_elem = start_elem + math.copysign(1, start_elem)
|
|
56
|
+
self.hexes[new_elem] = {}
|
|
57
|
+
source_points = self.vertices_to_surf[self._sur_names.index(dir_str)]
|
|
58
|
+
destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)]
|
|
59
|
+
for d_p, s_p in zip(destination_points + source_points, source_points + source_points):
|
|
60
|
+
self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[start_elem][s_p])
|
|
61
|
+
for s_p in source_points: # modif_points:
|
|
62
|
+
self.hexes[new_elem][s_p][dir_str[0]] = self.hexes[new_elem][s_p][dir_str[0]] + dist
|
|
63
|
+
self.hexes[new_elem]['ct'] = self.hexes[start_elem]['ct']
|
|
64
|
+
return new_elem
|
|
65
|
+
|
|
66
|
+
def _reorder_and_renumber_hexes(self):
|
|
67
|
+
self.hexes = collections.OrderedDict(sorted(self.hexes.items()))
|
|
68
|
+
first_hex_num = list(self.hexes.keys())[0]
|
|
69
|
+
temp_dict = {}
|
|
70
|
+
for h_num, h_dict in self.hexes.items():
|
|
71
|
+
temp_dict[h_num - first_hex_num + 1] = h_dict
|
|
72
|
+
self.hexes = temp_dict
|
|
73
|
+
for h_num, h_dict in self.hexes.items():
|
|
74
|
+
turn = h_dict.pop('ct', None)
|
|
75
|
+
self.hexes[h_num] = collections.OrderedDict(sorted(h_dict.items()))
|
|
76
|
+
self.hexes[h_num]['ct'] = turn
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Winding(Hexahedrons):
|
|
80
|
+
|
|
81
|
+
def __init__(self, cctdm, layer, post_proc=False):
|
|
82
|
+
|
|
83
|
+
super(Winding, self).__init__()
|
|
84
|
+
self.cctdm = cctdm
|
|
85
|
+
self.layer = layer
|
|
86
|
+
self.post_proc = post_proc
|
|
87
|
+
self.z_corr = 0
|
|
88
|
+
self._populate_corners()
|
|
89
|
+
self.z_corr = self._center_z_and_offset()
|
|
90
|
+
self._generate_hexes()
|
|
91
|
+
|
|
92
|
+
def _populate_corners(self):
|
|
93
|
+
r"""
|
|
94
|
+
Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn.
|
|
95
|
+
y (v)
|
|
96
|
+
3----------2
|
|
97
|
+
|\ ^ |\
|
|
98
|
+
| \ | | \
|
|
99
|
+
| \ | | \
|
|
100
|
+
| 7------+---6
|
|
101
|
+
| | +-- |-- | -> x (u)
|
|
102
|
+
0---+---\--1 |
|
|
103
|
+
\ | \ \ |
|
|
104
|
+
\ | \ \ |
|
|
105
|
+
\| z(w)\|
|
|
106
|
+
4----------5
|
|
107
|
+
|
|
108
|
+
:return: none
|
|
109
|
+
"""
|
|
110
|
+
self._sign_of_rot = math.copysign(1, self.cctdm.geometry.windings.alphas[self.layer])
|
|
111
|
+
if self._sign_of_rot > 0:
|
|
112
|
+
corner_seq = {
|
|
113
|
+
0: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf'
|
|
114
|
+
1: {'w_s': -1, 'h_s': 1, 'far': False}, # 'brcf'
|
|
115
|
+
2: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf'
|
|
116
|
+
3: {'w_s': 1, 'h_s': -1, 'far': False}, # 'tlcf'
|
|
117
|
+
4: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc'
|
|
118
|
+
5: {'w_s': -1, 'h_s': 1, 'far': True}, # bottom right corner close #'brcc'
|
|
119
|
+
6: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
120
|
+
7: {'w_s': 1, 'h_s': -1, 'far': True}, # top right corner close #'tlcc'
|
|
121
|
+
}
|
|
122
|
+
else:
|
|
123
|
+
corner_seq = {
|
|
124
|
+
0: {'w_s': 1, 'h_s': 1, 'far': False}, # 'trcf'
|
|
125
|
+
1: {'w_s': 1, 'h_s': -1, 'far': False}, # 'brcf'
|
|
126
|
+
2: {'w_s': -1, 'h_s': -1, 'far': False}, # 'blcf'
|
|
127
|
+
3: {'w_s': -1, 'h_s': 1, 'far': False}, # 'tlcf'
|
|
128
|
+
4: {'w_s': 1, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
129
|
+
5: {'w_s': 1, 'h_s': -1, 'far': True}, # bottom right corner close #'brcc'
|
|
130
|
+
6: {'w_s': -1, 'h_s': -1, 'far': True}, # bottom left corner close #'blcc'
|
|
131
|
+
7: {'w_s': -1, 'h_s': 1, 'far': True}, # top left corner close #'tlcc'
|
|
132
|
+
}
|
|
133
|
+
for corner_num, corner_dict in corner_seq.items():
|
|
134
|
+
self._add_corner_points(corner_num, *self.calc_turns_corner_coords(corner_dict['w_s'], corner_dict['h_s'], far=corner_dict['far']))
|
|
135
|
+
|
|
136
|
+
def calc_turns_corner_coords(self, w_s=1, h_s=1, far=False):
|
|
137
|
+
"""
|
|
138
|
+
Based on https://doi.org/10.1016/j.cryogenics.2020.103041
|
|
139
|
+
:param h_s: wire height sign
|
|
140
|
+
:param w_s: wire width sign
|
|
141
|
+
:param far: for far surface set to True, for close surface set to False. This refers to walls of hexagon.
|
|
142
|
+
:return:
|
|
143
|
+
"""
|
|
144
|
+
r_wm = self.cctdm.geometry.windings.r_wms[self.layer] # radius of winding layer, in the middle of the conductor groove [m]
|
|
145
|
+
w_i = self.cctdm.geometry.windings.lps[self.layer] # layer pitch [m]
|
|
146
|
+
n_turns = self.cctdm.geometry.windings.n_turnss[self.layer] # number of turns [-]
|
|
147
|
+
nept = self.cctdm.geometry.windings.ndpts[self.layer] - 1 # number of elements per turn [-]
|
|
148
|
+
alpha = self.cctdm.geometry.windings.alphas[self.layer] * np.pi / 180 # inclination of the winding [deg]
|
|
149
|
+
theta_0 = 0 # offset start at theta_0, i.e. angular position of the beginning of the 1st turn[deg]
|
|
150
|
+
z_0 = 0 # offset start at z_0, i.e. axial position of the beginning of the 1st turn [m]
|
|
151
|
+
hh = 0.5 * h_s * self.cctdm.geometry.windings.wwhs[self.layer] # half of h (groove height)
|
|
152
|
+
hw = 0.5 * w_s * self.cctdm.geometry.windings.wwws[self.layer] # half of w (groove width)
|
|
153
|
+
tot_angle = 2 * np.pi * (n_turns + 1 / nept)
|
|
154
|
+
tot_points = int(nept * n_turns) + 1
|
|
155
|
+
if alpha > 0:
|
|
156
|
+
theta = np.linspace(0, tot_angle, tot_points, endpoint=False)
|
|
157
|
+
else:
|
|
158
|
+
theta = np.linspace(0 + np.pi, tot_angle + np.pi, tot_points, endpoint=False) # this rotates terminals for opposite inclination to be on the opposite side
|
|
159
|
+
if far:
|
|
160
|
+
theta = theta[1:] # give all points but the first one, so the far surface can be created
|
|
161
|
+
else:
|
|
162
|
+
theta = theta[:-1] # give all the points but the last one so a near surface can be created
|
|
163
|
+
if theta.size < 2:
|
|
164
|
+
raise ValueError(f'Combination of number of division points per turn (ndpts) and number of turns (n_turnss) must result in at least 2 Hexahedrons, but inputs result in only {theta.size}!')
|
|
165
|
+
C = r_wm * np.arctan(alpha) * np.cos(theta - theta_0) + w_i / (2 * np.pi)
|
|
166
|
+
D = np.sqrt(r_wm ** 2 + np.square(C))
|
|
167
|
+
# -- x, y, z points coordinates
|
|
168
|
+
xs = (r_wm + hh) * np.cos(theta) - np.sin(theta) * hw * C / D
|
|
169
|
+
ys = (r_wm + hh) * np.sin(theta) + np.cos(theta) * hw * C / D
|
|
170
|
+
zs = r_wm * np.sin(theta - theta_0) / np.tan(alpha) + w_i * (theta - theta_0) / (2 * np.pi) + z_0 - hw * r_wm / D # original formula
|
|
171
|
+
if alpha > 0:
|
|
172
|
+
channel_turn = np.ceil((theta - 2 * np.pi / (nept + 1)) / (2 * np.pi))
|
|
173
|
+
# channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn.
|
|
174
|
+
else:
|
|
175
|
+
channel_turn = np.ceil((theta - np.pi - 2 * np.pi / (nept + 1)) / (2 * np.pi))
|
|
176
|
+
channel_turn[0] = channel_turn[1] # make the hex at theta 0 to belong to the first turn.
|
|
177
|
+
return xs.tolist(), ys.tolist(), zs.tolist(), list(map(int, channel_turn))
|
|
178
|
+
|
|
179
|
+
def _center_z_and_offset(self, offset=0):
|
|
180
|
+
z_mins = []
|
|
181
|
+
z_maxs = []
|
|
182
|
+
for c_val in self._corner_points.values():
|
|
183
|
+
z_mins.append(np.min(c_val['z']))
|
|
184
|
+
z_maxs.append(np.max(c_val['z']))
|
|
185
|
+
z_min_wind = np.min(np.array(z_mins))
|
|
186
|
+
z_max_wind = np.max(np.array(z_maxs))
|
|
187
|
+
z_centre = (z_min_wind + z_max_wind) / 2
|
|
188
|
+
if not self.post_proc:
|
|
189
|
+
for c_key, c_val in self._corner_points.items():
|
|
190
|
+
self._corner_points[c_key]['z'] = (c_val['z'] - z_centre + offset).tolist()
|
|
191
|
+
z_mins = []
|
|
192
|
+
z_maxs = []
|
|
193
|
+
for c_val in self._corner_points.values():
|
|
194
|
+
z_mins.append(np.min(c_val['z']))
|
|
195
|
+
z_maxs.append(np.max(c_val['z']))
|
|
196
|
+
self.z_min_winding_layer = np.min(np.array(z_mins))
|
|
197
|
+
self.z_max_winding_layer = np.max(np.array(z_maxs))
|
|
198
|
+
return z_centre + offset
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class FQPL(Hexahedrons):
|
|
202
|
+
def __init__(self, cctdm, layer):
|
|
203
|
+
"""
|
|
204
|
+
FQPL hex generator
|
|
205
|
+
:param cctdm: cct data model object parsed from yaml input file
|
|
206
|
+
:param layer: loop number, integer
|
|
207
|
+
"""
|
|
208
|
+
super(FQPL, self).__init__()
|
|
209
|
+
self.cctdm = cctdm
|
|
210
|
+
self.layer = layer
|
|
211
|
+
self._calc_z_unit_len()
|
|
212
|
+
self._populate_corners()
|
|
213
|
+
self._generate_hexes()
|
|
214
|
+
self._generate_fqpl()
|
|
215
|
+
self._rotate_fqpl()
|
|
216
|
+
self._reorder_and_renumber_hexes()
|
|
217
|
+
self._clean_attr()
|
|
218
|
+
|
|
219
|
+
def _calc_z_unit_len(self):
|
|
220
|
+
if self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_min':
|
|
221
|
+
self.near = self.cctdm.geometry.air.z_min
|
|
222
|
+
self.z_unit_len = (self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer]
|
|
223
|
+
|
|
224
|
+
elif self.cctdm.geometry.fqpls.z_starts[self.layer] == 'z_max':
|
|
225
|
+
self.near = self.cctdm.geometry.air.z_max
|
|
226
|
+
self.z_unit_len = (self.near - self.cctdm.geometry.fqpls.z_ends[self.layer] - self.cctdm.geometry.fqpls.fwws[self.layer]) / self.cctdm.geometry.fqpls.fndpls[self.layer]
|
|
227
|
+
else:
|
|
228
|
+
raise ValueError(f'fqpl.z_starts parameter must be a string equal to z_min or z_max, but {self.cctdm.geometry.fqpls.z_starts[self.layer]} was given!')
|
|
229
|
+
|
|
230
|
+
self.extrusion_sign = math.copysign(1, self.cctdm.geometry.fqpls.z_ends[self.layer] - self.near)
|
|
231
|
+
|
|
232
|
+
def _calc_first_corner_coords(self, w_s, h_s, far, offset):
|
|
233
|
+
x = [self.cctdm.geometry.fqpls.r_ins[self.layer] + self.cctdm.geometry.fqpls.fwhs[self.layer] * h_s] # not rotated coordinate
|
|
234
|
+
y = [self.cctdm.geometry.fqpls.fwws[self.layer] * w_s] # not rotated coordinate
|
|
235
|
+
if far:
|
|
236
|
+
z = [self.near - self.z_unit_len + offset]
|
|
237
|
+
else:
|
|
238
|
+
z = [self.near + offset]
|
|
239
|
+
return x, y, z, [1]
|
|
240
|
+
|
|
241
|
+
def _populate_corners(self):
|
|
242
|
+
r"""
|
|
243
|
+
Generates hexahedron with 8 corners numbered 0-7. The size of hexahedron is taken from groove size of cct as specified in yaml. The length is from number of elements per turn.
|
|
244
|
+
y (v)
|
|
245
|
+
3----------2
|
|
246
|
+
|\ ^ |\
|
|
247
|
+
| \ | | \
|
|
248
|
+
| \ | | \
|
|
249
|
+
| 7------+---6
|
|
250
|
+
| | +-- |-- | -> x (u)
|
|
251
|
+
0---+---\--1 |
|
|
252
|
+
\ | \ \ |
|
|
253
|
+
\ | \ \ |
|
|
254
|
+
\| z(w)\|
|
|
255
|
+
4----------5
|
|
256
|
+
|
|
257
|
+
:return: none
|
|
258
|
+
"""
|
|
259
|
+
if self.extrusion_sign > 0:
|
|
260
|
+
offset = self.z_unit_len
|
|
261
|
+
corner_seq = {
|
|
262
|
+
0: {'w_s': -0.5, 'h_s': 0, 'far': True}, # 'blcf'
|
|
263
|
+
1: {'w_s': -0.5, 'h_s': 1, 'far': True}, # 'tlcf'
|
|
264
|
+
2: {'w_s': 0.5, 'h_s': 1, 'far': True}, # 'trcf'
|
|
265
|
+
3: {'w_s': 0.5, 'h_s': 0, 'far': True}, # 'brcf'
|
|
266
|
+
4: {'w_s': -0.5, 'h_s': 0, 'far': False}, # bottom left corner close #'blcc'
|
|
267
|
+
5: {'w_s': -0.5, 'h_s': 1, 'far': False}, # top left corner close #'tlcc'
|
|
268
|
+
6: {'w_s': 0.5, 'h_s': 1, 'far': False}, # top right corner close #'trcc'
|
|
269
|
+
7: {'w_s': 0.5, 'h_s': 0, 'far': False}, # bottom right corner close #'brcc'
|
|
270
|
+
}
|
|
271
|
+
else:
|
|
272
|
+
offset = 0
|
|
273
|
+
corner_seq = {
|
|
274
|
+
0: {'w_s': -0.5, 'h_s': 0, 'far': False}, # 'blcf'
|
|
275
|
+
1: {'w_s': -0.5, 'h_s': 1, 'far': False}, # 'tlcf'
|
|
276
|
+
2: {'w_s': 0.5, 'h_s': 1, 'far': False}, # 'trcf'
|
|
277
|
+
3: {'w_s': 0.5, 'h_s': 0, 'far': False}, # 'brcf'
|
|
278
|
+
4: {'w_s': -0.5, 'h_s': 0, 'far': True}, # bottom left corner close #'blcc'
|
|
279
|
+
5: {'w_s': -0.5, 'h_s': 1, 'far': True}, # top left corner close #'tlcc'
|
|
280
|
+
6: {'w_s': 0.5, 'h_s': 1, 'far': True}, # top right corner close #'trcc'
|
|
281
|
+
7: {'w_s': 0.5, 'h_s': 0, 'far': True}, # bottom right corner close #'brcc'
|
|
282
|
+
}
|
|
283
|
+
for corner_num, corner_dict in corner_seq.items():
|
|
284
|
+
self._add_corner_points(corner_num, *self._calc_first_corner_coords(corner_dict['w_s'], corner_dict['h_s'], corner_dict['far'], offset))
|
|
285
|
+
|
|
286
|
+
def _get_ref_point(self, elem_number, surf_n_dir_str, coord, dist):
|
|
287
|
+
"""
|
|
288
|
+
Gets center point for a surface 'pointing' in the direction surf_n_dir_str of hex with elem_number. It then moves that point into along coordinate coord and by distance dist
|
|
289
|
+
:param elem_number: hex number to take the surface from
|
|
290
|
+
:param surf_n_dir_str: 'direction of surface' (kind of normal), options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
291
|
+
:param coord: direction to more the point in, options are: 'x', 'y', 'z'
|
|
292
|
+
:param dist: distance to move the point, this is positive or negative float
|
|
293
|
+
:return:
|
|
294
|
+
"""
|
|
295
|
+
source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)] # get the indexes 4 points for the surface points in direction surf_n_dir_str
|
|
296
|
+
points_coor = [self.hexes[elem_number][point] for point in source_points] # get the points, they contain coordinates
|
|
297
|
+
xs = []
|
|
298
|
+
ys = []
|
|
299
|
+
zs = []
|
|
300
|
+
for point in points_coor: # get coordinates into list for each axis
|
|
301
|
+
for cor_list, coor in zip([xs, ys, zs], ['x', 'y', 'z']):
|
|
302
|
+
cor_list.append(point[coor])
|
|
303
|
+
p = {'x': np.mean(xs), 'y': np.mean(ys), 'z': np.mean(zs)} # get averages of each axis coordinate, giving the center of the surface
|
|
304
|
+
p[coord] = p[coord] + dist
|
|
305
|
+
return p
|
|
306
|
+
|
|
307
|
+
def _add_elem_with_rot(self, elem_number, surf_n_dir_str, add_ax, angle, ref_p_coor):
|
|
308
|
+
"""
|
|
309
|
+
:param elem_number: idx of start element
|
|
310
|
+
:param surf_n_dir_str: direction of adding element, options are: 'x-', 'x+', 'y-', 'y+', 'z-', 'z+'
|
|
311
|
+
:param ref_p_coor: length of new element in the direction specified above [m]
|
|
312
|
+
:return: index of created element
|
|
313
|
+
"""
|
|
314
|
+
mod_coor = [surf_n_dir_str[0], add_ax]
|
|
315
|
+
# mod_coor.remove(surf_n_dir_str[0]) # get the other two coordinates that arenet the direction of the surface to take
|
|
316
|
+
|
|
317
|
+
angle_rad = np.deg2rad(angle)
|
|
318
|
+
op_dir = surf_n_dir_str[0] + self._op_sign[surf_n_dir_str[1]]
|
|
319
|
+
new_elem = elem_number + math.copysign(1, elem_number)
|
|
320
|
+
self.hexes[new_elem] = {}
|
|
321
|
+
source_points = self.vertices_to_surf[self._sur_names.index(surf_n_dir_str)]
|
|
322
|
+
destination_points = self.vertices_to_surf[self._sur_names.index(op_dir)]
|
|
323
|
+
for d_p, s_p in zip(destination_points + source_points, source_points + source_points):
|
|
324
|
+
self.hexes[new_elem][d_p] = copy.deepcopy(self.hexes[elem_number][s_p])
|
|
325
|
+
for s_p in source_points: # modif_points:
|
|
326
|
+
v1 = self.hexes[new_elem][s_p][mod_coor[0]]
|
|
327
|
+
v2 = self.hexes[new_elem][s_p][mod_coor[1]]
|
|
328
|
+
v1_ofset = ref_p_coor[mod_coor[0]]
|
|
329
|
+
v2_ofset = ref_p_coor[mod_coor[1]]
|
|
330
|
+
v1 = v1 - v1_ofset
|
|
331
|
+
v2 = v2 - v2_ofset
|
|
332
|
+
v1_new = np.cos(angle_rad) * v1 - np.sin(angle_rad) * v2
|
|
333
|
+
v2_new = np.sin(angle_rad) * v1 + np.cos(angle_rad) * v2
|
|
334
|
+
v1 = v1_new + v1_ofset
|
|
335
|
+
v2 = v2_new + v2_ofset
|
|
336
|
+
self.hexes[new_elem][s_p][mod_coor[0]] = v1
|
|
337
|
+
self.hexes[new_elem][s_p][mod_coor[1]] = v2
|
|
338
|
+
self.hexes[new_elem]['ct'] = self.hexes[elem_number]['ct']
|
|
339
|
+
return new_elem
|
|
340
|
+
|
|
341
|
+
def _generate_fqpl(self):
|
|
342
|
+
new_elem = self._add_elem_to_elem(0, 'z+', self.extrusion_sign * self.z_unit_len)
|
|
343
|
+
for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer] - 2):
|
|
344
|
+
new_elem = self._add_elem_to_elem(new_elem, 'z+', self.extrusion_sign * self.z_unit_len)
|
|
345
|
+
coord = 'x'
|
|
346
|
+
ref_point = self._get_ref_point(new_elem, 'z+', coord, self.cctdm.geometry.fqpls.r_bs[self.layer]) # centre of rotation pint.
|
|
347
|
+
n_seg = 10
|
|
348
|
+
angle = 180 / n_seg
|
|
349
|
+
for _ in range(n_seg):
|
|
350
|
+
new_elem = self._add_elem_with_rot(new_elem, 'z+', coord, self.extrusion_sign * angle, ref_point)
|
|
351
|
+
for _ in range(self.cctdm.geometry.fqpls.fndpls[self.layer]):
|
|
352
|
+
new_elem = self._add_elem_to_elem(new_elem, 'z+', -self.extrusion_sign * self.z_unit_len)
|
|
353
|
+
|
|
354
|
+
def _rotate_fqpl(self):
|
|
355
|
+
for h_num, h_dict in self.hexes.items():
|
|
356
|
+
channel_turn = h_dict.pop('ct', None)
|
|
357
|
+
for p_num, p_dict in h_dict.items():
|
|
358
|
+
xx = p_dict['x']
|
|
359
|
+
yy = p_dict['y']
|
|
360
|
+
x = xx * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) - yy * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate
|
|
361
|
+
y = xx * np.sin(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) + yy * np.cos(np.deg2rad(self.cctdm.geometry.fqpls.thetas[self.layer])) # rotated coordinate
|
|
362
|
+
self.hexes[h_num][p_num]['x'] = x
|
|
363
|
+
self.hexes[h_num][p_num]['y'] = y
|
|
364
|
+
self.hexes[h_num]['ct'] = channel_turn
|
|
365
|
+
|
|
366
|
+
def _clean_attr(self):
|
|
367
|
+
del self.cctdm
|
|
368
|
+
del self.layer
|
|
369
|
+
del self.near
|
|
370
|
+
del self.z_unit_len
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class Generate_BREPs:
|
|
374
|
+
def __init__(self, fdm, verbose=True):
|
|
375
|
+
"""
|
|
376
|
+
Class to generate (build) Windings and Formers (WF) of a canted cosine theta (CCT) magnet and save them to brep files
|
|
377
|
+
:param fdm: fiqus data model
|
|
378
|
+
:param verbose: If True more information is printed in python console.
|
|
379
|
+
"""
|
|
380
|
+
self.cctdm = fdm.magnet
|
|
381
|
+
self.model_folder = os.path.join(os.getcwd())
|
|
382
|
+
self.magnet_name = fdm.general.magnet_name
|
|
383
|
+
|
|
384
|
+
self.verbose = verbose
|
|
385
|
+
self.cctwi = WindingsInformation()
|
|
386
|
+
self.cctwi.magnet_name = self.magnet_name
|
|
387
|
+
self.transl_dict = {'vol': '', 'surf': '_bd', 'surf_in': '_in', 'surf_out': '_out', 'cochain': '_cut'}
|
|
388
|
+
self.gu = GmshUtils(self.model_folder, self.verbose)
|
|
389
|
+
self.gu.initialize()
|
|
390
|
+
self.formers = []
|
|
391
|
+
self.air = []
|
|
392
|
+
gmsh.option.setString('Geometry.OCCTargetUnit', 'M')
|
|
393
|
+
|
|
394
|
+
def generate_windings_or_fqpls(self, type_str):
|
|
395
|
+
"""
|
|
396
|
+
Generates windings brep files, as many as number of entries in the yaml file for windings.
|
|
397
|
+
:param type_str: What to generate, options are: 'windings' or 'fqpls'
|
|
398
|
+
:return: None, saves brep to disk
|
|
399
|
+
"""
|
|
400
|
+
if self.verbose:
|
|
401
|
+
print(f'Generating {type_str} started')
|
|
402
|
+
start_time = timeit.default_timer()
|
|
403
|
+
|
|
404
|
+
def generate_geom(worf_in, names_in):
|
|
405
|
+
worf_tags_dict_in = {}
|
|
406
|
+
for worf_num, worf_in in enumerate(worf_in):
|
|
407
|
+
worf_tags_dict_in[worf_num] = self.generate_hex_geom(worf_in)
|
|
408
|
+
gmsh.model.occ.synchronize()
|
|
409
|
+
gmsh.write(os.path.join(self.model_folder, f'{names_in[worf_num]}.brep'))
|
|
410
|
+
gmsh.clear()
|
|
411
|
+
return worf_tags_dict_in
|
|
412
|
+
|
|
413
|
+
if self.cctdm.geometry.windings.names and type_str == 'windings':
|
|
414
|
+
worf = [Winding(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.windings.names)] # worf = winding or fqpl
|
|
415
|
+
names = [name for name in self.cctdm.geometry.windings.names]
|
|
416
|
+
for w in worf:
|
|
417
|
+
if self.cctdm.geometry.air.z_min > w.z_min_winding_layer or w.z_max_winding_layer > self.cctdm.geometry.air.z_max:
|
|
418
|
+
raise ValueError(f'{self.cctdm.geometry.air.name} region dimensions are too s mall to fit the windings turns. Please extend z_min and/or z_max')
|
|
419
|
+
else:
|
|
420
|
+
pass
|
|
421
|
+
worf_tags_dict = generate_geom(worf, names)
|
|
422
|
+
|
|
423
|
+
if self.cctdm.geometry.fqpls.names and type_str == 'fqpls':
|
|
424
|
+
worf = [FQPL(self.cctdm, i) for i, _ in enumerate(self.cctdm.geometry.fqpls.names)] # worf = winding or fqpl
|
|
425
|
+
names = [name for name in self.cctdm.geometry.fqpls.names]
|
|
426
|
+
worf_tags_dict = generate_geom(worf, names)
|
|
427
|
+
|
|
428
|
+
w_z_maxs = []
|
|
429
|
+
w_z_mins = []
|
|
430
|
+
if type_str == 'windings':
|
|
431
|
+
for winding in worf:
|
|
432
|
+
w_z_maxs.append(winding.z_max_winding_layer)
|
|
433
|
+
w_z_mins.append(winding.z_min_winding_layer)
|
|
434
|
+
self.cctwi.windings_avg_length = float(np.mean(w_z_maxs) - np.mean(w_z_mins))
|
|
435
|
+
self.cctwi.windings.names = names
|
|
436
|
+
self.cctwi.windings.t_in.vol_st = []
|
|
437
|
+
self.cctwi.windings.t_in.surf_st = []
|
|
438
|
+
self.cctwi.windings.t_out.vol_st = []
|
|
439
|
+
self.cctwi.windings.t_out.surf_st = []
|
|
440
|
+
self.cctwi.windings.t_in.vol_et = []
|
|
441
|
+
self.cctwi.windings.t_in.surf_et = []
|
|
442
|
+
self.cctwi.windings.t_out.vol_et = []
|
|
443
|
+
self.cctwi.windings.t_out.surf_et = []
|
|
444
|
+
self.cctwi.windings.t_in.lc_et = []
|
|
445
|
+
self.cctwi.windings.t_out.lc_et = []
|
|
446
|
+
self.cctwi.windings.t_in.ndpterms = self.cctdm.geometry.windings.ndpt_ins
|
|
447
|
+
self.cctwi.windings.t_out.ndpterms = self.cctdm.geometry.windings.ndpt_outs
|
|
448
|
+
self.cctwi.windings.t_in.z_air = self.cctdm.geometry.air.z_min
|
|
449
|
+
self.cctwi.windings.t_out.z_air = self.cctdm.geometry.air.z_max
|
|
450
|
+
for winding_num, winding_dict in worf_tags_dict.items():
|
|
451
|
+
first_vol = list(winding_dict.keys())[0]
|
|
452
|
+
last_vol = list(winding_dict.keys())[-1]
|
|
453
|
+
self.cctwi.windings.t_in.vol_st.append(winding_dict[first_vol]['v_t'])
|
|
454
|
+
self.cctwi.windings.t_in.surf_st.append(winding_dict[first_vol]['sf_ts'][0])
|
|
455
|
+
self.cctwi.windings.t_out.vol_st.append(winding_dict[last_vol]['v_t'])
|
|
456
|
+
self.cctwi.windings.t_out.surf_st.append(winding_dict[last_vol]['sf_ts'][2])
|
|
457
|
+
self.cctwi.windings.t_in.vol_et.append(winding_dict[first_vol]['v_t'])
|
|
458
|
+
self.cctwi.windings.t_in.surf_et.append(winding_dict[first_vol]['sf_ts'][0])
|
|
459
|
+
self.cctwi.windings.t_out.vol_et.append(winding_dict[last_vol]['v_t'])
|
|
460
|
+
self.cctwi.windings.t_out.surf_et.append(winding_dict[last_vol]['sf_ts'][2])
|
|
461
|
+
|
|
462
|
+
lc_st_neg_alpha = [[1, 0], [0, 3], [3, 2], [2, 1], [6, 4], [4, 5], [5, 7], [7, 6], [1, 6], [0, 4], [3, 5], [2, 7]]
|
|
463
|
+
lc_st_pos_alpha = [[3, 1], [1, 0], [0, 2], [2, 3], [7, 4], [4, 5], [5, 6], [6, 7], [3, 7], [1, 4], [0, 5], [2, 6]]
|
|
464
|
+
lc_et_neg_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]]
|
|
465
|
+
lc_et_pos_alpha = [[5, 7], [7, 6], [6, 4], [4, 5], [1, 3], [3, 2], [2, 0], [0, 1], [5, 1], [7, 3], [6, 2], [4, 0]]
|
|
466
|
+
# [[1, 2], [2, 3], [3, 4], [4, 1], [5, 6], [6, 7], [7, 8], [8, 5], [1, 5], [2, 6], [3, 7], [4, 8]]
|
|
467
|
+
# [[2, 1], [1, 4], [4, 3], [3, 2], [6, 5], [5, 8], [8, 7], [7, 6], [2, 6], [1, 5], [4, 8], [3, 7]]
|
|
468
|
+
|
|
469
|
+
lc_et_pos_corr = [[1, 0], [0, 3], [3, 2], [2, 1], [5, 4], [4, 7], [7, 6], [6, 5], [1, 5], [0, 4], [3, 7], [2, 6]]
|
|
470
|
+
|
|
471
|
+
self.cctwi.windings.t_in.lc_st = []
|
|
472
|
+
self.cctwi.windings.t_out.lc_st = []
|
|
473
|
+
for alpha in self.cctdm.geometry.windings.alphas:
|
|
474
|
+
if alpha > 0:
|
|
475
|
+
self.cctwi.windings.t_in.lc_st.append(lc_st_pos_alpha)
|
|
476
|
+
self.cctwi.windings.t_out.lc_st.append(lc_st_pos_alpha)
|
|
477
|
+
self.cctwi.windings.t_in.lc_et.append(lc_et_pos_corr)
|
|
478
|
+
self.cctwi.windings.t_out.lc_et.append(lc_et_pos_alpha)
|
|
479
|
+
else:
|
|
480
|
+
self.cctwi.windings.t_in.lc_st.append(lc_st_neg_alpha)
|
|
481
|
+
self.cctwi.windings.t_out.lc_st.append(lc_st_neg_alpha)
|
|
482
|
+
self.cctwi.windings.t_in.lc_et.append(lc_et_neg_alpha)
|
|
483
|
+
self.cctwi.windings.t_out.lc_et.append(lc_et_pos_corr)
|
|
484
|
+
|
|
485
|
+
if self.verbose:
|
|
486
|
+
print(f'Generating {type_str} took {timeit.default_timer() - start_time:.2f} s')
|
|
487
|
+
|
|
488
|
+
def save_volume_info(self):
|
|
489
|
+
if self.cctdm.geometry.fqpls.names:
|
|
490
|
+
fqpls = self.cctdm.geometry.fqpls.names
|
|
491
|
+
else:
|
|
492
|
+
fqpls = []
|
|
493
|
+
|
|
494
|
+
self.cctwi.w_names = self.cctdm.geometry.windings.names
|
|
495
|
+
self.cctwi.f_names = fqpls
|
|
496
|
+
self.cctwi.formers = self.cctdm.geometry.formers.names
|
|
497
|
+
self.cctwi.air = self.cctdm.geometry.air.name
|
|
498
|
+
|
|
499
|
+
volume_info_file = os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.wi')
|
|
500
|
+
uff.write_data_to_yaml(volume_info_file, self.cctwi.dict())
|
|
501
|
+
|
|
502
|
+
def generate_formers(self):
|
|
503
|
+
if self.cctdm.geometry.formers.r_ins:
|
|
504
|
+
if self.verbose:
|
|
505
|
+
print('Generating Formers Started')
|
|
506
|
+
start_time = timeit.default_timer()
|
|
507
|
+
|
|
508
|
+
for f_i, _ in enumerate(self.cctdm.geometry.formers.r_ins):
|
|
509
|
+
z = self.cctdm.geometry.formers.z_mins[f_i]
|
|
510
|
+
dz = self.cctdm.geometry.formers.z_maxs[f_i] - self.cctdm.geometry.formers.z_mins[f_i]
|
|
511
|
+
cylin_out = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_outs[f_i], angle=2 * math.pi)) # add cylinder to align to existing bodies in the pro file
|
|
512
|
+
cylin_in = (3, gmsh.model.occ.addCylinder(0, 0, z, 0, 0, dz, self.cctdm.geometry.formers.r_ins[f_i], angle=2 * math.pi)) # add another cylinder to subtract from the first one to make a tube
|
|
513
|
+
cylinder = gmsh.model.occ.cut([cylin_out], [cylin_in], removeObject=True) # subtract cylinders to make a tube
|
|
514
|
+
self.formers.append(cylinder[0][0]) # keep just the tag and append
|
|
515
|
+
gmsh.model.occ.synchronize()
|
|
516
|
+
gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.formers.names[f_i]}.brep'))
|
|
517
|
+
gmsh.clear()
|
|
518
|
+
if self.verbose:
|
|
519
|
+
print(f'Generating formers took {timeit.default_timer() - start_time:.2f} s')
|
|
520
|
+
|
|
521
|
+
def generate_air(self):
|
|
522
|
+
if self.cctdm.geometry.air.ar:
|
|
523
|
+
if self.verbose:
|
|
524
|
+
print('Generating air started')
|
|
525
|
+
start_time = timeit.default_timer()
|
|
526
|
+
if self.cctdm.geometry.air.sh_type == 'cylinder':
|
|
527
|
+
self.air = [(3, gmsh.model.occ.addCylinder(0, 0, self.cctdm.geometry.air.z_min, 0, 0, self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, angle=2 * math.pi))]
|
|
528
|
+
elif self.cctdm.geometry.air.sh_type == 'cuboid':
|
|
529
|
+
air_box_size = [-self.cctdm.geometry.air.ar / 2, -self.cctdm.geometry.air.ar / 2, self.cctdm.geometry.air.z_min, self.cctdm.geometry.air.ar, self.cctdm.geometry.air.ar,
|
|
530
|
+
self.cctdm.geometry.air.z_max - self.cctdm.geometry.air.z_min] # list of box size with: x, y, z, dx, dy, dz
|
|
531
|
+
self.air = [(3, gmsh.model.occ.addBox(*air_box_size))]
|
|
532
|
+
else:
|
|
533
|
+
raise ValueError(f'Shape type: {self.cctdm.geometry.air.sh_type} is not supported!')
|
|
534
|
+
gmsh.model.occ.synchronize()
|
|
535
|
+
gmsh.write(os.path.join(self.model_folder, f'{self.cctdm.geometry.air.name}.brep'))
|
|
536
|
+
gmsh.clear()
|
|
537
|
+
if self.verbose:
|
|
538
|
+
print(f'Generating air took {timeit.default_timer() - start_time:.2f} s')
|
|
539
|
+
|
|
540
|
+
def generate_regions_file(self):
|
|
541
|
+
if self.verbose:
|
|
542
|
+
print('Generating Regions File Started')
|
|
543
|
+
start_time = timeit.default_timer()
|
|
544
|
+
cctrm = Reg_Mod_FiQ.RegionsModel()
|
|
545
|
+
vrt = 1000000 # volume region tag start
|
|
546
|
+
srt = 2000000 # surface region tag start
|
|
547
|
+
lrt = 3000000 # line region tag start
|
|
548
|
+
# -------- powered ----------
|
|
549
|
+
# volumes
|
|
550
|
+
cctrm.powered['cct'] = Reg_Mod_FiQ.Powered()
|
|
551
|
+
cctrm.powered['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
552
|
+
cctrm.powered['cct'].vol.currents = self.cctdm.solve.windings.currents + self.cctdm.solve.fqpls.currents
|
|
553
|
+
cctrm.powered['cct'].vol.sigmas = self.cctdm.solve.windings.sigmas + self.cctdm.solve.fqpls.sigmas
|
|
554
|
+
cctrm.powered['cct'].vol.mu_rs = self.cctdm.solve.windings.mu_rs + self.cctdm.solve.fqpls.mu_rs
|
|
555
|
+
reg = []
|
|
556
|
+
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
557
|
+
reg.append(vrt)
|
|
558
|
+
vrt += 1
|
|
559
|
+
cctrm.powered['cct'].vol.numbers = reg
|
|
560
|
+
# surfaces
|
|
561
|
+
cctrm.powered['cct'].surf_in.names = [name + self.transl_dict['surf_in'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
562
|
+
cctrm.powered['cct'].surf_out.names = [name + self.transl_dict['surf_out'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
563
|
+
reg = []
|
|
564
|
+
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
565
|
+
reg.append(srt)
|
|
566
|
+
srt += 1
|
|
567
|
+
cctrm.powered['cct'].surf_in.numbers = reg
|
|
568
|
+
reg = []
|
|
569
|
+
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
570
|
+
reg.append(srt)
|
|
571
|
+
srt += 1
|
|
572
|
+
cctrm.powered['cct'].surf_out.numbers = reg
|
|
573
|
+
|
|
574
|
+
# -------- induced ----------
|
|
575
|
+
# volumes
|
|
576
|
+
cctrm.induced['cct'] = Reg_Mod_FiQ.Induced()
|
|
577
|
+
cctrm.induced['cct'].vol.names = [name + self.transl_dict['vol'] for name in self.cctdm.geometry.formers.names]
|
|
578
|
+
cctrm.induced['cct'].vol.sigmas = self.cctdm.solve.formers.sigmas
|
|
579
|
+
cctrm.induced['cct'].vol.mu_rs = self.cctdm.solve.formers.mu_rs
|
|
580
|
+
reg = []
|
|
581
|
+
for _ in self.cctdm.geometry.formers.names:
|
|
582
|
+
reg.append(vrt)
|
|
583
|
+
vrt += 1
|
|
584
|
+
cctrm.induced['cct'].vol.numbers = reg
|
|
585
|
+
|
|
586
|
+
# -------- air ----------
|
|
587
|
+
# volumes
|
|
588
|
+
cctrm.air.vol.name = self.cctdm.geometry.air.name + self.transl_dict['vol']
|
|
589
|
+
cctrm.air.vol.sigma = self.cctdm.solve.air.sigma
|
|
590
|
+
cctrm.air.vol.mu_r = self.cctdm.solve.air.mu_r
|
|
591
|
+
cctrm.air.vol.number = vrt
|
|
592
|
+
vrt += 1
|
|
593
|
+
|
|
594
|
+
# surface
|
|
595
|
+
cctrm.air.surf.name = self.cctdm.geometry.air.name + self.transl_dict['surf']
|
|
596
|
+
cctrm.air.surf.number = srt
|
|
597
|
+
srt += 1
|
|
598
|
+
|
|
599
|
+
# # center line
|
|
600
|
+
# cctrm.air.line.name = 'Center_line'
|
|
601
|
+
# cctrm.air.line.number = lrt
|
|
602
|
+
# lrt += 1
|
|
603
|
+
lrt = srt
|
|
604
|
+
|
|
605
|
+
# --------- cuts -------
|
|
606
|
+
# these need to be done at the end with the highest surface tags
|
|
607
|
+
cctrm.powered['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names]
|
|
608
|
+
reg = []
|
|
609
|
+
for _ in self.cctdm.geometry.windings.names + self.cctdm.geometry.fqpls.names:
|
|
610
|
+
reg.append(lrt)
|
|
611
|
+
lrt += 1
|
|
612
|
+
cctrm.powered['cct'].cochain.numbers = reg
|
|
613
|
+
# induced cuts
|
|
614
|
+
cctrm.induced['cct'].cochain.names = [name + self.transl_dict['cochain'] for name in self.cctdm.geometry.formers.names]
|
|
615
|
+
reg = []
|
|
616
|
+
for _ in self.cctdm.geometry.formers.names:
|
|
617
|
+
reg.append(lrt)
|
|
618
|
+
lrt += 1
|
|
619
|
+
cctrm.induced['cct'].cochain.numbers = reg
|
|
620
|
+
|
|
621
|
+
uff.write_data_to_yaml(os.path.join(self.model_folder, f'{self.cctwi.magnet_name}.regions'), cctrm.dict())
|
|
622
|
+
|
|
623
|
+
if self.verbose:
|
|
624
|
+
print(f'Generating Regions File Took {timeit.default_timer() - start_time:.2f} s')
|
|
625
|
+
|
|
626
|
+
@staticmethod
|
|
627
|
+
def generate_hex_geom(hexes_dict,
|
|
628
|
+
cons_for_lines=[[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7]],
|
|
629
|
+
vol_tag=-1):
|
|
630
|
+
"""
|
|
631
|
+
Generates hexahedra volumes from data in hexes dict
|
|
632
|
+
:param hexes_dict: dictionary of data generated by either Winding or FQPL class in Hexahedrons.py
|
|
633
|
+
:param cons_for_lines: line connection defined as list of list in terms of point numbers to connect in the hexahedron
|
|
634
|
+
:param vol_tag: volume tag to use for generated volume of hexahedron
|
|
635
|
+
:return: dictionary with tags of created volumes, surface loops, surface fillings, closed loops, lines, points and channel turn (i.e. turn number of CCT winding).
|
|
636
|
+
"""
|
|
637
|
+
tags_dict = {}
|
|
638
|
+
for h_num, h_dict in hexes_dict.hexes.items():
|
|
639
|
+
p_ts = [] # Point tags
|
|
640
|
+
channel_turn = h_dict.pop('ct', None)
|
|
641
|
+
for p_num, p_dict in h_dict.items():
|
|
642
|
+
p_ts.append(gmsh.model.occ.addPoint(p_dict['x'], p_dict['y'], p_dict['z']))
|
|
643
|
+
|
|
644
|
+
l_ts = [] # Line tags
|
|
645
|
+
for p_c in cons_for_lines: # point connections to make a line
|
|
646
|
+
l_ts.append(gmsh.model.occ.addLine(p_ts[p_c[0]], p_ts[p_c[1]]))
|
|
647
|
+
|
|
648
|
+
cl_ts = [] # Curved Loops tags
|
|
649
|
+
for l_c in [[0, 1, 2, 3], [4, 5, 6, 7], [3, 8, 7, 11], [1, 9, 5, 10], [0, 8, 4, 9], [2, 11, 6, 10]]: # line connections to make a curved loop
|
|
650
|
+
cl_ts.append(gmsh.model.occ.addCurveLoop([l_ts[l_c[0]], l_ts[l_c[1]], l_ts[l_c[2]], l_ts[l_c[3]]]))
|
|
651
|
+
sf_ts = [] # Surface Filling tags
|
|
652
|
+
for cl_t in cl_ts:
|
|
653
|
+
sf_ts.append(gmsh.model.occ.addSurfaceFilling(cl_t, degree=3, tol2d=0.000001, tol3d=0.00001, tolAng=0.001, tolCurv=0.05))
|
|
654
|
+
sl_t = gmsh.model.occ.addSurfaceLoop(sf_ts, sewing=True)
|
|
655
|
+
v_t = gmsh.model.occ.addVolume([sl_t], vol_tag)
|
|
656
|
+
tags_dict[int(h_num)] = {'v_t': v_t, 'sl_t': sl_t, 'sf_ts': sf_ts, 'cl_ts': cl_ts, 'l_ts': l_ts, 'p_ts': p_ts, 'ct': channel_turn}
|
|
657
|
+
return tags_dict
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class Prepare_BREPs:
|
|
661
|
+
def __init__(self, fdm, verbose=True) -> object:
|
|
662
|
+
"""
|
|
663
|
+
Class to preparing brep files by adding terminals.
|
|
664
|
+
:param fdm: FiQuS data model
|
|
665
|
+
:param verbose: If True more information is printed in python console.
|
|
666
|
+
"""
|
|
667
|
+
|
|
668
|
+
self.cctdm = fdm.magnet
|
|
669
|
+
self.model_folder = os.path.join(os.getcwd())
|
|
670
|
+
self.magnet_name = fdm.general.magnet_name
|
|
671
|
+
|
|
672
|
+
self.verbose = verbose
|
|
673
|
+
self.winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi')
|
|
674
|
+
self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation)
|
|
675
|
+
self.gu = GmshUtils(self.model_folder, self.verbose)
|
|
676
|
+
self.gu.initialize()
|
|
677
|
+
self.model_file = os.path.join(self.model_folder, f'{self.magnet_name}.brep')
|
|
678
|
+
|
|
679
|
+
@staticmethod
|
|
680
|
+
def get_pts_for_sts(surface_tags):
|
|
681
|
+
"""
|
|
682
|
+
Get point tags for surface tags
|
|
683
|
+
:param surface_tags: list of surface tags to get point tags
|
|
684
|
+
:return: list of point tags belonging to the surfaces in the list
|
|
685
|
+
"""
|
|
686
|
+
vol_line_tags = []
|
|
687
|
+
vol_line_tags_i = 0
|
|
688
|
+
vol_line_tags_idx = []
|
|
689
|
+
for surf_tag in surface_tags:
|
|
690
|
+
line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1]
|
|
691
|
+
for line_tag in line_tags_new:
|
|
692
|
+
vol_line_tags_i += 1
|
|
693
|
+
if line_tag not in vol_line_tags:
|
|
694
|
+
vol_line_tags.append(line_tag)
|
|
695
|
+
vol_line_tags_idx.append(vol_line_tags_i)
|
|
696
|
+
point_tags = []
|
|
697
|
+
point_tags_i = 0
|
|
698
|
+
point_tags_idx = []
|
|
699
|
+
for line_tag in vol_line_tags:
|
|
700
|
+
point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1]
|
|
701
|
+
for point_tag in point_tags_new:
|
|
702
|
+
point_tags_i += 1
|
|
703
|
+
if point_tag not in point_tags:
|
|
704
|
+
point_tags.append(point_tag)
|
|
705
|
+
point_tags_idx.append(point_tags_i)
|
|
706
|
+
return point_tags, vol_line_tags_idx, point_tags_idx
|
|
707
|
+
|
|
708
|
+
def straighten_terminal(self, gui=False):
|
|
709
|
+
"""
|
|
710
|
+
Extends winding geom_generators to the air region boundary by extending the geom_generators with 'terminals' geom_generators up to the air boundary surfaces
|
|
711
|
+
By default this extends along the z axis and makes the final surface normal to the z axis.
|
|
712
|
+
:return: None, saves breps with straighten terminals to disk
|
|
713
|
+
"""
|
|
714
|
+
|
|
715
|
+
for i, name in enumerate(self.cctwi.windings.names):
|
|
716
|
+
if self.verbose:
|
|
717
|
+
print(f'Straightening terminals of {name} started')
|
|
718
|
+
start_time = timeit.default_timer()
|
|
719
|
+
gmsh.open(os.path.join(self.model_folder, f'{name}.brep'))
|
|
720
|
+
for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
|
|
721
|
+
vol_tag = terminal.vol_st[i]
|
|
722
|
+
vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
|
|
723
|
+
surf_tag = terminal.surf_st[i]
|
|
724
|
+
if surf_tag not in vol_surf_tags:
|
|
725
|
+
raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
|
|
726
|
+
# surf_point_tags = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
|
|
727
|
+
surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag]) # only 'powered' surface
|
|
728
|
+
vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags) # all tags of hex
|
|
729
|
+
vol_point_dict = {}
|
|
730
|
+
for p_tag in vol_point_tags:
|
|
731
|
+
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
732
|
+
vol_point_dict[p_tag] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
|
|
733
|
+
z = []
|
|
734
|
+
y = []
|
|
735
|
+
x = []
|
|
736
|
+
for p_tag in surf_point_tags: # calculate average z position for the terminal surface
|
|
737
|
+
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
738
|
+
x.append((xmin + xmax) / 2)
|
|
739
|
+
y.append((ymin + ymax) / 2)
|
|
740
|
+
z.append((zmin + zmax) / 2)
|
|
741
|
+
x_avg = np.mean(x)
|
|
742
|
+
y_avg = np.mean(y)
|
|
743
|
+
z_avg = np.mean(z)
|
|
744
|
+
sign_x = [math.copysign(1, x_i - x_avg) for x_i in x]
|
|
745
|
+
sign_y = [math.copysign(1, y_i - y_avg) for y_i in y]
|
|
746
|
+
dist_x = []
|
|
747
|
+
dist_y = []
|
|
748
|
+
for j in range(len(x) - 1):
|
|
749
|
+
dist_x.append(math.sqrt((x[j] - x[j + 1]) ** 2))
|
|
750
|
+
dist_y.append(math.sqrt((y[j] - y[j + 1]) ** 2))
|
|
751
|
+
eq_len = self.cctdm.geometry.windings.wwhs[i] / 2
|
|
752
|
+
y_shift_sign = math.copysign(1, -terminal.z_air * self.cctdm.geometry.windings.alphas[i])
|
|
753
|
+
new_x = [x_avg + s_x * eq_len for s_x in sign_x]
|
|
754
|
+
new_y = [y_avg + s_y * eq_len + y_shift_sign * eq_len - y_shift_sign * dist_y[0] / 2 for s_y in sign_y]
|
|
755
|
+
for p_tag, x_n, y_n in zip(surf_point_tags, new_x, new_y): # assign z_avg to only points on the terminal surface
|
|
756
|
+
vol_point_dict[p_tag]['x'] = x_n
|
|
757
|
+
vol_point_dict[p_tag]['y'] = y_n
|
|
758
|
+
vol_point_dict[p_tag]['z'] = z_avg
|
|
759
|
+
gmsh.model.occ.remove([(3, terminal.vol_st[i])], recursive=True)
|
|
760
|
+
gmsh.model.occ.synchronize()
|
|
761
|
+
hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
|
|
762
|
+
hexes.hexes[0] = {} # only one winding layer is involved per iteration, so hardcoded 0 index
|
|
763
|
+
for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
|
|
764
|
+
hexes.hexes[0][p_i] = p_dict
|
|
765
|
+
Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_st[i], vol_tag=terminal.vol_st[i])
|
|
766
|
+
gmsh.model.occ.synchronize()
|
|
767
|
+
gmsh.write(os.path.join(self.model_folder, f'{name}.brep'))
|
|
768
|
+
if self.verbose:
|
|
769
|
+
print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
|
|
770
|
+
# uff.write_data_to_yaml(self.winding_info_yaml_path, self.cctvi.dict())
|
|
771
|
+
if gui:
|
|
772
|
+
gmsh.model.occ.synchronize()
|
|
773
|
+
self.gu.launch_interactive_GUI()
|
|
774
|
+
else:
|
|
775
|
+
gmsh.clear()
|
|
776
|
+
|
|
777
|
+
def extend_terms(self, operation='extend', gui=False, ):
|
|
778
|
+
self.cctwi = uff.read_data_from_yaml(self.winding_info_file, WindingsInformation) # this is repeated as the operation over the data for the case add, so an original data for extend operation needs to be loaded.
|
|
779
|
+
if operation == 'add':
|
|
780
|
+
for terminal in [self.cctwi.windings.t_in, self.cctwi.windings.t_out]:
|
|
781
|
+
terminal.ndpterms = [1] * len(terminal.ndpterms)
|
|
782
|
+
terminal.z_air = terminal.z_add
|
|
783
|
+
elif operation == 'extend':
|
|
784
|
+
pass
|
|
785
|
+
file_in_postfix = ''
|
|
786
|
+
file_out_postfix = ''
|
|
787
|
+
for i, name in enumerate(self.cctwi.windings.names):
|
|
788
|
+
if self.verbose:
|
|
789
|
+
print(f'Extending terminals of {name} started')
|
|
790
|
+
start_time = timeit.default_timer()
|
|
791
|
+
gmsh.open(os.path.join(self.model_folder, f'{name}{file_in_postfix}.brep'))
|
|
792
|
+
volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
|
|
793
|
+
for oper, terminal in enumerate([self.cctwi.windings.t_in, self.cctwi.windings.t_out]):
|
|
794
|
+
vol_tag = terminal.vol_et[i]
|
|
795
|
+
vol_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
|
|
796
|
+
surf_tag = terminal.surf_et[i]
|
|
797
|
+
if surf_tag not in vol_surf_tags:
|
|
798
|
+
raise ValueError(f'Surface tag of {surf_tag} given for volume in of {vol_tag} does not belong to the volume!')
|
|
799
|
+
surf_point_tags, surf_line_tags_idx, surf_point_tags_idx = self.get_pts_for_sts([surf_tag])
|
|
800
|
+
surf_point_tags = sorted(surf_point_tags)
|
|
801
|
+
vol_point_tags, vol_line_tags_idx, vol_point_tags_idx = self.get_pts_for_sts(vol_surf_tags)
|
|
802
|
+
vol_point_tags = sorted(vol_point_tags)
|
|
803
|
+
surf_point_tags_idx_pw = [i for i, e in enumerate(vol_point_tags) if e in set(surf_point_tags)] # powered
|
|
804
|
+
surf_point_tags_idx_oth = [i for i, e in enumerate(vol_point_tags) if e not in set(surf_point_tags)] # other (opposite powered)
|
|
805
|
+
vol_point_dict = {}
|
|
806
|
+
for idx_pw, p_tag in zip(surf_point_tags_idx_pw, surf_point_tags):
|
|
807
|
+
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
808
|
+
vol_point_dict[idx_pw] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2}
|
|
809
|
+
z_ext = round((terminal.z_air - zmin) / terminal.ndpterms[i], 8)
|
|
810
|
+
for idx_oth, p_tag in zip(surf_point_tags_idx_oth, surf_point_tags):
|
|
811
|
+
xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, p_tag)
|
|
812
|
+
vol_point_dict[idx_oth] = {'x': (xmin + xmax) / 2, 'y': (ymin + ymax) / 2, 'z': (zmin + zmax) / 2 + z_ext}
|
|
813
|
+
hexes = Hexahedrons() # create hex class instance to be able to reuse existing code for generating hexes geom_generators
|
|
814
|
+
for h in range(terminal.ndpterms[i]):
|
|
815
|
+
hexes.hexes[h] = {}
|
|
816
|
+
for p_i, (_, p_dict) in enumerate(vol_point_dict.items()):
|
|
817
|
+
hexes.hexes[h][p_i] = copy.deepcopy(p_dict)
|
|
818
|
+
for p_t, p_dict in vol_point_dict.items():
|
|
819
|
+
p_dict['z'] = p_dict['z'] + z_ext
|
|
820
|
+
for idx_oth in range(4, 8):
|
|
821
|
+
z_air = terminal.z_air
|
|
822
|
+
hexes.hexes[h][idx_oth]['z'] = z_air
|
|
823
|
+
tags_dict = Generate_BREPs.generate_hex_geom(hexes, cons_for_lines=terminal.lc_et[i], vol_tag=-1)
|
|
824
|
+
new_volumes = []
|
|
825
|
+
for _, hex_dict in tags_dict.items():
|
|
826
|
+
new_volumes.append(hex_dict['v_t'])
|
|
827
|
+
gmsh.model.occ.synchronize()
|
|
828
|
+
if oper == 0: # this vol_in is needed for reordering volumes later
|
|
829
|
+
vol_in = new_volumes
|
|
830
|
+
renumbered_vols = []
|
|
831
|
+
other_vols = list(set(volumes) - set(vol_in))
|
|
832
|
+
max_tag = gmsh.model.occ.getMaxTag(3)
|
|
833
|
+
for vol in other_vols:
|
|
834
|
+
gmsh.model.setTag(3, vol, vol + max_tag)
|
|
835
|
+
renumbered_vols.append(vol + max_tag)
|
|
836
|
+
for n_tag, vol in enumerate(reversed(vol_in)):
|
|
837
|
+
gmsh.model.setTag(3, vol, n_tag + 1)
|
|
838
|
+
max_term_tag = len(vol_in) + 1
|
|
839
|
+
spiral_volumes = []
|
|
840
|
+
for n_tag, vol in enumerate(renumbered_vols):
|
|
841
|
+
gmsh.model.setTag(3, vol, n_tag + max_term_tag)
|
|
842
|
+
spiral_volumes.append(n_tag + max_term_tag)
|
|
843
|
+
gmsh.write(os.path.join(self.model_folder, f'{name}{file_out_postfix}.brep'))
|
|
844
|
+
if self.verbose:
|
|
845
|
+
print(f'Straightening terminals of {name} took {timeit.default_timer() - start_time:.2f} s')
|
|
846
|
+
print(f'Writing volume information file: {name}.vi ')
|
|
847
|
+
vi = {'export': spiral_volumes, 'all': [vol[1] for vol in gmsh.model.getEntities(dim=3)]}
|
|
848
|
+
json.dump(vi, open(f"{os.path.join(self.model_folder, name)}.vi", 'w'))
|
|
849
|
+
if self.verbose:
|
|
850
|
+
print(f'Done writing volume information file: {name}.vi ')
|
|
851
|
+
if gui:
|
|
852
|
+
self.gu.launch_interactive_GUI()
|
|
853
|
+
else:
|
|
854
|
+
gmsh.clear()
|
|
855
|
+
|
|
856
|
+
def save_fqpl_vi(self):
|
|
857
|
+
for f_name in self.cctdm.geometry.fqpls.names:
|
|
858
|
+
if self.verbose:
|
|
859
|
+
print(f'Writing volume information file: {f_name}.vi ')
|
|
860
|
+
gmsh.open(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
861
|
+
volumes = [vol[1] for vol in gmsh.model.getEntities(dim=3)]
|
|
862
|
+
export = volumes
|
|
863
|
+
vi = {'export': export, 'all': volumes}
|
|
864
|
+
json.dump(vi, open(f"{os.path.join(self.model_folder, f_name)}.vi", 'w'))
|
|
865
|
+
if self.verbose:
|
|
866
|
+
print(f'Done writing volume information file: {f_name}.vi ')
|
|
867
|
+
gmsh.clear()
|
|
868
|
+
|
|
869
|
+
def fragment(self, gui=False):
|
|
870
|
+
if self.verbose:
|
|
871
|
+
print('Loading files in preparation for fragment operation')
|
|
872
|
+
for f_name in self.cctwi.w_names + self.cctwi.f_names:
|
|
873
|
+
gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
874
|
+
num_vol = np.max([vol[1] for vol in gmsh.model.getEntities(dim=3)])
|
|
875
|
+
for f_name in self.cctwi.formers:
|
|
876
|
+
gmsh.merge(os.path.join(self.model_folder, f'{f_name}.brep'))
|
|
877
|
+
num_vol += 1
|
|
878
|
+
gmsh.merge(os.path.join(self.model_folder, f'{self.cctwi.air}.brep'))
|
|
879
|
+
num_vol += 1
|
|
880
|
+
entities = gmsh.model.getEntities(dim=3)
|
|
881
|
+
if len(entities) != num_vol:
|
|
882
|
+
raise ValueError('Not consistent volumes numbers in brep and json files!')
|
|
883
|
+
objectDimTags = [entities[-1]]
|
|
884
|
+
toolDimTags = entities[:-1]
|
|
885
|
+
# central_line = [(1, 3898)]
|
|
886
|
+
# toolDimTags = toolDimTags+central_line
|
|
887
|
+
if self.verbose:
|
|
888
|
+
print(f'Fragmenting {self.magnet_name} started')
|
|
889
|
+
start_time = timeit.default_timer()
|
|
890
|
+
gmsh.model.occ.fragment(objectDimTags, toolDimTags, removeObject=True, removeTool=True)
|
|
891
|
+
gmsh.model.occ.synchronize()
|
|
892
|
+
if self.verbose:
|
|
893
|
+
print(f'Fragmenting {self.magnet_name} took {timeit.default_timer() - start_time:.2f} s')
|
|
894
|
+
|
|
895
|
+
gmsh.write(self.model_file)
|
|
896
|
+
if gui:
|
|
897
|
+
self.gu.launch_interactive_GUI()
|
|
898
|
+
else:
|
|
899
|
+
gmsh.clear()
|
|
900
|
+
gmsh.finalize()
|
|
901
|
+
|
|
902
|
+
def load_geometry(self, gui=False):
|
|
903
|
+
gmsh.open(self.model_file)
|
|
904
|
+
if gui:
|
|
905
|
+
self.gu.launch_interactive_GUI()
|