fiqus 2024.6.0__py3-none-any.whl → 2024.12.0__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.
Files changed (69) hide show
  1. fiqus/MainFiQuS.py +290 -134
  2. fiqus/data/DataConductor.py +301 -301
  3. fiqus/data/DataFiQuS.py +128 -84
  4. fiqus/data/DataFiQuSCCT.py +150 -150
  5. fiqus/data/DataFiQuSConductor.py +84 -84
  6. fiqus/data/DataFiQuSConductorAC_Strand.py +565 -565
  7. fiqus/data/DataFiQuSMultipole.py +716 -42
  8. fiqus/data/DataFiQuSPancake3D.py +737 -278
  9. fiqus/data/DataMultipole.py +180 -15
  10. fiqus/data/DataRoxieParser.py +90 -51
  11. fiqus/data/DataSettings.py +121 -0
  12. fiqus/data/DataWindingsCCT.py +37 -37
  13. fiqus/data/RegionsModelFiQuS.py +18 -6
  14. fiqus/geom_generators/GeometryCCT.py +905 -905
  15. fiqus/geom_generators/GeometryConductorAC_Strand.py +1391 -1391
  16. fiqus/geom_generators/GeometryMultipole.py +1827 -227
  17. fiqus/geom_generators/GeometryPancake3D.py +316 -117
  18. fiqus/geom_generators/GeometryPancake3DUtils.py +549 -0
  19. fiqus/getdp_runners/RunGetdpCCT.py +4 -4
  20. fiqus/getdp_runners/RunGetdpConductorAC_Strand.py +201 -201
  21. fiqus/getdp_runners/RunGetdpMultipole.py +115 -42
  22. fiqus/getdp_runners/RunGetdpPancake3D.py +28 -6
  23. fiqus/mains/MainCCT.py +2 -2
  24. fiqus/mains/MainConductorAC_Strand.py +132 -132
  25. fiqus/mains/MainMultipole.py +113 -62
  26. fiqus/mains/MainPancake3D.py +63 -23
  27. fiqus/mesh_generators/MeshCCT.py +209 -209
  28. fiqus/mesh_generators/MeshConductorAC_Strand.py +656 -656
  29. fiqus/mesh_generators/MeshMultipole.py +1243 -181
  30. fiqus/mesh_generators/MeshPancake3D.py +275 -192
  31. fiqus/parsers/ParserCOND.py +825 -0
  32. fiqus/parsers/ParserDAT.py +16 -16
  33. fiqus/parsers/ParserGetDPOnSection.py +212 -212
  34. fiqus/parsers/ParserGetDPTimeTable.py +134 -134
  35. fiqus/parsers/ParserMSH.py +53 -53
  36. fiqus/parsers/ParserPOS.py +214 -214
  37. fiqus/parsers/ParserRES.py +142 -142
  38. fiqus/plotters/PlotPythonCCT.py +133 -133
  39. fiqus/plotters/PlotPythonConductorAC.py +855 -840
  40. fiqus/plotters/PlotPythonMultipole.py +18 -18
  41. fiqus/post_processors/PostProcessCCT.py +440 -440
  42. fiqus/post_processors/PostProcessConductorAC.py +49 -49
  43. fiqus/post_processors/PostProcessMultipole.py +353 -229
  44. fiqus/post_processors/PostProcessPancake3D.py +8 -13
  45. fiqus/pre_processors/PreProcessCCT.py +175 -175
  46. fiqus/pro_assemblers/ProAssembler.py +14 -6
  47. fiqus/pro_material_functions/ironBHcurves.pro +246 -246
  48. fiqus/pro_templates/combined/CCT_template.pro +274 -274
  49. fiqus/pro_templates/combined/ConductorAC_template.pro +1025 -1025
  50. fiqus/pro_templates/combined/Multipole_template.pro +1694 -126
  51. fiqus/pro_templates/combined/Pancake3D_template.pro +2294 -1103
  52. fiqus/pro_templates/combined/TSA_materials.pro +162 -0
  53. fiqus/pro_templates/combined/materials.pro +36 -18
  54. fiqus/utils/Utils.py +508 -110
  55. fiqus/utils/update_data_settings.py +33 -0
  56. fiqus-2024.12.0.dist-info/METADATA +130 -0
  57. fiqus-2024.12.0.dist-info/RECORD +84 -0
  58. {fiqus-2024.6.0.dist-info → fiqus-2024.12.0.dist-info}/WHEEL +1 -1
  59. tests/test_FiQuS.py +1 -1
  60. tests/test_geometry_generators.py +101 -2
  61. tests/test_mesh_generators.py +154 -1
  62. tests/test_solvers.py +115 -21
  63. tests/utils/fiqus_test_classes.py +85 -21
  64. tests/utils/generate_reference_files_ConductorAC.py +57 -57
  65. tests/utils/generate_reference_files_Pancake3D.py +4 -5
  66. tests/utils/helpers.py +97 -97
  67. fiqus-2024.6.0.dist-info/METADATA +0 -103
  68. fiqus-2024.6.0.dist-info/RECORD +0 -79
  69. {fiqus-2024.6.0.dist-info → fiqus-2024.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,549 @@
1
+ import math
2
+ from enum import Enum
3
+ from typing import List, Tuple, Dict
4
+
5
+ import os
6
+ import numpy as np
7
+ import gmsh
8
+ import copy
9
+
10
+ import matplotlib.pyplot as plt
11
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
12
+
13
+ from fiqus.parsers.ParserCOND import ParserCOND
14
+
15
+ class direction(Enum):
16
+ """
17
+ A class to specify direction easily.
18
+ """
19
+
20
+ ccw = 0
21
+ cw = 1
22
+
23
+ class coordinate(Enum):
24
+ """
25
+ A class to specify coordinate types easily.
26
+ """
27
+
28
+ rectangular = 0
29
+ cylindrical = 1
30
+ spherical = 2
31
+
32
+ class point:
33
+ """
34
+ This is a class for creating points in GMSH. It supports rectangular and cylindrical
35
+ coordinates. Moreover, vector operations are supported.
36
+
37
+ :param r0: x, r, or r (default: 0.0)
38
+ :type r0: float, optional
39
+ :param r1: y, theta, or theta (default: 0.0)
40
+ :type r1: float, optional
41
+ :param r2: z, z, or phi (default: 0.0)
42
+ :type r2: float, optional
43
+ :param type: coordinate type (default: coordinate.rectangular)
44
+ :type type: coordinate, optional
45
+ """
46
+
47
+ def __init__(self, r0=0.0, r1=0.0, r2=0.0, type=coordinate.rectangular) -> None:
48
+
49
+ self.type = type # Store 'type' as an instance attribute
50
+
51
+ if type is coordinate.rectangular:
52
+ self.x = r0
53
+ self.y = r1
54
+ self.z = r2
55
+
56
+ self.r = math.sqrt(self.x**2 + self.y**2)
57
+ self.theta = math.atan2(self.y, self.x)
58
+ elif type is coordinate.cylindrical:
59
+ self.r = r0
60
+ self.theta = r1
61
+ self.x = self.r * math.cos(self.theta)
62
+ self.y = self.r * math.sin(self.theta)
63
+ self.z = r2
64
+ elif type is coordinate.spherical:
65
+ raise ValueError("Spherical coordinates are not supported yet!")
66
+ else:
67
+ raise ValueError("Improper coordinate type value!")
68
+
69
+ self.tag = gmsh.model.occ.addPoint(self.x, self.y, self.z)
70
+
71
+ def __repr__(self):
72
+ """
73
+ Returns the string representation of the point.
74
+
75
+ :return: string representation of the point
76
+ :rtype: str
77
+ """
78
+ return "point(%r, %r, %r, %r)" % (self.x, self.y, self.z, self.type)
79
+
80
+ def __abs__(self):
81
+ """
82
+ Returns the magnitude of the point vector.
83
+
84
+ :return: the magnitude of the point vector
85
+ :rtype: float
86
+ """
87
+ return math.hypot(self.x, self.y, self.z)
88
+
89
+ def __add__(self, other):
90
+ """
91
+ Returns the summation of two point vectors.
92
+
93
+ :param other: point vector to be added
94
+ :type other: point
95
+ :return: the summation of two point vectors
96
+ :rtype: point
97
+ """
98
+ x = self.x + other.x
99
+ y = self.y + other.y
100
+ z = self.z + other.z
101
+ return point(x, y, z, coordinate.rectangular)
102
+
103
+ def __mul__(self, scalar):
104
+ """
105
+ Returns the product of a point vector and a scalar.
106
+
107
+ :param scalar: a scalar value
108
+ :type scalar: float
109
+ :return: point
110
+ :rtype: point
111
+ """
112
+ return point(
113
+ self.x * scalar,
114
+ self.y * scalar,
115
+ self.z * scalar,
116
+ coordinate.rectangular,
117
+ )
118
+
119
+
120
+
121
+ class spiralCurve:
122
+ """
123
+ A class to create a spiral curves parallel to XY plane in GMSH. The curve is defined
124
+ by a spline and it is divided into sub-curves. Sub-curves are used because it makes
125
+ the geometry creation process easier.
126
+
127
+ :param innerRadius: inner radius
128
+ :type innerRadius: float
129
+ :param gap: gap after each turn
130
+ :type gap: float
131
+ :param turns: number of turns
132
+ :type turns: float
133
+ :param z: z coordinate
134
+ :type z: float
135
+ :param initialTheta: initial theta angle in radians
136
+ :type initialTheta: float
137
+ :param direction: direction of the spiral (default: direction.ccw)
138
+ :type direction: direction, optional
139
+ :param cutPlaneNormal: normal vector of the plane that will cut the spiral curve
140
+ (default: None)
141
+ :type cutPlaneNormal: tuple[float, float, float], optional
142
+ """
143
+
144
+ # If the number of points used per turn is n, then the number of sections per turn
145
+ # is n-1. They set the resolution of the spiral curve. It sets the limit of the
146
+ # precision of the float number of turns that can be used to create the spiral
147
+ # curve. The value below might be modified in Geometry.__init__ method.
148
+ sectionsPerTurn = 16
149
+
150
+ # There will be curvesPerTurn curve entities per turn. It will be effectively the
151
+ # number of volumes per turn in the end. The value below might be modified in
152
+ # Geometry.__init__ method.
153
+ curvesPerTurn = 2
154
+
155
+ def __init__(
156
+ self,
157
+ innerRadius,
158
+ gap,
159
+ turns,
160
+ z,
161
+ initialTheta,
162
+ transitionNotchAngle,
163
+ geo,
164
+ direction=direction.ccw, # TODO code this to understand cw direction
165
+ cutPlaneNormal=Tuple[float, float, float]
166
+ ) -> None:
167
+ spt = self.sectionsPerTurn # just to make the code shorter
168
+ self.turnRes = 1 / spt # turn resolution
169
+ cpt = self.curvesPerTurn # just to make the code shorter
170
+ self.turns = turns
171
+ self.geo = geo
172
+ # =============================================================================
173
+ # GENERATING POINTS STARTS ====================================================
174
+ # =============================================================================
175
+ print('The theta is')
176
+ print(initialTheta)
177
+
178
+ print('This is the status of the cutPlane')
179
+ print(cutPlaneNormal)
180
+ # Calculate the coordinates of the points that define the spiral curve:
181
+ if direction is direction.ccw:
182
+ # If the spiral is counter-clockwise, the initial theta angle decreases,
183
+ # and r increases as the theta angle decreases.
184
+ multiplier = 1
185
+ elif direction is direction.cw:
186
+ # If the spiral is clockwise, the initial theta angle increases, and r
187
+ # increases as the theta angle increases.
188
+ multiplier = -1
189
+
190
+ NofPointsPerTurn = int(spt + 1)
191
+ thetaArrays = []
192
+ for turn in range(1, int(self.turns) + 1):
193
+ thetaArrays.append(
194
+ np.linspace(
195
+ initialTheta + (turn - 1) * 2 * math.pi * multiplier,
196
+ initialTheta + (turn) * 2 * math.pi * multiplier,
197
+ NofPointsPerTurn,
198
+ )
199
+ )
200
+
201
+ thetaArrays.append(
202
+ np.linspace(
203
+ initialTheta + (turn) * 2 * math.pi * multiplier,
204
+ initialTheta + (self.turns) * 2 * math.pi * multiplier,
205
+ round(spt * (self.turns - turn) + 1),
206
+ )
207
+ )
208
+
209
+ if cutPlaneNormal is not None:
210
+ # If the cutPlaneNormal is specified, the spiral curve will be cut by a
211
+ # plane that is normal to the cutPlaneNormal vector and passes through the
212
+ # origin.
213
+
214
+ alpha = math.atan2(cutPlaneNormal[1], cutPlaneNormal[0]) - math.pi / 2
215
+ alpha2 = alpha + math.pi
216
+
217
+ listOfBreakPoints = []
218
+ for turn in range(1, int(self.turns) + 2):
219
+ breakPoint1 = alpha + (turn - 1) * 2 * math.pi * multiplier
220
+ breakPoint2 = alpha2 + (turn - 1) * 2 * math.pi * multiplier
221
+ if (
222
+ breakPoint1 > initialTheta
223
+ and breakPoint1 < initialTheta + 2 * math.pi * self.turns
224
+ ):
225
+ listOfBreakPoints.append(breakPoint1)
226
+ if (
227
+ breakPoint2 > initialTheta
228
+ and breakPoint2 < initialTheta + 2 * math.pi * self.turns
229
+ ):
230
+ listOfBreakPoints.append(breakPoint2)
231
+
232
+ thetaArrays.append(np.array(listOfBreakPoints))
233
+
234
+ theta = np.concatenate(thetaArrays)
235
+ theta = np.round(theta, 10)
236
+ theta = np.unique(theta)
237
+ theta = np.sort(theta)
238
+ theta = theta[::multiplier]
239
+
240
+ r = innerRadius + (theta - initialTheta) / (2 * math.pi) * (gap) * multiplier
241
+ z = np.ones(theta.shape) * z
242
+
243
+ # Create the points and store their tags:
244
+ points = [] # point objects
245
+ pointTags = [] # point tags
246
+ breakPointObjectsDueToCutPlane = [] # only used if cutPlaneNormal is not None
247
+ breakPointTagsDueToCutPlane = [] # only used if cutPlaneNormal is not None
248
+ pointObjectsWithoutBreakPoints = [] # only used if cutPlaneNormal is not None
249
+ pointTagsWithoutBreakPoints = [] # only used if cutPlaneNormal is not None
250
+ breakPointObjectsDueToTransition = []
251
+ breakPointTagsDueToTransition = []
252
+ coordinateList = []
253
+
254
+ for j in range(len(theta)):
255
+ pointObject = point(r[j], theta[j], z[j], coordinate.cylindrical)
256
+ [x_c, y_c, z_c] = [r[j], theta[j], z[j]]
257
+ coordinateList.append([x_c, y_c, z_c])
258
+ points.append(pointObject)
259
+ pointTags.append(pointObject.tag)
260
+ if cutPlaneNormal is not None:
261
+ if theta[j] in listOfBreakPoints:
262
+ breakPointObjectsDueToCutPlane.append(pointObject)
263
+ breakPointTagsDueToCutPlane.append(pointObject.tag)
264
+ else:
265
+ pointObjectsWithoutBreakPoints.append(pointObject)
266
+ pointTagsWithoutBreakPoints.append(pointObject.tag)
267
+
268
+ # identify if the point is a break point due to the layer transition:
269
+ angle1 = initialTheta + (2 * math.pi - transitionNotchAngle) * multiplier
270
+ angle2 = (
271
+ initialTheta
272
+ + ((self.turns % 1) * 2 * math.pi + transitionNotchAngle) * multiplier
273
+ )
274
+ if math.isclose(
275
+ math.fmod(theta[j], 2 * math.pi), angle1, abs_tol=1e-6
276
+ ) or math.isclose(math.fmod(theta[j], 2 * math.pi), angle2, abs_tol=1e-6):
277
+ breakPointObjectsDueToTransition.append(pointObject)
278
+ breakPointTagsDueToTransition.append(pointObject.tag)
279
+
280
+ # Logic to break the points up into relevant geom coordinates
281
+ # Brick points structure (for X-Y plane only for now):
282
+ # [[[x1, y1, z1], [x2, y2, z2], [x3, y3, z3], [x4, y4, z4]], ...]
283
+ # Theoretically, very easy to extend to 8 points knowing the height of the
284
+
285
+
286
+ # Defining the coordinate lists to which the points are to be added
287
+
288
+ # winding one covers the list of points in the domain of theta [k, pi*k], where k is an integer number
289
+ winding_1 = []
290
+ # winding one covers the list of points in the domain of theta [pi*k, 2pi*k], where k is an integer number
291
+ winding_2 = []
292
+ # winding one covers the list of points in the domain of theta [k, pi*k], where k is an integer number
293
+ winding_3 = []
294
+ # winding one covers the list of points in the domain of theta [pi*k, 2pi*k], where k is an integer number
295
+ winding_4 = []
296
+
297
+ for i in range(len(theta)-1): # range is reduced as no brick can be created starting at the last point
298
+ # print(i)
299
+ # Assuming theta is a numpy array and you're looking for the index of a value close to pi
300
+ value_to_find = theta[i]+2*np.pi
301
+ tolerance = 1e-10 # Define a small tolerance
302
+ # Find indices where the condition is true
303
+ indices = np.where(np.abs(theta - value_to_find) < tolerance)[0]
304
+ z = self.geo.wi.h/2
305
+ z_p = z
306
+ z_n = -z
307
+
308
+ if len(indices) > 0:
309
+ windingUpIndex = indices[0] # Take the first index if there are multiple matches
310
+
311
+ try:
312
+ x_1 = r[i] * np.cos(theta[i])
313
+ y_1 = r[i] * np.sin(theta[i])
314
+ x_2 = r[i + 1] * np.cos(theta[i + 1])
315
+ y_2 = r[i + 1] * np.sin(theta[i + 1])
316
+ x_3 = r[windingUpIndex] * np.cos(theta[windingUpIndex])
317
+ y_3 = r[windingUpIndex] * np.sin(theta[windingUpIndex])
318
+ x_4 = r[windingUpIndex + 1] * np.cos(theta[windingUpIndex + 1])
319
+ y_4 = r[windingUpIndex + 1] * np.sin(theta[windingUpIndex + 1])
320
+
321
+ addPoints = [[x_1, y_1, z_n], [x_2, y_2, z_n], [x_4, y_4, z_n], [x_3, y_3, z_n], [x_1, y_1, z_p], [x_2, y_2, z_p], [x_4, y_4, z_p], [x_3, y_3, z_p]]
322
+
323
+ k = int(theta[i] / (2 * np.pi))
324
+
325
+ angle_in_current_turn = round(theta[i] % (2 * np.pi), 6)
326
+ angle_second_brick_point = round(theta[i+1] % (2 * np.pi), 6)
327
+
328
+ if (((round(angle_in_current_turn, 6) <= round(np.pi, 6)) or (round(angle_in_current_turn, 6) == round(2*np.pi, 6))) and (round(angle_second_brick_point, 6) <= round(np.pi, 6))):
329
+ k = int(round(theta[i] / (2 * np.pi)))
330
+ if k % 2 == 0:
331
+ winding_1.append(addPoints)
332
+ else:
333
+ winding_3.append(addPoints)
334
+
335
+ if (round(angle_in_current_turn, 6) >= round(np.pi, 6) and (round(angle_second_brick_point, 6) >= round(np.pi, 6) or round(angle_second_brick_point, 6) == 0.0)):
336
+
337
+ if k % 2 == 0:
338
+ winding_2.append(addPoints)
339
+ else:
340
+ winding_4.append(addPoints)
341
+
342
+ except IndexError:
343
+ print('All of the winding conductor points have been found')
344
+
345
+ x_coords = []
346
+ y_coords = []
347
+ z_coords = []
348
+
349
+ # Writing the conductor file
350
+ windingPointList = [winding_1, winding_2, winding_3, winding_4]
351
+
352
+ if self.geo.conductorWrite:
353
+ dict_cond_sample = {0: {'SHAPE': 'BR8', 'XCENTRE': '0.0', 'YCENTRE': '0.0', 'ZCENTRE': '0.0', 'PHI1': '0.0', 'THETA1': '0.0', 'PSI1': '0.0', 'XCEN2': '0.0', 'YCEN2': '0.0', 'ZCEN2': '0.0', 'THETA2': '0.0', 'PHI2': '0.0', 'PSI2': '0.0', 'XP1': '-0.879570', 'YP1': '-0.002940', 'ZP1': '-1.131209', 'XP2': '-0.879570', 'YP2': '0.002940', 'ZP2': '-1.131209', 'XP3': '-0.881381', 'YP3': '0.002940', 'ZP3': '-1.114205', 'XP4': '-0.881381', 'YP4': '-0.002940', 'ZP4': '-1.114205', 'XP5': '-0.861227', 'YP5': '-0.002972', 'ZP5': '-1.129183', 'XP6': '-0.861208', 'YP6': '0.002908', 'ZP6': '-1.129182', 'XP7': '-0.863294', 'YP7': '0.002912', 'ZP7': '-1.112210', 'XP8': '-0.863313', 'YP8': '-0.002968', 'ZP8': '-1.112211', 'CURD': '201264967.975494', 'SYMMETRY': '1', 'DRIVELABEL': 'drive 0', 'IRXY': '0', 'IRYZ': '0', 'IRZX': '0', 'TOLERANCE': '1e-6'}}
354
+
355
+ # Use a dictionary to manage dict_cond_1, dict_cond_2, etc.
356
+ dict_conds = {}
357
+
358
+ k = 1 # Start from 1 for dict_cond_1, dict_cond_2, ...
359
+ for winding in windingPointList:
360
+ n = 0
361
+ dict_conds[k] = {}
362
+ for brick in winding:
363
+ dict_conds[k][n] = copy.deepcopy(dict_cond_sample[0])
364
+ for pointIndex in range(8):
365
+ dict_conds[k][n][f'XP{pointIndex + 1}'] = str(brick[pointIndex][0])
366
+ dict_conds[k][n][f'YP{pointIndex + 1}'] = str(brick[pointIndex][1])
367
+ dict_conds[k][n][f'ZP{pointIndex + 1}'] = str(brick[pointIndex][2])
368
+ n += 1
369
+ k += 1
370
+
371
+ target_dir = os.path.join(os.getcwd(), 'tests', '_outputs', 'parsers')
372
+
373
+ # Ensure the target directory exists
374
+ os.makedirs(target_dir, exist_ok=True)
375
+ for i in range (1, 5):
376
+ # Define the output file path
377
+ out_file = os.path.join(target_dir, f'winding_{i}.cond')
378
+ input_dict = dict_conds[i]
379
+ list_of_shapes = ['BR8']
380
+ for shape in list_of_shapes:
381
+ pc = ParserCOND()
382
+ print(f'the input dictionary is {input_dict}')
383
+ pc.write_cond(input_dict, out_file)
384
+
385
+ # =============================================================================
386
+ # GENERATING POINTS ENDS ======================================================
387
+ # =============================================================================
388
+
389
+ # =============================================================================
390
+ # GENERATING SPLINES STARTS ===================================================
391
+ # =============================================================================
392
+
393
+ # Create the spline with the points:
394
+ spline = gmsh.model.occ.addSpline(pointTags)
395
+
396
+ # Split the spline into sub-curves:
397
+ sectionsPerCurve = int(spt / cpt)
398
+
399
+ # Create a list of point tags that will split the spline:
400
+ # Basically, they are the points to divide the spirals into sectionsPerCurve
401
+ # turns. However, some other points are also included to support the float
402
+ # number of turns. It is best to visually look at the divisions with
403
+ # gmsh.fltk.run() to understand why the split points are chosen the way they are
404
+ # selected.
405
+ if cutPlaneNormal is None:
406
+ pointObjectsWithoutBreakPoints = points
407
+ pointTagsWithoutBreakPoints = pointTags
408
+
409
+ splitPointTags = list(
410
+ set(pointTagsWithoutBreakPoints[:-1:sectionsPerCurve])
411
+ | set(pointTagsWithoutBreakPoints[-spt - 1 :: -spt])
412
+ | set(breakPointTagsDueToCutPlane)
413
+ | set(breakPointTagsDueToTransition)
414
+ )
415
+ splitPointTags = sorted(splitPointTags)
416
+ # Remove the first element of the list (starting point):
417
+ _, *splitPointTags = splitPointTags
418
+
419
+ # Also create a list of corresponding point objects:
420
+ splitPoints = list(
421
+ set(pointObjectsWithoutBreakPoints[:-1:sectionsPerCurve])
422
+ | set(pointObjectsWithoutBreakPoints[-spt - 1 :: -spt])
423
+ | set(breakPointObjectsDueToCutPlane)
424
+ | set(breakPointObjectsDueToTransition)
425
+ )
426
+ splitPoints = sorted(splitPoints, key=lambda x: x.tag)
427
+ # Remove the first element of the list (starting point):
428
+ _, *splitPoints = splitPoints
429
+
430
+ # Split the spline:
431
+ dims = [0] * len(splitPointTags)
432
+ _, splines = gmsh.model.occ.fragment(
433
+ [(1, spline)],
434
+ list(zip(dims, splitPointTags)),
435
+ removeObject=True,
436
+ removeTool=True,
437
+ )
438
+ splines = splines[0]
439
+ self.splineTags = [j for _, j in splines]
440
+
441
+ # Note the turn number of each spline. This will be used in getSplineTag and
442
+ # getSplineTags methods.
443
+ self.splineTurns = []
444
+ for i in range(len(self.splineTags)):
445
+ if i == 0:
446
+ startPoint = points[0]
447
+ endPoint = splitPoints[0]
448
+ elif i == len(self.splineTags) - 1:
449
+ startPoint = splitPoints[-1]
450
+ endPoint = points[-1]
451
+ else:
452
+ startPoint = splitPoints[i - 1]
453
+ endPoint = splitPoints[i]
454
+
455
+ startTurn = (startPoint.theta - initialTheta) / (2 * math.pi)
456
+ startTurn = round(startTurn / self.turnRes) * self.turnRes
457
+ endTurn = (endPoint.theta - initialTheta) / (2 * math.pi)
458
+ endTurn = round(endTurn / self.turnRes) * self.turnRes
459
+
460
+ if direction is direction.ccw:
461
+ self.splineTurns.append((startTurn, endTurn))
462
+ else:
463
+ self.splineTurns.append((-startTurn, -endTurn))
464
+
465
+ # Check if splineTurn tuples starts with the small turn number:
466
+ for i in range(len(self.splineTurns)):
467
+ self.splineTurns[i] = sorted(self.splineTurns[i])
468
+
469
+ # =============================================================================
470
+ # GENERATING SPLINES ENDS =====================================================
471
+ # =============================================================================
472
+
473
+ # Find start and end points of the spiral curve:
474
+ gmsh.model.occ.synchronize() # synchronize the model to make getBoundary work
475
+ self.startPointTag = gmsh.model.getBoundary([(1, self.getSplineTag(0))])[1][1]
476
+ self.endPointTag = gmsh.model.getBoundary(
477
+ [(1, self.getSplineTag(self.turns, endPoint=True))]
478
+ )[1][1]
479
+
480
+ def getSplineTag(self, turn, endPoint=False):
481
+ """
482
+ Returns the spline tag at a specific turn. It returns the spline tag of the
483
+ section that is on the turn except its end point.
484
+
485
+ :param turn: turn number (it can be a float)
486
+ :type turn: float
487
+ :param endPoint: if True, return the spline tag of the section that is on the
488
+ turn including its end point but not its start point (default: False)
489
+ :type endPoint: bool, optional
490
+ :return: spline tag
491
+ """
492
+ if endPoint:
493
+ for i, r in enumerate(self.splineTurns):
494
+ if r[0] + (self.turnRes / 2) < turn <= r[1] + (self.turnRes / 2):
495
+ return self.splineTags[i]
496
+ else:
497
+ for i, r in enumerate(self.splineTurns):
498
+ if r[0] - (self.turnRes / 2) <= turn < r[1] - (self.turnRes / 2):
499
+ return self.splineTags[i]
500
+
501
+ def getPointTag(self, turn, endPoint=False):
502
+ """
503
+ Returns the point object at a specific turn.
504
+
505
+ :param turn: turn number (it can be a float)
506
+ :type turn: float
507
+ :return: point object
508
+ :rtype: point
509
+ """
510
+ if turn < 0 or turn > self.turns:
511
+ raise ValueError("Turn number is out of range!")
512
+
513
+ if turn == 0:
514
+ return self.startPointTag
515
+ elif turn == self.turns:
516
+ return self.endPointTag
517
+ else:
518
+ curveTag = self.getSplineTag(turn, endPoint=endPoint)
519
+ if endPoint:
520
+ points = gmsh.model.getBoundary([(1, curveTag)])
521
+ return points[1][1]
522
+ else:
523
+ points = gmsh.model.getBoundary([(1, curveTag)])
524
+ return points[0][1]
525
+
526
+ def getSplineTags(self, turnStart=None, turnEnd=None):
527
+ """
528
+ Get the spline tags from a specific turn to another specific turn. If turnStart
529
+ and turnEnd are not specified, it returns all the spline tags.
530
+
531
+ :param turnStart: start turn number (it can be a float) (default: None)
532
+ :type turnStart: float, optional
533
+ :param turnEnd: end turn number (it can be a float) (default: None)
534
+ :type turnEnd: float, optional
535
+ :return: spline tags
536
+ :rtype: list[int]
537
+ """
538
+ if turnStart is None and turnEnd is None:
539
+ return self.splineTags
540
+ elif turnStart is None or turnEnd is None:
541
+ raise ValueError(
542
+ "turnStart and turnEnd must be both specified or both not specified."
543
+ " You specified only one of them."
544
+ )
545
+ else:
546
+ start = self.splineTags.index(self.getSplineTag(turnStart, False))
547
+ end = self.splineTags.index(self.getSplineTag(turnEnd, True)) + 1
548
+ return self.splineTags[start:end]
549
+
@@ -11,14 +11,14 @@ from fiqus.pro_assemblers.ProAssembler import ASS_PRO as ass_pro
11
11
 
12
12
 
13
13
  class RunGetdpCCT:
14
- def __init__(self, fdm, settings: dict = None, verbose=True):
14
+ def __init__(self, fdm, GetDP_path: dict = None, verbose=True):
15
15
  """
16
16
  Class to preparing brep files by adding terminals.
17
17
  :param fdm: FiQuS data model
18
18
  :param verbose: If True more information is printed in python console.
19
19
  """
20
20
  self.cctdm = fdm.magnet
21
- self.settings = settings
21
+ self.GetDP_path = GetDP_path
22
22
  self.model_folder = os.path.join(os.getcwd())
23
23
  self.magnet_name = fdm.general.magnet_name
24
24
  self.mesh_folder = Path(self.model_folder).parent
@@ -26,7 +26,7 @@ class RunGetdpCCT:
26
26
  self.cctrm = uff.read_data_from_yaml(os.path.join(self.model_folder, f'{self.magnet_name}.regions'), RegionsModel)
27
27
  self.ap = ass_pro(os.path.join(self.model_folder, self.magnet_name))
28
28
  self.gu = GmshUtils(self.model_folder, self.verbose)
29
- self.gu.initialize()
29
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
30
30
  self.pos_names = []
31
31
  # for variable, volume, file_ext in zip(self.cctdm.solve.variables, self.cctdm.solve.volumes, self.cctdm.solve.file_exts):
32
32
  # self.pos_names.append(f'{variable}_{volume}.{file_ext}')
@@ -55,7 +55,7 @@ class RunGetdpCCT:
55
55
  if self.verbose:
56
56
  print('Solving Started !!!')
57
57
  start_time = timeit.default_timer()
58
- getdp_binary = self.settings['GetDP_path']
58
+ getdp_binary = self.GetDP_path
59
59
  gmsh.onelab.run(f"{self.magnet_name}", f"{getdp_binary} {os.path.join(self.model_folder, self.magnet_name)}.pro {command} -msh {os.path.join(self.mesh_folder, self.magnet_name)}.msh")
60
60
  gmsh.onelab.setChanged("GetDP", 0)
61
61