fiqus 2025.11.0__py3-none-any.whl → 2025.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 (27) hide show
  1. fiqus/MainFiQuS.py +6 -0
  2. fiqus/data/DataConductor.py +9 -1
  3. fiqus/data/DataFiQuS.py +3 -3
  4. fiqus/data/DataFiQuSConductorAC_Rutherford.py +569 -0
  5. fiqus/data/DataFiQuSHomogenizedConductor.py +478 -0
  6. fiqus/geom_generators/GeometryConductorAC_Rutherford.py +706 -0
  7. fiqus/geom_generators/GeometryConductorAC_Strand_RutherfordCopy.py +1848 -0
  8. fiqus/geom_generators/GeometryHomogenizedConductor.py +183 -0
  9. fiqus/getdp_runners/RunGetdpConductorAC_Rutherford.py +200 -0
  10. fiqus/getdp_runners/RunGetdpHomogenizedConductor.py +178 -0
  11. fiqus/mains/MainConductorAC_Rutherford.py +76 -0
  12. fiqus/mains/MainHomogenizedConductor.py +112 -0
  13. fiqus/mesh_generators/MeshConductorAC_Rutherford.py +235 -0
  14. fiqus/mesh_generators/MeshConductorAC_Strand_RutherfordCopy.py +718 -0
  15. fiqus/mesh_generators/MeshHomogenizedConductor.py +229 -0
  16. fiqus/post_processors/PostProcessAC_Rutherford.py +142 -0
  17. fiqus/post_processors/PostProcessHomogenizedConductor.py +114 -0
  18. fiqus/pro_templates/combined/ConductorACRutherford_template.pro +1742 -0
  19. fiqus/pro_templates/combined/HomogenizedConductor_template.pro +1663 -0
  20. {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/METADATA +6 -5
  21. {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/RECORD +27 -11
  22. tests/test_geometry_generators.py +20 -0
  23. tests/test_mesh_generators.py +38 -0
  24. tests/test_solvers.py +100 -0
  25. {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/LICENSE.txt +0 -0
  26. {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/WHEEL +0 -0
  27. {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,229 @@
1
+ import os, json, gmsh
2
+
3
+ from fiqus.data import RegionsModelFiQuS
4
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
5
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
6
+
7
+ class Mesh:
8
+ def __init__(self, fdm, verbose=True):
9
+ """
10
+ A base-class used to manage the mesh for HomogenizedConductor model.
11
+
12
+ :ivar fdm: The fiqus data model for input parameters.
13
+ :vartype fdm: dict
14
+ """
15
+ self.fdm = fdm
16
+ self.mesh_folder = os.path.join(os.getcwd())
17
+ self.geom_folder = os.path.dirname(self.mesh_folder)
18
+ self.mesh_file = os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}.msh")
19
+ self.regions_file = os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}.regions")
20
+ self.vi_file = os.path.join(self.geom_folder, f'{self.fdm.general.magnet_name}.vi')
21
+ self.verbose = verbose
22
+
23
+ # dictionaries for physical groups
24
+ self.dimTags_physical_surfaces = {}
25
+ self.dimTags_physical_boundaries = {}
26
+ self.dimTags_physical_cuts = {}
27
+ self.dimTags_physical_points ={}
28
+
29
+ # Read volume information file:
30
+ with open(self.vi_file, "r") as f:
31
+ self.dimTags = json.load(f)
32
+
33
+ for key, value in self.dimTags.items():
34
+ self.dimTags[key] = tuple(value) # dimTags contains all surfaces
35
+
36
+ self.gu = GmshUtils(self.mesh_folder, self.verbose)
37
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
38
+
39
+ gmsh.option.setNumber("General.Terminal", verbose)
40
+
41
+ def generate_mesh(self):
42
+ """ This function generates a mesh based on the volume information (vi) file created in the geometry step and the yaml input. """
43
+
44
+ self.generate_physical_groups()
45
+
46
+ # scale by domain size
47
+ domain_size = self.fdm.magnet.geometry.air.radius
48
+ # Mesh size field for cables:
49
+ strand_field = gmsh.model.mesh.field.add("Constant")
50
+ gmsh.model.mesh.field.setNumbers(strand_field, "SurfacesList", [value[1] for key, value in self.dimTags.items() if 'Cable' in key])
51
+ gmsh.model.mesh.field.setNumber(strand_field, "VIn", self.fdm.magnet.mesh.cable_mesh_size_ratio * domain_size)
52
+ # Mesh size field for excitation coils:
53
+ coil_field = gmsh.model.mesh.field.add("Constant")
54
+ gmsh.model.mesh.field.setNumbers(coil_field, "SurfacesList", [value[1] for key, value in self.dimTags.items() if 'Coil' in key])
55
+ gmsh.model.mesh.field.setNumber(coil_field, "VIn", self.fdm.magnet.mesh.cable_mesh_size_ratio * domain_size)
56
+ # Mesh size field for air:
57
+ air_field = gmsh.model.mesh.field.add("Constant")
58
+ gmsh.model.mesh.field.setNumbers(air_field, "SurfacesList", [self.dimTags['Air'][1]])
59
+ gmsh.model.mesh.field.setNumber(air_field, "VIn", self.fdm.magnet.mesh.air_boundary_mesh_size_ratio * domain_size)
60
+
61
+ total_meshSize_field = gmsh.model.mesh.field.add("Min")
62
+ gmsh.model.mesh.field.setNumbers(total_meshSize_field, "FieldsList", [strand_field, coil_field, air_field])
63
+ gmsh.model.mesh.field.setAsBackgroundMesh(total_meshSize_field)
64
+
65
+ gmsh.option.setNumber("Mesh.MeshSizeFactor", self.fdm.magnet.mesh.scaling_global)
66
+ gmsh.model.mesh.generate(2)
67
+
68
+
69
+ def generate_physical_groups(self):
70
+ """ This function generates the physical groups within the mesh based on the volume information (vi) file and stores their tags in dictionaries.
71
+
72
+ :raises ValueError: For unknown volume names in the vi-file
73
+ """
74
+ gmsh.model.occ.synchronize()
75
+
76
+ for key, value in self.dimTags.items():
77
+ # surface
78
+ surf_tag = gmsh.model.addPhysicalGroup(dim=value[0], tags=[value[1]], name=key)
79
+ self.dimTags_physical_surfaces.update({str(key):(2, surf_tag)})
80
+ # (outer) boundary curves
81
+ if 'Air' in key:
82
+ boundary_curves = gmsh.model.get_boundary([tuple(val) for val in self.dimTags.values()], combined=True)
83
+ elif 'Cable' in key:
84
+ boundary_curves = gmsh.model.get_boundary([tuple(value)])
85
+ elif 'Coil' in key:
86
+ boundary_curves = gmsh.model.get_boundary([tuple(value)])
87
+ else:
88
+ raise ValueError('Unknown volume in declaration in VI file.')
89
+ bnd_tag = gmsh.model.addPhysicalGroup(dim=1, tags=[dimTag[1] for dimTag in boundary_curves], name=key+'Boundary' )
90
+ self.dimTags_physical_boundaries.update({str(key+'Boundary' ):(1, bnd_tag)})
91
+ # arbitrary boundary point
92
+ point_tag = gmsh.model.addPhysicalGroup(dim=0, tags=[gmsh.model.get_boundary(boundary_curves, combined=False)[0][1]], name=key+'BoundaryPoint' )
93
+ self.dimTags_physical_points.update({str(key+'BoundaryPoint' ):(1, point_tag)})
94
+
95
+
96
+ def generate_cuts(self):
97
+ """ This function generates and orients the domain cuts for the cable and coil regions through the gmsh internal homology module."""
98
+
99
+ dimTags_physical_cable_boundaries = {k: v for k, v in self.dimTags_physical_boundaries.items() if 'Cable' in k}
100
+ dimTags_physical_excitation_coil_boundaries = {k: v for k, v in self.dimTags_physical_boundaries.items() if 'Coil' in k}
101
+
102
+ tags_physical_cable_surfaces = [v[1] for k, v in self.dimTags_physical_surfaces.items() if 'Cable' in k]
103
+ tags_physical_excitation_coil_surfaces = [v[1] for k, v in self.dimTags_physical_surfaces.items() if 'Coil' in k]
104
+
105
+ # print(dimTags_physical_cable_boundaries)
106
+ # print(dimTags_physical_excitation_coil_boundaries)
107
+ # print(tags_physical_cable_surfaces)
108
+ # print(tags_physical_excitation_coil_surfaces)
109
+
110
+ # cohomology cuts for all cables
111
+ for _, value in dimTags_physical_cable_boundaries.items():
112
+ gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[value[1]], dims=[1])
113
+ for _, value in dimTags_physical_excitation_coil_boundaries.items():
114
+ gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[value[1]], dims=[1])
115
+
116
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[self.dimTags_physical_surfaces['Air'][1]]+tags_physical_excitation_coil_surfaces, dims=[1])
117
+ for coil_surface in tags_physical_excitation_coil_surfaces:
118
+ tags_physical_excitation_other_coil_surfaces = [other_coil for other_coil in tags_physical_excitation_coil_surfaces if other_coil != coil_surface]
119
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[self.dimTags_physical_surfaces['Air'][1]]+tags_physical_cable_surfaces+tags_physical_excitation_other_coil_surfaces, dims=[1])
120
+
121
+
122
+
123
+ dimTags_homology = gmsh.model.mesh.computeHomology()
124
+ # print(dimTags_homology)
125
+ dimTags_bnds = dimTags_homology[:int(len(dimTags_homology)/2)] # first half are dimTags are the cut boundaries
126
+ dimTags_cuts = dimTags_homology[int(len(dimTags_homology)/2):] # second half are the actual cut edges
127
+ gmsh.model.mesh.clearHomologyRequests()
128
+
129
+ # post process homology cuts
130
+ bnd_tags = []
131
+ cut_tags = []
132
+ for i in range(len(dimTags_physical_cable_boundaries)):
133
+ self.dimTags_physical_cuts.update({'Cable'+str(i+1)+'Cut':(1, dimTags_cuts[-1][1]+(i+1))}) # +1 tag shift for post processed cuts
134
+ bnd_tags.append(dimTags_bnds[i][1])
135
+ cut_tags.append(dimTags_cuts[i][1])
136
+ for i in range(len(dimTags_physical_excitation_coil_boundaries)):
137
+ self.dimTags_physical_cuts.update({'Coil'+str(i+1)+'Cut':(1, dimTags_cuts[-1][1]+(len(dimTags_physical_cable_boundaries)+i+1))}) # +1 tag shift for post processed cuts
138
+ bnd_tags.append(dimTags_bnds[len(dimTags_physical_cable_boundaries)+i][1])
139
+ cut_tags.append(dimTags_cuts[len(dimTags_physical_cable_boundaries)+i][1])
140
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", ','.join(map(str,bnd_tags)))
141
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", ','.join(map(str,cut_tags)))
142
+ gmsh.plugin.run("HomologyPostProcessing")
143
+
144
+
145
+ def generate_regions_file(self):
146
+ """
147
+ Generates a .regions file for the GetDP solver, containing all necessary information about the model.
148
+ The regions model contains data about physical surfaces, boundaries, points and cuts and is stored in the mesh folder.
149
+
150
+ :raises ValueError: For unknown volumes in the vi-file
151
+ """
152
+ regions_model = RegionsModel()
153
+
154
+ # The region Cables will include the homogenized surfaces
155
+ regions_model.powered['Cables'] = RegionsModelFiQuS.Powered()
156
+ regions_model.powered['Cables'].vol.names = []
157
+ regions_model.powered['Cables'].vol.numbers = []
158
+ regions_model.powered['Cables'].surf.names = []
159
+ regions_model.powered['Cables'].surf.numbers = []
160
+ regions_model.powered['Cables'].curve.names = []
161
+ regions_model.powered['Cables'].curve.numbers = []
162
+ regions_model.powered['Cables'].cochain.names = []
163
+ regions_model.powered['Cables'].cochain.numbers = []
164
+ # Excitation coil regions
165
+ regions_model.powered['ExcitationCoils'] = RegionsModelFiQuS.Powered()
166
+ regions_model.powered['ExcitationCoils'].vol.names = []
167
+ regions_model.powered['ExcitationCoils'].vol.numbers = []
168
+ regions_model.powered['ExcitationCoils'].surf.names = []
169
+ regions_model.powered['ExcitationCoils'].surf.numbers = []
170
+ regions_model.powered['ExcitationCoils'].curve.names = []
171
+ regions_model.powered['ExcitationCoils'].curve.numbers = []
172
+ regions_model.powered['ExcitationCoils'].cochain.names = []
173
+ regions_model.powered['ExcitationCoils'].cochain.numbers = []
174
+
175
+ gmsh.model.occ.synchronize()
176
+ for name in self.dimTags.keys():
177
+ cut_name = name+'Cut'
178
+ boundary_name = name+'Boundary'
179
+ point_name = boundary_name+'Point'
180
+ if 'Air' in name:
181
+ regions_model.air.vol.number = self.dimTags_physical_surfaces[name][1]
182
+ regions_model.air.vol.name = "Air"
183
+ regions_model.air.surf.number = self.dimTags_physical_boundaries[boundary_name][1]
184
+ regions_model.air.surf.name = boundary_name
185
+ regions_model.air.point.names = [point_name]
186
+ regions_model.air.point.numbers = [self.dimTags_physical_points[point_name][1]]
187
+ elif 'Cable' in name:
188
+ regions_model.powered['Cables'].vol.names.append(name)
189
+ regions_model.powered['Cables'].vol.numbers.append(self.dimTags_physical_surfaces[name][1])
190
+ regions_model.powered['Cables'].surf.names.append(boundary_name)
191
+ regions_model.powered['Cables'].surf.numbers.append(self.dimTags_physical_boundaries[boundary_name][1])
192
+ regions_model.powered['Cables'].curve.names.append(point_name)
193
+ regions_model.powered['Cables'].curve.numbers.append(self.dimTags_physical_points[point_name][1])
194
+ regions_model.powered['Cables'].cochain.names.append(cut_name)
195
+ regions_model.powered['Cables'].cochain.numbers.append(self.dimTags_physical_cuts[cut_name][1])
196
+ elif 'Coil' in name:
197
+ regions_model.powered['ExcitationCoils'].vol.names.append(name)
198
+ regions_model.powered['ExcitationCoils'].vol.numbers.append(self.dimTags_physical_surfaces[name][1])
199
+ regions_model.powered['ExcitationCoils'].surf.names.append(boundary_name)
200
+ regions_model.powered['ExcitationCoils'].surf.numbers.append(self.dimTags_physical_boundaries[boundary_name][1])
201
+ regions_model.powered['ExcitationCoils'].curve.names.append(point_name)
202
+ regions_model.powered['ExcitationCoils'].curve.numbers.append(self.dimTags_physical_points[point_name][1])
203
+ regions_model.powered['ExcitationCoils'].cochain.names.append(cut_name)
204
+ regions_model.powered['ExcitationCoils'].cochain.numbers.append(self.dimTags_physical_cuts[cut_name][1])
205
+ else:
206
+ raise ValueError('Unknown physical region')
207
+
208
+ FilesAndFolders.write_data_to_yaml(self.regions_file, regions_model.model_dump())
209
+
210
+ def save_mesh(self, gui: bool = False):
211
+ """ Saves the mesh to a .msh file. If gui is True, the mesh is also loaded in the gmsh GUI. """
212
+
213
+ gmsh.write(self.mesh_file)
214
+ if gui:
215
+ self.gu.launch_interactive_GUI()
216
+ else:
217
+ if gmsh.isInitialized():
218
+ gmsh.clear()
219
+ gmsh.finalize()
220
+
221
+ def load_mesh(self, gui : bool = False):
222
+ """ Loads a previously generated mesh. """
223
+
224
+ gmsh.clear()
225
+ gmsh.open(self.mesh_file)
226
+
227
+ if gui:
228
+ self.gu.launch_interactive_GUI()
229
+
@@ -0,0 +1,142 @@
1
+ import os, logging
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+
5
+ logger = logging.getLogger('FiQuS')
6
+
7
+ class PostProcess:
8
+ def __init__(self, solution_folder_path):
9
+ self.solution_folder_path = solution_folder_path
10
+
11
+ self.plot_transport()
12
+ # self.plot_strand_split()
13
+ self.plot_fluxhyst()
14
+ self.plot_power_rohf()
15
+
16
+ def load_standard_txt(self, file_rel_path, skiprows=1):
17
+ """Load the content of a txt file into a nested numpy array
18
+
19
+ :param file_rel_path: relative file path in the solution folder (with file extension)
20
+ :param skiprows: number of rows to skip at the top of the txt file, defaults to 1
21
+ :return: nested np.array with time at index 0
22
+ """
23
+ file_path = os.path.join(self.solution_folder_path, file_rel_path)
24
+ return np.loadtxt(file_path, skiprows=skiprows).T if os.path.isfile(file_path) else None
25
+
26
+ def plot_transport(self):
27
+ """ Plots transport current and voltage over time if existing """
28
+
29
+ Vt_data = self.load_standard_txt('txt_files\\Vt.txt')
30
+ It_data = self.load_standard_txt('txt_files\\It.txt')
31
+
32
+ if all(x is not None for x in (It_data, Vt_data)):
33
+ fig, ax1 = plt.subplots()
34
+ ax1.set_title("Transport current and voltage")
35
+ ax1.set_xlabel(r'Time $(s)$')
36
+ ax1.set_ylabel(r'Current $(A)$', color = 'red')
37
+ ax1.plot(It_data[0], It_data[1], color = 'red')
38
+ ax1.grid(axis='x', linestyle='dotted')
39
+ ax1.tick_params(axis='y', labelcolor = 'red')
40
+
41
+ ax2 = ax1.twinx()
42
+ ax2.set_ylabel(r'Voltage $(V)$', color = 'blue')
43
+ ax2.plot(Vt_data[0], Vt_data[1], color = 'blue')
44
+ ax2.tick_params(axis='y', labelcolor = 'blue')
45
+ ax2.grid(axis='y', linestyle='dotted')
46
+ fig.tight_layout()
47
+
48
+ def plot_fluxhyst(self):
49
+ """ Plots the internal flux hysteresis and resulting voltages if existing (ROHF enabled and stranded strands) """
50
+
51
+ fluxhyst_data = self.load_standard_txt('txt_files\\flux_hyst.txt')
52
+ voltage_rohf = self.load_standard_txt('txt_files\\voltage_rohf.txt')
53
+ Is_data = self.load_standard_txt('txt_files\\Is.txt')
54
+
55
+ if all(x is not None for x in (fluxhyst_data, Is_data)):
56
+ # plot only the first quarter of the strands because of symmetry ...
57
+ q_idx = round((len(fluxhyst_data)-1) / 4)
58
+ colors = plt.cm.rainbow(np.linspace(0,1,q_idx))
59
+ plt.figure(figsize=(10,5))
60
+ # Flux in strand currents
61
+ plt.subplot(121)
62
+ plt.title('ROHF Flux hysteresis')
63
+ for i in range(1,q_idx+1):
64
+ plt.plot(Is_data[i], fluxhyst_data[i], label='Strand '+str(i), color=colors[i-1])
65
+ plt.xlabel(r"Strand current (A)")
66
+ plt.ylabel(r'Internal flux (Wb/m$)')
67
+ plt.legend()
68
+
69
+ # Resulting strand ROHF voltages
70
+ plt.subplot(122)
71
+ plt.title('ROHF voltages')
72
+ if voltage_rohf is not None:
73
+ for i in range(1, q_idx+1):
74
+ plt.plot(voltage_rohf[0], voltage_rohf[i], label='Strand '+str(i), color=colors[i-1])
75
+ #plt.plot(self.linflux_voltage[0], self.linflux_voltage[i], linestyle='dashed', color=colors[i-1])
76
+ plt.xlabel('Time (s)')
77
+ plt.ylabel(r'Voltage (V/m)')
78
+ plt.legend()
79
+
80
+ def plot_strand_split(self):
81
+ """ Plots the Strand currents and voltages for stranded strands enabled """
82
+
83
+ Vt_data = self.load_standard_txt('txt_files\\Vt.txt')
84
+ It_data = self.load_standard_txt('txt_files\\It.txt')
85
+ Vs_data = self.load_standard_txt('txt_files\\Vs.txt')
86
+ Is_data = self.load_standard_txt('txt_files\\Is.txt')
87
+
88
+ if all(x is not None for x in (It_data, Vt_data, Is_data, Vs_data)):
89
+ # plot only the first quarter of the strands because of symmetry ...
90
+ q_idx = round((len(Vs_data)-1) / 4)
91
+ colors = plt.cm.rainbow(np.linspace(0,1,q_idx))
92
+
93
+ plt.figure(figsize=(9,5))
94
+ plt.subplot(121)
95
+ plt.title('Strand voltages')
96
+ plt.plot(Vt_data[0], Vt_data[1], label='Total transport voltage')
97
+ for i in range(1,q_idx+1):
98
+ plt.plot(Vs_data[0], Vs_data[i], label='Strand '+str(i), color=colors[i-1])
99
+ plt.plot(Vs_data[0], sum(Vs_data[1:]), 'r.--', label=r'$\sum \ V_s$')
100
+ #plt.scatter(self.Vs_data[0], self.Vs_data[36], marker='x', label='Strand 36')
101
+ plt.xlabel(r"Time (s)")
102
+ plt.ylabel(r'Voltage (V/m)')
103
+ plt.legend()
104
+
105
+ plt.subplot(122)
106
+ plt.title('Strand currents')
107
+ plt.plot(It_data[0], It_data[1], label='Total transport current')
108
+ for i in range(1,q_idx+1):
109
+ plt.plot(Is_data[0], Is_data[i], label='Strand '+str(i), color=colors[i-1])
110
+ plt.plot(Is_data[0], sum(Is_data[1:]), 'r.--', label=r'$\sum \ I_s$')
111
+ #plt.scatter(self.Is_data[0], self.Is_data[36], marker='x', label='Strand 36')
112
+ plt.xlabel(r"Time (s)")
113
+ plt.ylabel(r'Current (A)')
114
+ plt.legend()
115
+
116
+ def plot_power_rohf(self):
117
+ """ Plots the ROHF related power contributions if existing """
118
+
119
+ power = self.load_standard_txt('txt_files\\power.txt')
120
+ power_hyst_rohf = self.load_standard_txt('txt_files\\power_hyst_ROHF.txt')
121
+ power_eddy_rohf = self.load_standard_txt('txt_files\\power_eddy_ROHF.txt')
122
+
123
+ if power is not None and len(power) == 5:
124
+ colors = plt.cm.rainbow(np.linspace(0,1,4))
125
+
126
+ plt.figure()
127
+ plt.title('Instantaneous power loss contributions')
128
+ #plt.plot(self.power[0], self.power[1], label='Total', color=colors[0])
129
+ plt.plot(power[0], power[2], label='hyst powerfile', linestyle='--', color=colors[1])
130
+ plt.plot(power_hyst_rohf[0], sum(power_hyst_rohf[1:]), label='Hyst', color=colors[1])
131
+ plt.plot(power[0], power[3], label='Eddy powerfile', linestyle='--', color=colors[2])
132
+ plt.plot(power_eddy_rohf[0], sum(power_eddy_rohf[1:]), label='Eddy', color=colors[2])
133
+ #plt.plot(power_hyst_strands[0], sum(power_hyst_strands[1:]), label='strands sum')
134
+ plt.xlabel(r"Time (s)")
135
+ plt.ylabel(r'Power loss (W/m)')
136
+ plt.legend()
137
+
138
+ def show(self):
139
+ """ Display all generated plots at once """
140
+ plt.show() if len(plt.get_fignums()) > 0 else logger.info('- NO POSTPROC DATA -')
141
+
142
+
@@ -0,0 +1,114 @@
1
+ import os, re, logging
2
+ import numpy as np
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+
6
+ logger = logging.getLogger('FiQuS')
7
+
8
+
9
+ class PostProcess():
10
+ """Class for python post processing of HomogenizedConductor simulations"""
11
+ def __init__(self, fdm, solution_folder_path):
12
+ self.fdm = fdm
13
+ self.solution_folder_path = solution_folder_path
14
+ # try to create ROHF plots
15
+ self.plot_density_lines()
16
+ self.fluxhyst()
17
+ self.power_loss()
18
+
19
+ def load_standard_txt(self, rel_file_path, skiprows=0, transpose=True):
20
+ """This function loads standards txt files within the solution folder to numpy arrays.
21
+
22
+ :param rel_file_path: relative path with file extension within the solution folder
23
+ """
24
+ abs_file_path = os.path.join(self.solution_folder_path, rel_file_path)
25
+ data = None
26
+ if os.path.isfile(abs_file_path):
27
+ data = np.loadtxt(abs_file_path, skiprows=skiprows).T if transpose else np.loadtxt(abs_file_path, skiprows=skiprows)
28
+ return data
29
+
30
+ def fluxhyst(self):
31
+ """This function tries to load and display the flux hysteresis (ROHF activated) in the solution data."""
32
+
33
+ fluxhyst = self.load_standard_txt('txt_files\\fluxhyst.txt')
34
+ #flux_average = self.load_standard_txt('txt_files\\flux_density_avg.txt')
35
+ #flux_strand = self.load_standard_txt('txt_files\\flux_density_strand_avg.txt')
36
+
37
+ voltage_rohf = self.load_standard_txt('txt_files\\voltage_rohf.txt')
38
+ It = self.load_standard_txt('txt_files\\It.txt', skiprows=1)
39
+
40
+ if fluxhyst is not None and It is not None:
41
+ plt.figure(figsize=(10,5))
42
+ plt.subplot(121)
43
+ plt.title('Flux hysteresis')
44
+ plt.plot(It[1], fluxhyst[1])
45
+ #plt.plot(It[1], fluxhyst[1] - mu_0/(4*pi) * It[1])
46
+ plt.xlabel('Transport current (A)')
47
+ plt.ylabel('Internal Flux (Tm)')
48
+ #plt.legend()
49
+
50
+ plt.subplot(122)
51
+ plt.title('ROHF voltage (unit length)')
52
+ if voltage_rohf is not None: plt.plot(voltage_rohf[0], voltage_rohf[1])
53
+ plt.xlabel('Time (s)')
54
+ plt.ylabel('Voltage (V)')
55
+ #plt.legend()
56
+ else:
57
+ logger.error("POSTPROC: No flux hysteresis data")
58
+
59
+ def plot_density_lines(self):
60
+
61
+ fluxdens_line = self.load_standard_txt('txt_files\\flux_density_line.txt', transpose=False)
62
+ currentdens_line = self.load_standard_txt('txt_files\\current_density_line.txt', transpose=False)
63
+ It = self.load_standard_txt('txt_files\\It.txt')
64
+
65
+ idx = 10
66
+
67
+ if fluxdens_line is not None and It is not None:
68
+ N_steps = len(It[0])
69
+ time = fluxdens_line[idx][1]
70
+ x_vals = np.zeros(200)
71
+ fluxdens = np.zeros(200)
72
+ for i in range(0,200):
73
+ x_vals[i] = fluxdens_line[idx+i*N_steps][2]
74
+ fluxdens[i] = fluxdens_line[idx+i*N_steps][5]
75
+
76
+ plt.figure()
77
+ plt.title('fluxdens at '+str(time) +'s')
78
+ plt.plot(x_vals, fluxdens)
79
+ plt.xlabel('Position on x-axis')
80
+ plt.ylabel('Flux density')
81
+
82
+ if currentdens_line is not None and It is not None:
83
+ N_steps = len(It[0])
84
+ time = currentdens_line[idx][1]
85
+ x_vals = np.zeros(200)
86
+ currentdens = np.zeros(200)
87
+ for i in range(0,200):
88
+ x_vals[i] = currentdens_line[idx+i*N_steps][2]
89
+ currentdens[i] = currentdens_line[idx+i*N_steps][7]
90
+
91
+ plt.figure()
92
+ plt.title('Current denisty at '+str(time) +'s')
93
+ plt.plot(x_vals, currentdens)
94
+ plt.xlabel('Position on x-axis')
95
+ plt.ylabel('Current density')
96
+
97
+ def power_loss(self):
98
+ """This function tries to load and display the power loss from the solution data. Currently only supports ROHF associated losses. """
99
+
100
+ power = self.load_standard_txt('txt_files\\power.txt')
101
+
102
+ if power is not None and len(power) == 5:
103
+ plt.figure()
104
+ plt.title('Instantaneous power losses')
105
+ plt.plot(power[0], power[2], label=r'$P_{\mathrm{irr}}$')
106
+ plt.plot(power[0], power[3], label=r'$P_{\mathrm{eddy}}$')
107
+ plt.plot(power[0], power[4], label=r'$P_{\mathrm{PL}}$')
108
+ plt.legend()
109
+ else:
110
+ logger.error("POSTPROC: no/wrong power data")
111
+
112
+ def show(self):
113
+ """Utility funtion which is called in the end of the python post proc to display all figures at the same time"""
114
+ plt.show() if len(plt.get_fignums()) > 0 else logger.info('- No postproc figures -')