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.
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 -855
  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.1.dist-info/METADATA +132 -0
  57. fiqus-2024.12.1.dist-info/RECORD +84 -0
  58. {fiqus-2024.7.0.dist-info → fiqus-2024.12.1.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.7.0.dist-info/METADATA +0 -103
  68. fiqus-2024.7.0.dist-info/RECORD +0 -79
  69. {fiqus-2024.7.0.dist-info → fiqus-2024.12.1.dist-info}/top_level.txt +0 -0
@@ -1,143 +1,143 @@
1
- import pandas as pd
2
- import re
3
- from collections import defaultdict
4
-
5
- class ParserRES:
6
-
7
- def __init__(self, res_file_path, write_data=None):
8
- """
9
- TO BE DONE!!
10
- Read res file and returns its content as object attribute .pqv (postprocessed quantity value) that is a float
11
- :param dat_file_path: Full path to .pos file, including file name and extension.
12
- :return: nothing, keeps attribute pqv (postprocessed quantity value)
13
- """
14
- self._res_format_markers = {'s': '$ResFormat', 'e': '$EndResFormat'}
15
- self._getdp_version_markers = {'s': '/* ', 'e': ','}
16
- self._encoding_markers = {'s': ', ', 'e': ' */'}
17
- # the 1.1 is hard-coded according to the GetDP documentation,
18
- # see https://getdp.info/doc/texinfo/getdp.html#File-formats
19
- self._res_file_format = {'s': '1.1 ', 'e': '\n$EndResFormat'}
20
-
21
- self.solution = defaultdict(dict)
22
-
23
- if write_data:
24
- self._res_file_path = res_file_path
25
- self._write_data = write_data
26
- self._write_res_file()
27
- else:
28
- # read contents of .res file
29
- with open(res_file_path) as f:
30
- self._contents = f.read()
31
- self._parse_res_file()
32
-
33
-
34
- def __get_content_between_markers(self, markers_dict):
35
- """
36
- Gets text string between two markers specified in markers_dict
37
- """
38
- return self._contents[self._contents.find(markers_dict['s']) + len(markers_dict['s']):self._contents.find(markers_dict['e'])]
39
-
40
- def _res_header(self):
41
- """
42
- Parse the header of the .res file.
43
- Add the attributes:
44
- - getdp_version: GetDP version that created the .res file
45
- - encoding: encoding of the .res file
46
- - res_file_format: format of the .res file
47
- """
48
- self.getdp_version = self.__get_content_between_markers(self._getdp_version_markers)
49
- self.encoding = self.__get_content_between_markers(self._encoding_markers)
50
- self.res_file_format = self.__get_content_between_markers(self._res_file_format)
51
-
52
- def _get_all_solution_blocks(self):
53
- """
54
- Add all unparsed solution blocks to the attribute _solution_blocks
55
- using regular expressions. It is a list of lists which each sub-list
56
- containing exactly one solution block.
57
- """
58
- solution_string = self._contents[self._contents.find('$Solution'):]
59
- self._solution_blocks = re.findall(r'\$Solution.*?\$EndSolution', solution_string, re.DOTALL)
60
-
61
- def _parse_res_file_single_solution_block(self, solution_block_split_by_line):
62
-
63
- # the first line is ignored
64
- header = solution_block_split_by_line[1]
65
- header_split = header.split()
66
- dof_data = int(header_split[0])
67
- time_real = float(header_split[1])
68
- time_imag = float(header_split[2])
69
- time_step = int(header_split[3])
70
- solution = [float(entry) for entry in solution_block_split_by_line[2:-1]]
71
-
72
- if "time_real" not in self.solution:
73
- self.solution['time_real'] = [time_real]
74
- else:
75
- self.solution['time_real'].append(time_real)
76
-
77
- if "time_imag" not in self.solution:
78
- self.solution['time_imag'] = [time_imag]
79
- else:
80
- self.solution['time_imag'].append(time_imag)
81
-
82
- if "time_step" not in self.solution:
83
- self.solution['time_step'] = [time_step]
84
- else:
85
- self.solution['time_step'].append(time_step)
86
-
87
- if "dof_data" not in self.solution:
88
- self.solution['dof_data'] = [dof_data]
89
- else:
90
- self.solution['dof_data'].append(dof_data)
91
-
92
- if "solution" not in self.solution:
93
- self.solution['solution'] = [solution]
94
- else:
95
- self.solution['solution'].append(solution)
96
-
97
- @staticmethod
98
- def __get_lines(data_str):
99
- """
100
- Converts text string into a list of lines
101
- """
102
- data_str = re.sub('\n', "'", data_str)
103
- data_str = re.sub('"', '', data_str)
104
- str_list = re.split("'", data_str)
105
- return str_list
106
-
107
- def _parse_res_file_solution_blocks(self):
108
- """
109
-
110
- """
111
- for solution_block in self._solution_blocks:
112
- # split by line
113
- solution_block_split_by_line = self.__get_lines(solution_block)
114
- self._parse_res_file_single_solution_block(solution_block_split_by_line)
115
-
116
- def _parse_res_file(self):
117
- self._res_header()
118
- self._get_all_solution_blocks()
119
- self._parse_res_file_solution_blocks()
120
-
121
- def _write_res_file(self):
122
- with open(self._res_file_path, "w") as f:
123
- # write header
124
- f.write(f"$ResFormat /* {self._write_data.getdp_version}, {self._write_data.encoding} */\n")
125
- # write res file format
126
- f.write(f"1.1 {self._write_data.res_file_format}\n")
127
- f.write(f"$EndResFormat\n")
128
-
129
- self._write_solution_block(f)
130
-
131
- def _write_solution_block(self, f):
132
- for time_real, time_imag, time_step, dof_data, solution in zip(self._write_data.solution['time_real'], self._write_data.solution['time_imag'], self._write_data.solution['time_step'], self._write_data.solution['dof_data'], self._write_data.solution['solution']):
133
-
134
- f.write(f"$Solution /* DofData #{dof_data} */\n")
135
- f.write(f"{dof_data} {time_real:.16g} {time_imag:.16g} {time_step}\n")
136
- f.write('\n'.join('{0:.16g}'.format(sol_entry) for sol_entry in solution))
137
- f.write(f"\n$EndSolution\n")
138
-
139
- # ==============================================================================
140
- #parsedRes = ParserRES('test.res')
141
- #ParserRES('test_written.res', write_data=parsedRes)
142
- #import filecmp
1
+ import pandas as pd
2
+ import re
3
+ from collections import defaultdict
4
+
5
+ class ParserRES:
6
+
7
+ def __init__(self, res_file_path, write_data=None):
8
+ """
9
+ TO BE DONE!!
10
+ Read res file and returns its content as object attribute .pqv (postprocessed quantity value) that is a float
11
+ :param dat_file_path: Full path to .pos file, including file name and extension.
12
+ :return: nothing, keeps attribute pqv (postprocessed quantity value)
13
+ """
14
+ self._res_format_markers = {'s': '$ResFormat', 'e': '$EndResFormat'}
15
+ self._getdp_version_markers = {'s': '/* ', 'e': ','}
16
+ self._encoding_markers = {'s': ', ', 'e': ' */'}
17
+ # the 1.1 is hard-coded according to the GetDP documentation,
18
+ # see https://getdp.info/doc/texinfo/getdp.html#File-formats
19
+ self._res_file_format = {'s': '1.1 ', 'e': '\n$EndResFormat'}
20
+
21
+ self.solution = defaultdict(dict)
22
+
23
+ if write_data:
24
+ self._res_file_path = res_file_path
25
+ self._write_data = write_data
26
+ self._write_res_file()
27
+ else:
28
+ # read contents of .res file
29
+ with open(res_file_path) as f:
30
+ self._contents = f.read()
31
+ self._parse_res_file()
32
+
33
+
34
+ def __get_content_between_markers(self, markers_dict):
35
+ """
36
+ Gets text string between two markers specified in markers_dict
37
+ """
38
+ return self._contents[self._contents.find(markers_dict['s']) + len(markers_dict['s']):self._contents.find(markers_dict['e'])]
39
+
40
+ def _res_header(self):
41
+ """
42
+ Parse the header of the .res file.
43
+ Add the attributes:
44
+ - getdp_version: GetDP version that created the .res file
45
+ - encoding: encoding of the .res file
46
+ - res_file_format: format of the .res file
47
+ """
48
+ self.getdp_version = self.__get_content_between_markers(self._getdp_version_markers)
49
+ self.encoding = self.__get_content_between_markers(self._encoding_markers)
50
+ self.res_file_format = self.__get_content_between_markers(self._res_file_format)
51
+
52
+ def _get_all_solution_blocks(self):
53
+ """
54
+ Add all unparsed solution blocks to the attribute _solution_blocks
55
+ using regular expressions. It is a list of lists which each sub-list
56
+ containing exactly one solution block.
57
+ """
58
+ solution_string = self._contents[self._contents.find('$Solution'):]
59
+ self._solution_blocks = re.findall(r'\$Solution.*?\$EndSolution', solution_string, re.DOTALL)
60
+
61
+ def _parse_res_file_single_solution_block(self, solution_block_split_by_line):
62
+
63
+ # the first line is ignored
64
+ header = solution_block_split_by_line[1]
65
+ header_split = header.split()
66
+ dof_data = int(header_split[0])
67
+ time_real = float(header_split[1])
68
+ time_imag = float(header_split[2])
69
+ time_step = int(header_split[3])
70
+ solution = [float(entry) for entry in solution_block_split_by_line[2:-1]]
71
+
72
+ if "time_real" not in self.solution:
73
+ self.solution['time_real'] = [time_real]
74
+ else:
75
+ self.solution['time_real'].append(time_real)
76
+
77
+ if "time_imag" not in self.solution:
78
+ self.solution['time_imag'] = [time_imag]
79
+ else:
80
+ self.solution['time_imag'].append(time_imag)
81
+
82
+ if "time_step" not in self.solution:
83
+ self.solution['time_step'] = [time_step]
84
+ else:
85
+ self.solution['time_step'].append(time_step)
86
+
87
+ if "dof_data" not in self.solution:
88
+ self.solution['dof_data'] = [dof_data]
89
+ else:
90
+ self.solution['dof_data'].append(dof_data)
91
+
92
+ if "solution" not in self.solution:
93
+ self.solution['solution'] = [solution]
94
+ else:
95
+ self.solution['solution'].append(solution)
96
+
97
+ @staticmethod
98
+ def __get_lines(data_str):
99
+ """
100
+ Converts text string into a list of lines
101
+ """
102
+ data_str = re.sub('\n', "'", data_str)
103
+ data_str = re.sub('"', '', data_str)
104
+ str_list = re.split("'", data_str)
105
+ return str_list
106
+
107
+ def _parse_res_file_solution_blocks(self):
108
+ """
109
+
110
+ """
111
+ for solution_block in self._solution_blocks:
112
+ # split by line
113
+ solution_block_split_by_line = self.__get_lines(solution_block)
114
+ self._parse_res_file_single_solution_block(solution_block_split_by_line)
115
+
116
+ def _parse_res_file(self):
117
+ self._res_header()
118
+ self._get_all_solution_blocks()
119
+ self._parse_res_file_solution_blocks()
120
+
121
+ def _write_res_file(self):
122
+ with open(self._res_file_path, "w") as f:
123
+ # write header
124
+ f.write(f"$ResFormat /* {self._write_data.getdp_version}, {self._write_data.encoding} */\n")
125
+ # write res file format
126
+ f.write(f"1.1 {self._write_data.res_file_format}\n")
127
+ f.write(f"$EndResFormat\n")
128
+
129
+ self._write_solution_block(f)
130
+
131
+ def _write_solution_block(self, f):
132
+ for time_real, time_imag, time_step, dof_data, solution in zip(self._write_data.solution['time_real'], self._write_data.solution['time_imag'], self._write_data.solution['time_step'], self._write_data.solution['dof_data'], self._write_data.solution['solution']):
133
+
134
+ f.write(f"$Solution /* DofData #{dof_data} */\n")
135
+ f.write(f"{dof_data} {time_real:.16g} {time_imag:.16g} {time_step}\n")
136
+ f.write('\n'.join('{0:.16g}'.format(sol_entry) for sol_entry in solution))
137
+ f.write(f"\n$EndSolution\n")
138
+
139
+ # ==============================================================================
140
+ #parsedRes = ParserRES('test.res')
141
+ #ParserRES('test_written.res', write_data=parsedRes)
142
+ #import filecmp
143
143
  #print(filecmp.cmp('test.res', 'test_written.res'))
@@ -1,134 +1,134 @@
1
- import os
2
- from operator import le, ge
3
-
4
- import numpy as np
5
- import pandas as pd
6
- import matplotlib.pyplot as plt
7
- from matplotlib.pyplot import cm
8
- from mpl_toolkits.mplot3d.art3d import Poly3DCollection
9
-
10
- from fiqus.geom_generators.GeometryCCT import Winding, FQPL
11
-
12
-
13
- class PlotPythonCCT:
14
- def __init__(self, fdm, verbose=True):
15
- """
16
- Class to cct models postprocessing
17
- :param fdm: FiQuS data model
18
- :param verbose: If True more information is printed in python console.
19
- """
20
- self.cctdm = fdm.magnet
21
- self.model_folder = os.path.join(os.getcwd())
22
- self.magnet_name = fdm.general.magnet_name
23
-
24
- self.verbose = verbose
25
- # self.gu = GmshUtils(self.model_folder, self.verbose)
26
- # self.gu.initialize()
27
-
28
- def plot_elements_file(self, lfes=None):
29
- field_map_3D_csv = 'field_map_3D.csv'
30
- df = pd.read_csv(field_map_3D_csv, delimiter=',', engine='python')
31
- sAvePositions = df['sAvePositions [m]'].to_numpy(dtype='float')[:-1] # [:-1] to remove last nan
32
- ph_order = df['nodeToPhysicalTurn [-]'].to_numpy(dtype='int')[:-1] # physical turns, [:-1] to remove last nan
33
- el_order = df['nodeToHalfTurn [-]'].to_numpy(dtype='int')[:-1] # electric order turns, [:-1] to remove last nan
34
- x3Dsurf_1 = df['x3Dsurf 1 [m]'].to_numpy(dtype='float')
35
- y3Dsurf_1 = df['y3Dsurf 1 [m]'].to_numpy(dtype='float')
36
- z3Dsurf_1 = df['z3Dsurf 1 [m]'].to_numpy(dtype='float')
37
- el_order_unique = np.unique(el_order)
38
-
39
- per_turn = False
40
-
41
- fig = plt.figure(figsize=(16, 10))
42
- ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
43
-
44
- if per_turn:
45
- colors = cm.rainbow(np.linspace(0, 1, np.shape(el_order_unique)[0]))
46
- for idx_turn, el_u in enumerate(el_order_unique.tolist()):
47
- indexes = np.where(el_u == el_order)[0]
48
- min_idx=np.min(indexes)
49
- max_idx=np.max(indexes+2)
50
- ax.plot(x3Dsurf_1[min_idx:max_idx], y3Dsurf_1[min_idx:max_idx], z3Dsurf_1[min_idx:max_idx], color=colors[idx_turn])
51
- else:
52
- ax.plot(x3Dsurf_1, y3Dsurf_1, z3Dsurf_1)
53
- # colors = cm.rainbow(np.linspace(0, 1, np.shape(x3Dsurf_1)[0]))
54
- # # ax.plot(x3Dsurf_1, y3Dsurf_1, z3Dsurf_1)
55
-
56
- # for idx in range(np.shape(x3Dsurf_1)[0]-1):
57
- # ax.plot([x3Dsurf_1[idx], x3Dsurf_1[idx+1]], [y3Dsurf_1[idx], y3Dsurf_1[idx+1]], [z3Dsurf_1[idx], z3Dsurf_1[idx+1]], color=colors[idx])
58
- # xmin, xmax, ymin, ymax, zmin, zmax = (0, 0, 0, 0, 0, 0)
59
- # #colors = cm.rainbow(np.linspace(0, 1, len(objects_list)))
60
- # ax.set_box_aspect((xmax - xmin, ymax - ymin, zmax - zmin)) # set axis to data aspect ratio
61
- # ax.set_xlim([xmin, xmax])
62
- # ax.set_ylim([ymin, ymax])
63
- # ax.set_zlim([zmin, zmax])
64
- ax.set_xlabel('x (m)')
65
- ax.set_ylabel('y (m)')
66
- ax.set_zlabel('z (m)')
67
- ax.view_init(elev=90, azim=0, vertical_axis='x')
68
- plt.show()
69
- pass
70
-
71
- def plot_elements_computed(self, lfes=None):
72
- """
73
- Makes a plot oh hexahedra by calculating 'on the fly' what is defined in the yaml data model for cct magnet.
74
- :param lfes: list with numbers of elements to put text labels on. For two windings an example would be [[3], [9]] to plot 3rd hex in the first winding and 9th in the second
75
- :return: Nothing. A matplotlib graph pops up on the screen.
76
- """
77
-
78
- windings = []
79
- fqpls = []
80
- for ww, _ in enumerate(self.cctdm.geometry.windings.names):
81
- winding_obj = Winding(self.cctdm, ww, post_proc=True)
82
- windings.append(winding_obj)
83
- for ff, _ in enumerate(self.cctdm.geometry.fqpls.names):
84
- fqpl_obj = FQPL(self.cctdm, ff)
85
- fqpls.append(fqpl_obj)
86
- objects_list = windings + fqpls
87
- if not lfes:
88
- lfes = [[] for _ in objects_list] # make it work if lfes is not supplied as input.
89
-
90
- def val_check(var, array, oper, func):
91
- new = func(array)
92
- if oper(new, var):
93
- return new
94
- else:
95
- return var
96
-
97
- fig = plt.figure(figsize=(16, 10))
98
- ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
99
- xmin, xmax, ymin, ymax, zmin, zmax = (0, 0, 0, 0, 0, 0)
100
- colors = cm.rainbow(np.linspace(0, 1, len(objects_list)))
101
- for obj, lfe, fc in zip(objects_list, lfes, colors):
102
- xs = []
103
- ys = []
104
- zs = []
105
- hexes = obj.hexes
106
- vts = obj.vertices_to_surf
107
- for elem_num, points_dict in hexes.items():
108
- list_of_coor_tuples = []
109
- points_dict.pop('ct', None) # removing turn from dict
110
- for p_num, p_coords in points_dict.items():
111
-
112
- list_of_coor_tuples.append((p_coords['x'], p_coords['y'], p_coords['z']))
113
- if elem_num in lfe:
114
- ax.scatter(p_coords['x'], p_coords['y'], p_coords['z'])
115
- ax.text(p_coords['x'], p_coords['y'], p_coords['z'], f'{p_num}', zdir='z')
116
- for coor_acc, coor_key in zip([xs, ys, zs], ['x', 'y', 'z']):
117
- coor_acc.append(p_coords[coor_key])
118
- poly3d = [[list_of_coor_tuples[vts[ix][iy]] for iy in range(len(vts[0]))] for ix in range(len(vts))]
119
- ax.add_collection3d(Poly3DCollection(poly3d, edgecolors='k', facecolors=fc, linewidths=0.5, alpha=0.5))
120
- xmin = val_check(xmin, xs, le, np.min)
121
- xmax = val_check(xmax, xs, ge, np.max)
122
- ymin = val_check(ymin, ys, le, np.min)
123
- ymax = val_check(ymax, ys, ge, np.max)
124
- zmin = val_check(zmin, zs, le, np.min)
125
- zmax = val_check(zmax, zs, ge, np.max)
126
- ax.set_box_aspect((xmax - xmin, ymax - ymin, zmax - zmin)) # set axis to data aspect ratio
127
- ax.set_xlim([xmin, xmax])
128
- ax.set_ylim([ymin, ymax])
129
- ax.set_zlim([zmin, zmax])
130
- ax.set_xlabel('x (m)')
131
- ax.set_ylabel('y (m)')
132
- ax.set_zlabel('z (m)')
133
- ax.view_init(elev=90, azim=0, vertical_axis='x')
1
+ import os
2
+ from operator import le, ge
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ from matplotlib.pyplot import cm
8
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
9
+
10
+ from fiqus.geom_generators.GeometryCCT import Winding, FQPL
11
+
12
+
13
+ class PlotPythonCCT:
14
+ def __init__(self, fdm, verbose=True):
15
+ """
16
+ Class to cct models postprocessing
17
+ :param fdm: FiQuS data model
18
+ :param verbose: If True more information is printed in python console.
19
+ """
20
+ self.cctdm = fdm.magnet
21
+ self.model_folder = os.path.join(os.getcwd())
22
+ self.magnet_name = fdm.general.magnet_name
23
+
24
+ self.verbose = verbose
25
+ # self.gu = GmshUtils(self.model_folder, self.verbose)
26
+ # self.gu.initialize()
27
+
28
+ def plot_elements_file(self, lfes=None):
29
+ field_map_3D_csv = 'field_map_3D.csv'
30
+ df = pd.read_csv(field_map_3D_csv, delimiter=',', engine='python')
31
+ sAvePositions = df['sAvePositions [m]'].to_numpy(dtype='float')[:-1] # [:-1] to remove last nan
32
+ ph_order = df['nodeToPhysicalTurn [-]'].to_numpy(dtype='int')[:-1] # physical turns, [:-1] to remove last nan
33
+ el_order = df['nodeToHalfTurn [-]'].to_numpy(dtype='int')[:-1] # electric order turns, [:-1] to remove last nan
34
+ x3Dsurf_1 = df['x3Dsurf 1 [m]'].to_numpy(dtype='float')
35
+ y3Dsurf_1 = df['y3Dsurf 1 [m]'].to_numpy(dtype='float')
36
+ z3Dsurf_1 = df['z3Dsurf 1 [m]'].to_numpy(dtype='float')
37
+ el_order_unique = np.unique(el_order)
38
+
39
+ per_turn = False
40
+
41
+ fig = plt.figure(figsize=(16, 10))
42
+ ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
43
+
44
+ if per_turn:
45
+ colors = cm.rainbow(np.linspace(0, 1, np.shape(el_order_unique)[0]))
46
+ for idx_turn, el_u in enumerate(el_order_unique.tolist()):
47
+ indexes = np.where(el_u == el_order)[0]
48
+ min_idx=np.min(indexes)
49
+ max_idx=np.max(indexes+2)
50
+ ax.plot(x3Dsurf_1[min_idx:max_idx], y3Dsurf_1[min_idx:max_idx], z3Dsurf_1[min_idx:max_idx], color=colors[idx_turn])
51
+ else:
52
+ ax.plot(x3Dsurf_1, y3Dsurf_1, z3Dsurf_1)
53
+ # colors = cm.rainbow(np.linspace(0, 1, np.shape(x3Dsurf_1)[0]))
54
+ # # ax.plot(x3Dsurf_1, y3Dsurf_1, z3Dsurf_1)
55
+
56
+ # for idx in range(np.shape(x3Dsurf_1)[0]-1):
57
+ # ax.plot([x3Dsurf_1[idx], x3Dsurf_1[idx+1]], [y3Dsurf_1[idx], y3Dsurf_1[idx+1]], [z3Dsurf_1[idx], z3Dsurf_1[idx+1]], color=colors[idx])
58
+ # xmin, xmax, ymin, ymax, zmin, zmax = (0, 0, 0, 0, 0, 0)
59
+ # #colors = cm.rainbow(np.linspace(0, 1, len(objects_list)))
60
+ # ax.set_box_aspect((xmax - xmin, ymax - ymin, zmax - zmin)) # set axis to data aspect ratio
61
+ # ax.set_xlim([xmin, xmax])
62
+ # ax.set_ylim([ymin, ymax])
63
+ # ax.set_zlim([zmin, zmax])
64
+ ax.set_xlabel('x (m)')
65
+ ax.set_ylabel('y (m)')
66
+ ax.set_zlabel('z (m)')
67
+ ax.view_init(elev=90, azim=0, vertical_axis='x')
68
+ plt.show()
69
+ pass
70
+
71
+ def plot_elements_computed(self, lfes=None):
72
+ """
73
+ Makes a plot oh hexahedra by calculating 'on the fly' what is defined in the yaml data model for cct magnet.
74
+ :param lfes: list with numbers of elements to put text labels on. For two windings an example would be [[3], [9]] to plot 3rd hex in the first winding and 9th in the second
75
+ :return: Nothing. A matplotlib graph pops up on the screen.
76
+ """
77
+
78
+ windings = []
79
+ fqpls = []
80
+ for ww, _ in enumerate(self.cctdm.geometry.windings.names):
81
+ winding_obj = Winding(self.cctdm, ww, post_proc=True)
82
+ windings.append(winding_obj)
83
+ for ff, _ in enumerate(self.cctdm.geometry.fqpls.names):
84
+ fqpl_obj = FQPL(self.cctdm, ff)
85
+ fqpls.append(fqpl_obj)
86
+ objects_list = windings + fqpls
87
+ if not lfes:
88
+ lfes = [[] for _ in objects_list] # make it work if lfes is not supplied as input.
89
+
90
+ def val_check(var, array, oper, func):
91
+ new = func(array)
92
+ if oper(new, var):
93
+ return new
94
+ else:
95
+ return var
96
+
97
+ fig = plt.figure(figsize=(16, 10))
98
+ ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
99
+ xmin, xmax, ymin, ymax, zmin, zmax = (0, 0, 0, 0, 0, 0)
100
+ colors = cm.rainbow(np.linspace(0, 1, len(objects_list)))
101
+ for obj, lfe, fc in zip(objects_list, lfes, colors):
102
+ xs = []
103
+ ys = []
104
+ zs = []
105
+ hexes = obj.hexes
106
+ vts = obj.vertices_to_surf
107
+ for elem_num, points_dict in hexes.items():
108
+ list_of_coor_tuples = []
109
+ points_dict.pop('ct', None) # removing turn from dict
110
+ for p_num, p_coords in points_dict.items():
111
+
112
+ list_of_coor_tuples.append((p_coords['x'], p_coords['y'], p_coords['z']))
113
+ if elem_num in lfe:
114
+ ax.scatter(p_coords['x'], p_coords['y'], p_coords['z'])
115
+ ax.text(p_coords['x'], p_coords['y'], p_coords['z'], f'{p_num}', zdir='z')
116
+ for coor_acc, coor_key in zip([xs, ys, zs], ['x', 'y', 'z']):
117
+ coor_acc.append(p_coords[coor_key])
118
+ poly3d = [[list_of_coor_tuples[vts[ix][iy]] for iy in range(len(vts[0]))] for ix in range(len(vts))]
119
+ ax.add_collection3d(Poly3DCollection(poly3d, edgecolors='k', facecolors=fc, linewidths=0.5, alpha=0.5))
120
+ xmin = val_check(xmin, xs, le, np.min)
121
+ xmax = val_check(xmax, xs, ge, np.max)
122
+ ymin = val_check(ymin, ys, le, np.min)
123
+ ymax = val_check(ymax, ys, ge, np.max)
124
+ zmin = val_check(zmin, zs, le, np.min)
125
+ zmax = val_check(zmax, zs, ge, np.max)
126
+ ax.set_box_aspect((xmax - xmin, ymax - ymin, zmax - zmin)) # set axis to data aspect ratio
127
+ ax.set_xlim([xmin, xmax])
128
+ ax.set_ylim([ymin, ymax])
129
+ ax.set_zlim([zmin, zmax])
130
+ ax.set_xlabel('x (m)')
131
+ ax.set_ylabel('y (m)')
132
+ ax.set_zlabel('z (m)')
133
+ ax.view_init(elev=90, azim=0, vertical_axis='x')
134
134
  plt.show()