fiqus 2025.2.0__py3-none-any.whl → 2025.11.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 (54) hide show
  1. fiqus/MainFiQuS.py +24 -28
  2. fiqus/data/DataConductor.py +350 -301
  3. fiqus/data/DataFiQuS.py +42 -115
  4. fiqus/data/DataFiQuSCCT.py +150 -150
  5. fiqus/data/DataFiQuSConductor.py +97 -84
  6. fiqus/data/DataFiQuSConductorAC_Strand.py +701 -565
  7. fiqus/data/DataModelCommon.py +439 -0
  8. fiqus/data/DataMultipole.py +0 -13
  9. fiqus/data/DataRoxieParser.py +7 -0
  10. fiqus/data/DataWindingsCCT.py +37 -37
  11. fiqus/data/RegionsModelFiQuS.py +61 -104
  12. fiqus/geom_generators/GeometryCCT.py +904 -905
  13. fiqus/geom_generators/GeometryConductorAC_Strand.py +1863 -1391
  14. fiqus/geom_generators/GeometryMultipole.py +5 -4
  15. fiqus/geom_generators/GeometryPancake3D.py +1 -1
  16. fiqus/getdp_runners/RunGetdpCCT.py +13 -4
  17. fiqus/getdp_runners/RunGetdpConductorAC_Strand.py +341 -201
  18. fiqus/getdp_runners/RunGetdpPancake3D.py +2 -2
  19. fiqus/mains/MainConductorAC_Strand.py +141 -133
  20. fiqus/mains/MainMultipole.py +6 -5
  21. fiqus/mains/MainPancake3D.py +3 -4
  22. fiqus/mesh_generators/MeshCCT.py +209 -209
  23. fiqus/mesh_generators/MeshConductorAC_Strand.py +709 -656
  24. fiqus/mesh_generators/MeshMultipole.py +43 -46
  25. fiqus/parsers/ParserDAT.py +16 -16
  26. fiqus/parsers/ParserGetDPOnSection.py +212 -212
  27. fiqus/parsers/ParserGetDPTimeTable.py +134 -134
  28. fiqus/parsers/ParserMSH.py +53 -53
  29. fiqus/parsers/ParserPOS.py +214 -214
  30. fiqus/parsers/ParserRES.py +142 -142
  31. fiqus/plotters/PlotPythonCCT.py +133 -133
  32. fiqus/plotters/PlotPythonConductorAC.py +1079 -855
  33. fiqus/plotters/PlotPythonMultipole.py +18 -18
  34. fiqus/post_processors/PostProcessCCT.py +444 -440
  35. fiqus/post_processors/PostProcessConductorAC.py +997 -49
  36. fiqus/post_processors/PostProcessMultipole.py +19 -19
  37. fiqus/pre_processors/PreProcessCCT.py +175 -175
  38. fiqus/pro_material_functions/ironBHcurves.pro +246 -246
  39. fiqus/pro_templates/combined/CCT_template.pro +275 -274
  40. fiqus/pro_templates/combined/ConductorAC_template.pro +1474 -1025
  41. fiqus/pro_templates/combined/Multipole_template.pro +5 -5
  42. fiqus/utils/Utils.py +12 -7
  43. {fiqus-2025.2.0.dist-info → fiqus-2025.11.0.dist-info}/METADATA +65 -63
  44. fiqus-2025.11.0.dist-info/RECORD +86 -0
  45. {fiqus-2025.2.0.dist-info → fiqus-2025.11.0.dist-info}/WHEEL +1 -1
  46. tests/test_geometry_generators.py +4 -0
  47. tests/test_mesh_generators.py +5 -0
  48. tests/test_solvers.py +41 -4
  49. tests/utils/fiqus_test_classes.py +15 -6
  50. tests/utils/generate_reference_files_ConductorAC.py +57 -57
  51. tests/utils/helpers.py +97 -97
  52. fiqus-2025.2.0.dist-info/RECORD +0 -85
  53. {fiqus-2025.2.0.dist-info → fiqus-2025.11.0.dist-info}/LICENSE.txt +0 -0
  54. {fiqus-2025.2.0.dist-info → fiqus-2025.11.0.dist-info}/top_level.txt +0 -0
@@ -65,7 +65,7 @@ class PostProcess:
65
65
  self.postproc_settings = pd.DataFrame({
66
66
  'variables': settings.variables,
67
67
  'volumes': settings.volumes})
68
- if 'compare_to_ROXIE' in settings.dict():
68
+ if 'compare_to_ROXIE' in settings.model_dump():
69
69
  self.physical_quantity = 'magnetic_flux_density'
70
70
  else:
71
71
  self.physical_quantity = 'temperature'
@@ -83,8 +83,8 @@ class PostProcess:
83
83
  # self.ax.set_xlim(0, 0.09) # todo
84
84
  # self.ax.set_ylim(0, 0.09)
85
85
 
86
- if not settings.dict().get('take_average_conductor_temperature', False):
87
- if settings.dict().get('compare_to_ROXIE', False):
86
+ if not settings.model_dump().get('take_average_conductor_temperature', False):
87
+ if settings.model_dump().get('compare_to_ROXIE', False):
88
88
  fig2 = plt.figure(2)
89
89
  self.ax2 = fig2.add_subplot(projection='3d')
90
90
  self.ax2.set_xlabel('x [m]')
@@ -99,9 +99,9 @@ class PostProcess:
99
99
  self.ax3 = fig3.add_subplot(projection='3d')
100
100
  self.ax3.set_xlabel('x [m]')
101
101
  self.ax3.set_ylabel('y [m]')
102
- self.ax3.set_zlabel('norm(B) [T]' if 'compare_to_ROXIE' in settings.dict() else '')
102
+ self.ax3.set_zlabel('norm(B) [T]' if 'compare_to_ROXIE' in settings.model_dump() else '')
103
103
 
104
- if 'compare_to_ROXIE' in settings.dict() and 'b' in settings.variables:
104
+ if 'compare_to_ROXIE' in settings.model_dump() and 'b' in settings.variables:
105
105
  b_index = settings.variables.index('b')
106
106
  file_to_open = os.path.join(self.solution_folder, f"b_{settings.volumes[b_index]}.pos")
107
107
  elif 'T' in settings.variables:
@@ -178,9 +178,9 @@ class PostProcess:
178
178
 
179
179
  def postProcess(self, postproc):
180
180
  df_ref = pd.DataFrame()
181
- model_file_extension = 'EM' if 'compare_to_ROXIE' in postproc.dict() else 'TH'
181
+ model_file_extension = 'EM' if 'compare_to_ROXIE' in postproc.model_dump() else 'TH'
182
182
 
183
- if postproc.dict().get('compare_to_ROXIE', False):
183
+ if postproc.model_dump().get('compare_to_ROXIE', False):
184
184
  # flag_self_field = False
185
185
  # path_map2d = Path(postproc.compare_to_ROXIE, "MQXA_All_" +
186
186
  # f"{'WithIron_' if self.data.magnet.geometry.with_iron_yoke else 'NoIron_'}" +
@@ -196,13 +196,13 @@ class PostProcess:
196
196
  conductorPositionsList = RoxieParsers.parseCond2d(path_cond2d)
197
197
 
198
198
  # Collect strands coordinates
199
- strands_x = df_ref['X-POS/MM'] / 1e3 if postproc.dict().get('compare_to_ROXIE', False) else self.strands['x']
200
- strands_y = df_ref['Y-POS/MM'] / 1e3 if postproc.dict().get('compare_to_ROXIE', False) else self.strands['y']
199
+ strands_x = df_ref['X-POS/MM'] / 1e3 if postproc.model_dump().get('compare_to_ROXIE', False) else self.strands['x']
200
+ strands_y = df_ref['Y-POS/MM'] / 1e3 if postproc.model_dump().get('compare_to_ROXIE', False) else self.strands['y']
201
201
 
202
202
  # Probe physical quantity values from view and region areas
203
203
  physical_quantity_values = {'x': [], 'y': []}
204
204
  cond_areas, current_signs = [], []
205
- if postproc.dict().get('take_average_conductor_temperature', False):
205
+ if postproc.model_dump().get('take_average_conductor_temperature', False):
206
206
  half_turns = {name[:-3]: str(values.vol.numbers[i])
207
207
  for group, values in self.rm.powered.items() for i, name in enumerate(values.vol.names)}
208
208
  self.avg_temperatures = pd.concat([pd.read_csv(os.path.join(self.solution_folder, 'T_avg', 'T_avg_0.txt'),
@@ -217,7 +217,7 @@ class PostProcess:
217
217
  self.avg_temperatures.to_csv(os.path.join(self.solution_folder, 'half_turn_temperatures_over_time.csv'), index=False)
218
218
  else:
219
219
  print(f"Info : {self.data.general.magnet_name} - I n t e r p o l a t i n g . . .")
220
- print(f"Info : Interpolating {'magnetic flux density' if 'compare_to_ROXIE' in postproc.dict() else 'temperature'} ...")
220
+ print(f"Info : Interpolating {'magnetic flux density' if 'compare_to_ROXIE' in postproc.model_dump() else 'temperature'} ...")
221
221
 
222
222
  # view = gmsh.view.getTags()[0] if len(postproc.variables) == 1 else self.postproc_settings[
223
223
  # (self.postproc_settings['variables'] == self.supported_variables[self.physical_quantity]) &
@@ -235,7 +235,7 @@ class PostProcess:
235
235
 
236
236
  # Probe
237
237
  probe_data = gmsh.view.probe(view, strands_x[i], strands_y[i], 0)[0]
238
- if 'compare_to_ROXIE' in postproc.dict():
238
+ if 'compare_to_ROXIE' in postproc.model_dump():
239
239
  physical_quantity_values['x'].append(probe_data[0])
240
240
  physical_quantity_values['y'].append(probe_data[1])
241
241
  else:
@@ -243,7 +243,7 @@ class PostProcess:
243
243
  physical_quantity_values['y'].append(0)
244
244
 
245
245
  # Plot conductor and block identifiers
246
- if postproc.dict().get('compare_to_ROXIE', False) and postproc.plot_all != 'false':
246
+ if postproc.model_dump().get('compare_to_ROXIE', False) and postproc.plot_all != 'false':
247
247
  if is_new_conductor:
248
248
  self.ax.text(df_ref['X-POS/MM'][i] / 1e3, df_ref['Y-POS/MM'][i] / 1e3, str(self.strands['ht'][i]),
249
249
  style='italic', bbox={'facecolor': 'blue', 'pad': 3})
@@ -283,7 +283,7 @@ class PostProcess:
283
283
  f"{physical_quantity_values['y'][i]:.4f}", # pq_y
284
284
  f"{cond_areas[i] / strands_nr * 1e6:.4f}", # area
285
285
  f"{current_signs[i] * self.data.power_supply.I_initial / strands_nr:.2f}", # curr
286
- f"{df_ref['FILL FAC.'][i] if postproc.dict().get('compare_to_ROXIE', False) else 0:.4f}")) # fill_fac
286
+ f"{df_ref['FILL FAC.'][i] if postproc.model_dump().get('compare_to_ROXIE', False) else 0:.4f}")) # fill_fac
287
287
 
288
288
  # Save map2d file
289
289
  with open(f"{self.model_file}_{model_file_extension}.map2d", 'w') as file:
@@ -291,19 +291,19 @@ class PostProcess:
291
291
  file.writelines(content)
292
292
  print(f"Info : Map2D file saved.")
293
293
  print(f"WARNING : [Map2D] All strand surface areas are equal within a conductor. Refer to the ROXIE map2d file for actual values")
294
- if not postproc.dict().get('compare_to_ROXIE', True):
294
+ if not postproc.model_dump().get('compare_to_ROXIE', True):
295
295
  print(f"WARNING : [Map2D] No data is available for Filling Factor. Refer to the ROXIE map2d file for correct values")
296
296
 
297
297
  # Compute errors
298
298
  pq = np.linalg.norm(np.column_stack((np.array(physical_quantity_values['x']), np.array(physical_quantity_values['y']))), axis=1)
299
- if postproc.dict().get('compare_to_ROXIE', False):
299
+ if postproc.model_dump().get('compare_to_ROXIE', False):
300
300
  BB_err = pq - BB_roxie
301
301
  self.postprocess_parameters['overall_error'] = np.mean(abs(BB_err))
302
302
  self.postprocess_parameters['minimum_diff'] = np.min(BB_err)
303
303
  self.postprocess_parameters['maximum_diff'] = np.max(BB_err)
304
304
 
305
305
  if postproc.plot_all != 'false':
306
- if postproc.dict().get('take_average_conductor_temperature', False):
306
+ if postproc.model_dump().get('take_average_conductor_temperature', False):
307
307
  min_value = self.avg_temperatures.iloc[:, 1:].min().min()
308
308
  max_value = self.avg_temperatures.iloc[:, 1:].max().max()
309
309
  ht_polygons = [patches.Polygon(np.array([(self.crns['iHr'][i][0], self.crns['iHr'][i][1]),
@@ -324,12 +324,12 @@ class PostProcess:
324
324
  plt.pause(0.05)
325
325
 
326
326
  else:
327
- self.plotHalfTurnGeometry(postproc.dict().get('compare_to_ROXIE', False))
327
+ self.plotHalfTurnGeometry(postproc.model_dump().get('compare_to_ROXIE', False))
328
328
  map2d_strands = self.ax.scatter(strands_x, strands_y, edgecolor='black', facecolor='black', s=10)
329
329
 
330
330
  scatter3D_pos = self.ax3.scatter3D(strands_x, strands_y, pq, c=pq, cmap='Greens', vmin=0, vmax=10)
331
331
 
332
- if postproc.dict().get('compare_to_ROXIE', False):
332
+ if postproc.model_dump().get('compare_to_ROXIE', False):
333
333
  if os.path.isfile(path_cond2d):
334
334
  conductors_corners = [condPos.xyCorner for condPos in conductorPositionsList]
335
335
  for corners in conductors_corners:
@@ -1,175 +1,175 @@
1
- import math
2
- import os
3
- import timeit
4
- import json
5
- import gmsh
6
- import numpy as np
7
- from fiqus.utils.Utils import FilesAndFolders as uff
8
- from fiqus.utils.Utils import GmshUtils
9
- from fiqus.data.DataWindingsCCT import WindingsInformation # for volume information
10
-
11
-
12
- class Pre_Process:
13
- def __init__(self, fdm, verbose=True):
14
- """
15
- Class to preparing brep files by adding terminals.
16
- :param fdm: FiQuS data model
17
- :param verbose: If True more information is printed in python console.
18
- """
19
- self.cctdm = fdm.magnet
20
- self.model_folder = os.path.join(os.getcwd())
21
- self.magnet_name = fdm.general.magnet_name
22
- winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi')
23
- self.cctwi = uff.read_data_from_yaml(winding_info_file, WindingsInformation)
24
- self.verbose = verbose
25
- self.gu = GmshUtils(self.model_folder, self.verbose)
26
- self.gu.initialize()
27
-
28
- def calculate_normals(self, gui=False):
29
- """
30
- Calculates normals for the cct channel directions, i.e. along winding direction, along radial direction of the former (height) and along axial direction (width). Normals are saved to a .json
31
- file and used later for post-processing of magnetic field into components along channel length, height and width. Note that this function does not give the correct 'sign of normals', i.e.
32
- normals facing inwards or outwards of the surface are not properly distinguished. The normals along the length are not reliable and only along the height and width are used for calculations and
33
- the field along the length is taken as a remaining field.
34
- This function needs full geom_generators .brep file and volume information files (.vi) for each individual powered volume.
35
- :param gui: if True, the gmsh graphical user interface is shown at the end and normals are displayed as a view
36
- :return: Nothing, a file for each powered geom_generators brep is
37
- """
38
- if self.verbose:
39
- print('Calculating Normals Started')
40
- start_time = timeit.default_timer()
41
- gmsh.open(os.path.join(self.model_folder, f'{self.magnet_name}.brep'))
42
-
43
- def _calc_normals_dir(tags_for_normals_in, surfs_idx, surfs_scale):
44
- v_to_suf = [[0, 1, 2, 3], [0, 1, 5, 4], [4, 5, 6, 7], [3, 7, 6, 2], [0, 3, 7, 4], [1, 5, 6, 2]]
45
- norm_e_x = [] # normal along x of volume
46
- norm_e_y = []
47
- norm_e_z = []
48
- norm_dict = {}
49
- normals_view = [] # this remains an empty list if view is False
50
- coor_e_x = [] # coordinate x of the center of volume
51
- coor_e_y = []
52
- coor_e_z = []
53
- for vol_tag in tags_for_normals_in:
54
- all_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
55
- surf_tags = [all_surf_tags[index] for index in surfs_idx]
56
- norm = []
57
- node_coord = []
58
- vol_line_tags = []
59
- for surf_tag in all_surf_tags:
60
- line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1]
61
- for line_tag in line_tags_new:
62
- if line_tag not in vol_line_tags:
63
- vol_line_tags.append(line_tag)
64
- point_tags = []
65
- for line_tag in vol_line_tags:
66
- point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1]
67
- for point_tag in point_tags_new:
68
- if point_tag not in point_tags:
69
- point_tags.append(int(point_tag))
70
- for surf_i, surf_tag, scale in zip(surfs_idx, surf_tags, surfs_scale):
71
- p_idx = v_to_suf[surf_i]
72
- s_node_coord = []
73
- for p_i in p_idx:
74
- xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, point_tags[p_i])
75
- s_node_coord.append((xmin + xmax) / 2)
76
- s_node_coord.append((ymin + ymax) / 2)
77
- s_node_coord.append((zmin + zmax) / 2)
78
- parametricCoord = gmsh.model.getParametrization(2, surf_tag, s_node_coord)
79
- s_norm = gmsh.model.getNormal(surf_tag, parametricCoord)
80
- norm.extend(scale*s_norm)
81
- node_coord.extend(s_node_coord)
82
- coor_s_x = [] # coordinates surface x
83
- coor_s_y = []
84
- coor_s_z = []
85
- norm_s_x = [] # normals surface x
86
- norm_s_y = []
87
- norm_s_z = []
88
-
89
- for i in range(0, len(node_coord), 3):
90
- coor_s_x.append(node_coord[i])
91
- coor_s_y.append(node_coord[i+1])
92
- coor_s_z.append(node_coord[i+2])
93
- norm_s_x.append(norm[i])
94
- norm_s_y.append(norm[i+1])
95
- norm_s_z.append(norm[i+2])
96
-
97
- coor_e_x.append(np.mean(coor_s_x))
98
- coor_e_y.append(np.mean(coor_s_y))
99
- coor_e_z.append(np.mean(coor_s_z))
100
- # norm_e_x.append(np.mean(norm_s_x))
101
- # norm_e_y.append(np.mean(norm_s_y))
102
- # norm_e_z.append(np.mean(norm_s_z))
103
-
104
- # norm_e_x.append(np.sqrt(np.sum(np.square(norm_s_x)))/(2*np.sqrt(2)))
105
- # norm_e_y.append(np.sqrt(np.sum(np.square(norm_s_y)))/(2*np.sqrt(2)))
106
- # norm_e_z.append(np.sqrt(np.sum(np.square(norm_s_z)))/(2*np.sqrt(2)))
107
- v_x = np.sum(norm_s_x)
108
- v_y = np.sum(norm_s_y)
109
- v_z = np.sum(norm_s_z)
110
- ampl = math.sqrt(v_x**2 + v_y**2 + v_z**2)
111
-
112
- norm_e_x.append(v_x/ampl)
113
- norm_e_y.append(v_y/ampl)
114
- norm_e_z.append(v_z/ampl)
115
-
116
- for i in range(len(coor_e_x)):
117
- normals_view.append(coor_e_x[i])
118
- normals_view.append(coor_e_y[i])
119
- normals_view.append(coor_e_z[i])
120
- normals_view.append(norm_e_x[i])
121
- normals_view.append(norm_e_y[i])
122
- normals_view.append(norm_e_z[i])
123
- norm_dict['x'] = coor_e_x
124
- norm_dict['y'] = coor_e_y
125
- norm_dict['z'] = coor_e_z
126
- norm_dict['n_x'] = norm_e_x
127
- norm_dict['n_y'] = norm_e_y
128
- norm_dict['n_z'] = norm_e_z
129
- return norm_dict, normals_view
130
- """
131
- This is helper function called in a loop below.
132
- """
133
- max_tag = 0
134
- for f_name in self.cctwi.w_names+self.cctwi.f_names:
135
- vol_tags = json.load(open(os.path.join(self.model_folder, f'{f_name}.vi')))
136
- export_tags = vol_tags['export']
137
- tags_for_normals = [e + max_tag for e in export_tags]
138
- max_tag = np.max(vol_tags['all']) + max_tag
139
- surfs_idx_l = [0, 2] # along length of the former groove
140
- if f_name in self.cctwi.w_names:
141
- surfs_scale_l = [1, 1]
142
- elif f_name in self.cctwi.f_names:
143
- surfs_scale_l = [1, -1] # change direction for fqpl
144
- norm_l, norm_view_l = _calc_normals_dir(tags_for_normals, surfs_idx_l, surfs_scale_l)
145
- surfs_idx_h = [1, 3] # along height of the former groove
146
- surfs_scale_h = [1, -1]
147
- norm_h, norm_view_h = _calc_normals_dir(tags_for_normals, surfs_idx_h, surfs_scale_h)
148
- surfs_idx_w = [4, 5] # along width of the former groove
149
- surfs_scale_w = [1, -1]
150
- norm_w, norm_view_w = _calc_normals_dir(tags_for_normals, surfs_idx_w, surfs_scale_w)
151
- normals_dict = {'normals_l': norm_l, 'normals_h': norm_h, 'normals_w': norm_w}
152
- json.dump(normals_dict, open(f"{os.path.join(self.model_folder, f_name)}.normals", 'w'))
153
- if gui:
154
- normals_all = [norm_view_l, norm_view_h, norm_view_w]
155
- self.__add_normals_view(f_name, normals_all)
156
- if self.verbose:
157
- print(f'Calculating Normals Took {timeit.default_timer() - start_time:.2f} s')
158
- if gui:
159
- self.gu.launch_interactive_GUI()
160
-
161
- @staticmethod
162
- def __add_normals_view(name, normals_all, norm_list=[0, 1, 2]):
163
- """
164
- THis adds new view in gmsh.
165
- :param name: name of view
166
- :param normals_all: dictionary with normals
167
- :param norm_list: which normals to plot. Default is: [0, 1, 2] corresponds to [n_l, n_h, n_w]. If this array is shorter the corresponding normals are skipped in views.
168
- :return:
169
- """
170
- norm_names_all = [f"{name}_n_l", f"{name}_n_h", f"{name}_n_w"]
171
- norm_names = [norm_names_all[index] for index in norm_list]
172
- normals = [normals_all[index] for index in norm_list]
173
- for view_name, view_data in zip(norm_names, normals):
174
- gmsh.view.addListData(gmsh.view.add(view_name), "VP", len(view_data) // 6, view_data)
175
- gmsh.model.occ.synchronize()
1
+ import math
2
+ import os
3
+ import timeit
4
+ import json
5
+ import gmsh
6
+ import numpy as np
7
+ from fiqus.utils.Utils import FilesAndFolders as uff
8
+ from fiqus.utils.Utils import GmshUtils
9
+ from fiqus.data.DataWindingsCCT import WindingsInformation # for volume information
10
+
11
+
12
+ class Pre_Process:
13
+ def __init__(self, fdm, verbose=True):
14
+ """
15
+ Class to preparing brep files by adding terminals.
16
+ :param fdm: FiQuS data model
17
+ :param verbose: If True more information is printed in python console.
18
+ """
19
+ self.cctdm = fdm.magnet
20
+ self.model_folder = os.path.join(os.getcwd())
21
+ self.magnet_name = fdm.general.magnet_name
22
+ winding_info_file = os.path.join(self.model_folder, f'{self.magnet_name}.wi')
23
+ self.cctwi = uff.read_data_from_yaml(winding_info_file, WindingsInformation)
24
+ self.verbose = verbose
25
+ self.gu = GmshUtils(self.model_folder, self.verbose)
26
+ self.gu.initialize()
27
+
28
+ def calculate_normals(self, gui=False):
29
+ """
30
+ Calculates normals for the cct channel directions, i.e. along winding direction, along radial direction of the former (height) and along axial direction (width). Normals are saved to a .json
31
+ file and used later for post-processing of magnetic field into components along channel length, height and width. Note that this function does not give the correct 'sign of normals', i.e.
32
+ normals facing inwards or outwards of the surface are not properly distinguished. The normals along the length are not reliable and only along the height and width are used for calculations and
33
+ the field along the length is taken as a remaining field.
34
+ This function needs full geom_generators .brep file and volume information files (.vi) for each individual powered volume.
35
+ :param gui: if True, the gmsh graphical user interface is shown at the end and normals are displayed as a view
36
+ :return: Nothing, a file for each powered geom_generators brep is
37
+ """
38
+ if self.verbose:
39
+ print('Calculating Normals Started')
40
+ start_time = timeit.default_timer()
41
+ gmsh.open(os.path.join(self.model_folder, f'{self.magnet_name}.brep'))
42
+
43
+ def _calc_normals_dir(tags_for_normals_in, surfs_idx, surfs_scale):
44
+ v_to_suf = [[0, 1, 2, 3], [0, 1, 5, 4], [4, 5, 6, 7], [3, 7, 6, 2], [0, 3, 7, 4], [1, 5, 6, 2]]
45
+ norm_e_x = [] # normal along x of volume
46
+ norm_e_y = []
47
+ norm_e_z = []
48
+ norm_dict = {}
49
+ normals_view = [] # this remains an empty list if view is False
50
+ coor_e_x = [] # coordinate x of the center of volume
51
+ coor_e_y = []
52
+ coor_e_z = []
53
+ for vol_tag in tags_for_normals_in:
54
+ all_surf_tags = gmsh.model.getAdjacencies(3, vol_tag)[1]
55
+ surf_tags = [all_surf_tags[index] for index in surfs_idx]
56
+ norm = []
57
+ node_coord = []
58
+ vol_line_tags = []
59
+ for surf_tag in all_surf_tags:
60
+ line_tags_new = gmsh.model.getAdjacencies(2, surf_tag)[1]
61
+ for line_tag in line_tags_new:
62
+ if line_tag not in vol_line_tags:
63
+ vol_line_tags.append(line_tag)
64
+ point_tags = []
65
+ for line_tag in vol_line_tags:
66
+ point_tags_new = gmsh.model.getAdjacencies(1, line_tag)[1]
67
+ for point_tag in point_tags_new:
68
+ if point_tag not in point_tags:
69
+ point_tags.append(int(point_tag))
70
+ for surf_i, surf_tag, scale in zip(surfs_idx, surf_tags, surfs_scale):
71
+ p_idx = v_to_suf[surf_i]
72
+ s_node_coord = []
73
+ for p_i in p_idx:
74
+ xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.occ.getBoundingBox(0, point_tags[p_i])
75
+ s_node_coord.append((xmin + xmax) / 2)
76
+ s_node_coord.append((ymin + ymax) / 2)
77
+ s_node_coord.append((zmin + zmax) / 2)
78
+ parametricCoord = gmsh.model.getParametrization(2, surf_tag, s_node_coord)
79
+ s_norm = gmsh.model.getNormal(surf_tag, parametricCoord)
80
+ norm.extend(scale*s_norm)
81
+ node_coord.extend(s_node_coord)
82
+ coor_s_x = [] # coordinates surface x
83
+ coor_s_y = []
84
+ coor_s_z = []
85
+ norm_s_x = [] # normals surface x
86
+ norm_s_y = []
87
+ norm_s_z = []
88
+
89
+ for i in range(0, len(node_coord), 3):
90
+ coor_s_x.append(node_coord[i])
91
+ coor_s_y.append(node_coord[i+1])
92
+ coor_s_z.append(node_coord[i+2])
93
+ norm_s_x.append(norm[i])
94
+ norm_s_y.append(norm[i+1])
95
+ norm_s_z.append(norm[i+2])
96
+
97
+ coor_e_x.append(np.mean(coor_s_x))
98
+ coor_e_y.append(np.mean(coor_s_y))
99
+ coor_e_z.append(np.mean(coor_s_z))
100
+ # norm_e_x.append(np.mean(norm_s_x))
101
+ # norm_e_y.append(np.mean(norm_s_y))
102
+ # norm_e_z.append(np.mean(norm_s_z))
103
+
104
+ # norm_e_x.append(np.sqrt(np.sum(np.square(norm_s_x)))/(2*np.sqrt(2)))
105
+ # norm_e_y.append(np.sqrt(np.sum(np.square(norm_s_y)))/(2*np.sqrt(2)))
106
+ # norm_e_z.append(np.sqrt(np.sum(np.square(norm_s_z)))/(2*np.sqrt(2)))
107
+ v_x = np.sum(norm_s_x)
108
+ v_y = np.sum(norm_s_y)
109
+ v_z = np.sum(norm_s_z)
110
+ ampl = math.sqrt(v_x**2 + v_y**2 + v_z**2)
111
+
112
+ norm_e_x.append(v_x/ampl)
113
+ norm_e_y.append(v_y/ampl)
114
+ norm_e_z.append(v_z/ampl)
115
+
116
+ for i in range(len(coor_e_x)):
117
+ normals_view.append(coor_e_x[i])
118
+ normals_view.append(coor_e_y[i])
119
+ normals_view.append(coor_e_z[i])
120
+ normals_view.append(norm_e_x[i])
121
+ normals_view.append(norm_e_y[i])
122
+ normals_view.append(norm_e_z[i])
123
+ norm_dict['x'] = coor_e_x
124
+ norm_dict['y'] = coor_e_y
125
+ norm_dict['z'] = coor_e_z
126
+ norm_dict['n_x'] = norm_e_x
127
+ norm_dict['n_y'] = norm_e_y
128
+ norm_dict['n_z'] = norm_e_z
129
+ return norm_dict, normals_view
130
+ """
131
+ This is helper function called in a loop below.
132
+ """
133
+ max_tag = 0
134
+ for f_name in self.cctwi.w_names+self.cctwi.f_names:
135
+ vol_tags = json.load(open(os.path.join(self.model_folder, f'{f_name}.vi')))
136
+ export_tags = vol_tags['export']
137
+ tags_for_normals = [e + max_tag for e in export_tags]
138
+ max_tag = np.max(vol_tags['all']) + max_tag
139
+ surfs_idx_l = [0, 2] # along length of the former groove
140
+ if f_name in self.cctwi.w_names:
141
+ surfs_scale_l = [1, 1]
142
+ elif f_name in self.cctwi.f_names:
143
+ surfs_scale_l = [1, -1] # change direction for fqpl
144
+ norm_l, norm_view_l = _calc_normals_dir(tags_for_normals, surfs_idx_l, surfs_scale_l)
145
+ surfs_idx_h = [1, 3] # along height of the former groove
146
+ surfs_scale_h = [1, -1]
147
+ norm_h, norm_view_h = _calc_normals_dir(tags_for_normals, surfs_idx_h, surfs_scale_h)
148
+ surfs_idx_w = [4, 5] # along width of the former groove
149
+ surfs_scale_w = [1, -1]
150
+ norm_w, norm_view_w = _calc_normals_dir(tags_for_normals, surfs_idx_w, surfs_scale_w)
151
+ normals_dict = {'normals_l': norm_l, 'normals_h': norm_h, 'normals_w': norm_w}
152
+ json.dump(normals_dict, open(f"{os.path.join(self.model_folder, f_name)}.normals", 'w'))
153
+ if gui:
154
+ normals_all = [norm_view_l, norm_view_h, norm_view_w]
155
+ self.__add_normals_view(f_name, normals_all)
156
+ if self.verbose:
157
+ print(f'Calculating Normals Took {timeit.default_timer() - start_time:.2f} s')
158
+ if gui:
159
+ self.gu.launch_interactive_GUI()
160
+
161
+ @staticmethod
162
+ def __add_normals_view(name, normals_all, norm_list=[0, 1, 2]):
163
+ """
164
+ THis adds new view in gmsh.
165
+ :param name: name of view
166
+ :param normals_all: dictionary with normals
167
+ :param norm_list: which normals to plot. Default is: [0, 1, 2] corresponds to [n_l, n_h, n_w]. If this array is shorter the corresponding normals are skipped in views.
168
+ :return:
169
+ """
170
+ norm_names_all = [f"{name}_n_l", f"{name}_n_h", f"{name}_n_w"]
171
+ norm_names = [norm_names_all[index] for index in norm_list]
172
+ normals = [normals_all[index] for index in norm_list]
173
+ for view_name, view_data in zip(norm_names, normals):
174
+ gmsh.view.addListData(gmsh.view.add(view_name), "VP", len(view_data) // 6, view_data)
175
+ gmsh.model.occ.synchronize()