fiqus 2025.2.0__py3-none-any.whl → 2025.10.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 +4 -9
  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.10.0.dist-info}/METADATA +65 -63
  44. fiqus-2025.10.0.dist-info/RECORD +86 -0
  45. {fiqus-2025.2.0.dist-info → fiqus-2025.10.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.10.0.dist-info}/LICENSE.txt +0 -0
  54. {fiqus-2025.2.0.dist-info → fiqus-2025.10.0.dist-info}/top_level.txt +0 -0
@@ -1,202 +1,342 @@
1
- import timeit
2
- import logging
3
- import os
4
- import subprocess
5
- import re
6
- import pandas as pd
7
- import pickle
8
- import numpy as np
9
-
10
- import gmsh
11
-
12
- from fiqus.utils.Utils import GmshUtils, FilesAndFolders
13
- from fiqus.data.RegionsModelFiQuS import RegionsModel
14
- import fiqus.data.DataFiQuSConductor as geom
15
- from fiqus.geom_generators.GeometryConductorAC_Strand import TwistedStrand
16
-
17
- from fiqus.pro_assemblers.ProAssembler import ASS_PRO
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
- class Solve:
22
- def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
23
- self.fdm = fdm
24
- self.cacdm = fdm.magnet
25
- self.GetDP_path = GetDP_path
26
- self.solution_folder = os.path.join(os.getcwd())
27
- self.magnet_name = fdm.general.magnet_name
28
- self.geometry_folder = geometry_folder
29
- self.mesh_folder = mesh_folder
30
- self.mesh_file = os.path.join(self.mesh_folder, f"{self.magnet_name}.msh")
31
- self.pro_file = os.path.join(self.solution_folder, f"{self.magnet_name}.pro")
32
- self.regions_file = os.path.join(mesh_folder, f"{self.magnet_name}.regions")
33
-
34
- self.verbose = verbose
35
- self.gu = GmshUtils(self.solution_folder, self.verbose)
36
- self.gu.initialize()
37
-
38
- self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.magnet_name))
39
- self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
40
- self.material_properties_model = None
41
-
42
- self.ed = {} # excitation dictionary
43
-
44
- gmsh.option.setNumber("General.Terminal", verbose)
45
-
46
- def read_excitation(self, inputs_folder_path):
47
- """
48
- Function for reading a CSV file for the 'from_file' excitation case.
49
-
50
- :param inputs_folder_path: The full path to the folder with input files.
51
- :type inputs_folder_path: str
52
- """
53
- if self.cacdm.solve.source_parameters.source_type == 'piecewise' and self.cacdm.solve.source_parameters.piecewise.source_csv_file:
54
- input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.piecewise.source_csv_file)
55
- print(f'Using excitation from file: {input_file}')
56
- df = pd.read_csv(input_file, delimiter=',', engine='python')
57
- excitation_time = df['time'].to_numpy(dtype='float').tolist()
58
- self.ed['time'] = excitation_time
59
- excitation_value = df['value'].to_numpy(dtype='float').tolist()
60
- self.ed['value'] = excitation_value
61
-
62
- def get_solution_parameters_from_yaml(self, inputs_folder_path):
63
- """
64
- Function for reading material properties from the geometry YAML file.
65
-
66
- This reads the 'solution' section of the YAML file and stores it in the solution folder.
67
- This could also be a place to change the material properties in the future.
68
-
69
- :param inputs_folder_path: The full path to the folder with input files.
70
- :type inputs_folder_path: str
71
- """
72
- if self.cacdm.geometry.io_settings.load.load_from_yaml:
73
- #load the geometry class from the pkl file
74
- geom_save_file = os.path.join(self.geometry_folder, f'{self.magnet_name}.pkl')
75
- with open(geom_save_file, "rb") as geom_save_file:
76
- geometry_class: TwistedStrand = pickle.load(geom_save_file)
77
-
78
- input_yaml_file = os.path.join(inputs_folder_path, self.cacdm.geometry.io_settings.load.filename)
79
- Conductor_dm = FilesAndFolders.read_data_from_yaml(input_yaml_file, geom.Conductor)
80
- solution_parameters = Conductor_dm.Solution
81
-
82
- # The geometry YAML file lists surfaces to exclude from the TI problem by their IDs. Here we convert these IDs to Gmsh physical surface tags.
83
- # This is done by comparing the outer boundary points of the surfaces to exclude with the outer boundary points of the matrix partitions.
84
- surfaces_excluded_from_TI_tags = []
85
-
86
- for surface_ID in solution_parameters.Surfaces_excluded_from_TI: # 1) Find the outer boundary points of the surfaces to exclude
87
- outer_boundary_points_a = []
88
- outer_boundary_curves_a = Conductor_dm.Geometry.Areas[surface_ID].Boundary
89
- for curve_ID in outer_boundary_curves_a:
90
- curve = Conductor_dm.Geometry.Curves[curve_ID]
91
- for point_ID in curve.Points:
92
- point = Conductor_dm.Geometry.Points[point_ID]
93
- outer_boundary_points_a.append(tuple(point.Coordinates))
94
-
95
- for matrix_partition in geometry_class.matrix: # 2) Find the outer boundary points of the matrix partitions
96
- outer_boundary_points_b = []
97
- outer_boundary_curves_b = matrix_partition.boundary_curves
98
- if len(outer_boundary_curves_b) == len(outer_boundary_curves_a): # If the number of boundary curves is different, the surfaces are not the same
99
- for curve in outer_boundary_curves_b:
100
- for point in curve.points:
101
- outer_boundary_points_b.append(tuple(point.pos))
102
-
103
- if np.allclose(sorted(outer_boundary_points_a), sorted(outer_boundary_points_b)): # If the outer boundary points are the same, the surfaces are the same
104
- surfaces_excluded_from_TI_tags.append(matrix_partition.physical_surface_tag) # 3) Add the physical surface tag to the list of surfaces to exclude
105
- break
106
-
107
- solution_parameters.Surfaces_excluded_from_TI = surfaces_excluded_from_TI_tags # Replace the surface IDs with the physical surface tags
108
-
109
- FilesAndFolders.write_data_to_yaml(os.path.join(self.solution_folder, "MaterialProperties.yaml"), solution_parameters.dict())
110
- self.material_properties_model = solution_parameters
111
-
112
-
113
-
114
-
115
- def assemble_pro(self):
116
- print("Assembling .pro file")
117
- self.ass_pro.assemble_combined_pro(template = self.cacdm.solve.pro_template, rm = self.regions_model, dm = self.fdm, ed=self.ed, mp=self.material_properties_model)
118
-
119
- def run_getdp(self, solve = True, postOperation = True, gui = False):
120
-
121
- command = ["-v2", "-verbose", "3"]
122
- if solve:
123
- command += ["-solve", "MagDyn"]
124
- if self.cacdm.solve.formulation_parameters.dynamic_correction:
125
- command += ["-pos", "MagDyn", "MagDyn_dynCorr"]
126
- else:
127
- command += ["-pos", "MagDyn"]
128
-
129
-
130
- startTime = timeit.default_timer()
131
- getdpProcess = subprocess.Popen([self.GetDP_path, self.pro_file, "-msh", self.mesh_file] + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
132
-
133
- with getdpProcess.stdout:
134
- for line in iter(getdpProcess.stdout.readline, b""):
135
- line = line.decode("utf-8").rstrip()
136
- line = line.split("\r")[-1]
137
- if not "Test" in line:
138
- if line.startswith("Info"):
139
- parsedLine = re.sub(r"Info\s+:\s+", "", line)
140
- logger.info(parsedLine)
141
- elif line.startswith("Warning"):
142
- parsedLine = re.sub(r"Warning\s+:\s+", "", line)
143
- logger.warning(parsedLine)
144
- elif line.startswith("Error"):
145
- parsedLine = re.sub(r"Error\s+:\s+", "", line)
146
- logger.error(parsedLine)
147
- logger.error("Solving CAC failed.")
148
- # raise Exception(parsedLine)
149
- elif re.match("##", line):
150
- logger.critical(line)
151
- else:
152
- logger.info(line)
153
-
154
- simulation_time = timeit.default_timer()-startTime
155
- # Save simulation time:
156
- if solve:
157
- logger.info(f"Solving CAC_1 has finished in {round(simulation_time, 3)} seconds.")
158
- with open(os.path.join(self.solution_folder, 'test_temporary', 'simulation_time.txt'), 'w') as file:
159
- file.write(str(simulation_time))
160
-
161
-
162
- if gui and ((postOperation and not solve) or (solve and postOperation and self.cacdm.postproc.generate_pos_files)):
163
- # gmsh.option.setNumber("Geometry.Volumes", 1)
164
- # gmsh.option.setNumber("Geometry.Surfaces", 1)
165
- # gmsh.option.setNumber("Geometry.Curves", 1)
166
- # gmsh.option.setNumber("Geometry.Points", 0)
167
- posFiles = [
168
- fileName
169
- for fileName in os.listdir(self.solution_folder)
170
- if fileName.endswith(".pos")
171
- ]
172
- for posFile in posFiles:
173
- gmsh.open(os.path.join(self.solution_folder, posFile))
174
- self.gu.launch_interactive_GUI()
175
- else:
176
- gmsh.clear()
177
- gmsh.finalize()
178
-
179
- def cleanup(self):
180
- """
181
- This funtion is used to remove .msh, .pre and .res files from the solution folder, as they may be large and not needed.
182
- """
183
- magnet_name = self.fdm.general.magnet_name
184
- cleanup = self.cacdm.postproc.cleanup
185
-
186
- if cleanup.remove_res_file:
187
- res_file_path = os.path.join(self.solution_folder, f"{magnet_name}.res")
188
- if os.path.exists(res_file_path):
189
- os.remove(res_file_path)
190
- logger.info(f"Removed {magnet_name}.res")
191
-
192
- if cleanup.remove_pre_file:
193
- pre_file_path = os.path.join(self.solution_folder, f"{magnet_name}.pre")
194
- if os.path.exists(pre_file_path):
195
- os.remove(pre_file_path)
196
- logger.info(f"Removed {magnet_name}.pre")
197
-
198
- if cleanup.remove_msh_file:
199
- msh_file_path = os.path.join(self.mesh_folder, f"{magnet_name}.msh")
200
- if os.path.exists(msh_file_path):
201
- os.remove(msh_file_path)
1
+ import timeit
2
+ import logging
3
+ import os
4
+ import subprocess
5
+ import re
6
+ import pandas as pd
7
+ import pickle
8
+ import numpy as np
9
+ import gmsh
10
+
11
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
12
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
13
+ import fiqus.data.DataFiQuSConductor as geom
14
+ from fiqus.geom_generators.GeometryConductorAC_Strand import TwistedStrand
15
+
16
+ from fiqus.pro_assemblers.ProAssembler import ASS_PRO
17
+
18
+ logger = logging.getLogger('FiQuS')
19
+
20
+
21
+ class Solve:
22
+ def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
23
+ self.fdm = fdm
24
+ self.cacdm = fdm.magnet
25
+ self.GetDP_path = GetDP_path
26
+ self.solution_folder = os.path.join(os.getcwd())
27
+ self.magnet_name = fdm.general.magnet_name
28
+ self.geometry_folder = geometry_folder
29
+ self.mesh_folder = mesh_folder
30
+ self.mesh_file = os.path.join(self.mesh_folder, f"{self.magnet_name}.msh")
31
+ self.pro_file = os.path.join(self.solution_folder, f"{self.magnet_name}.pro")
32
+ self.regions_file = os.path.join(mesh_folder, f"{self.magnet_name}.regions")
33
+
34
+ self.verbose = verbose
35
+ self.gu = GmshUtils(self.solution_folder, self.verbose)
36
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
37
+
38
+ self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.magnet_name))
39
+ self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
40
+ self.material_properties_model = None
41
+
42
+ self.ed = {} # excitation dictionary
43
+
44
+ gmsh.option.setNumber("General.Terminal", verbose)
45
+
46
+ def read_excitation(self, inputs_folder_path):
47
+ """
48
+ Function for reading a CSV file for the 'from_file' excitation case.
49
+
50
+ :param inputs_folder_path: The full path to the folder with input files.
51
+ :type inputs_folder_path: str
52
+ """
53
+ if self.cacdm.solve.source_parameters.source_type == 'piecewise' and self.cacdm.solve.source_parameters.piecewise.source_csv_file:
54
+ input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.piecewise.source_csv_file)
55
+ print(f'Using excitation from file: {input_file}')
56
+ df = pd.read_csv(input_file, delimiter=',', engine='python')
57
+ excitation_time = df['time'].to_numpy(dtype='float').tolist()
58
+ self.ed['time'] = excitation_time
59
+ excitation_value = df['value'].to_numpy(dtype='float').tolist()
60
+ self.ed['value'] = excitation_value
61
+
62
+ def get_solution_parameters_from_yaml(self, inputs_folder_path):
63
+ """
64
+ Function for reading material properties from the geometry YAML file.
65
+
66
+ This reads the 'solution' section of the YAML file and stores it in the solution folder.
67
+ This could also be a place to change the material properties in the future.
68
+
69
+ :param inputs_folder_path: The full path to the folder with input files.
70
+ :type inputs_folder_path: str
71
+ """
72
+ # load the geometry class from the .pkl file
73
+ geom_save_file = os.path.join(self.mesh_folder, f'{self.magnet_name}.pkl')
74
+ with open(geom_save_file, "rb") as geom_save_file:
75
+ geometry_class: TwistedStrand = pickle.load(geom_save_file)
76
+
77
+ if self.cacdm.geometry.io_settings.load.load_from_yaml:
78
+ # If the geometry is loaded from a YAML file, we need to read the solution parameters (material properties and surfaces to exclude from IP-problem) from the YAML file.
79
+ input_yaml_file = os.path.join(inputs_folder_path, self.cacdm.geometry.io_settings.load.filename)
80
+ Conductor_dm = FilesAndFolders.read_data_from_yaml(input_yaml_file, geom.Conductor)
81
+ solution_parameters = Conductor_dm.Solution
82
+
83
+ # The geometry YAML file lists surfaces to exclude from the TI problem by their IDs. Here we convert these IDs to Gmsh physical surface tags.
84
+ # This is done by comparing the outer boundary points of the surfaces to exclude with the outer boundary points of the matrix partitions.
85
+ surfaces_excluded_from_TI_tags = []
86
+
87
+ for surface_ID in solution_parameters.Surfaces_excluded_from_TI: # 1) Find the outer boundary points of the surfaces to exclude
88
+ outer_boundary_points_a = []
89
+ outer_boundary_curves_a = Conductor_dm.Geometry.Areas[surface_ID].Boundary
90
+ for curve_ID in outer_boundary_curves_a:
91
+ curve = Conductor_dm.Geometry.Curves[curve_ID]
92
+ for point_ID in curve.Points:
93
+ point = Conductor_dm.Geometry.Points[point_ID]
94
+ outer_boundary_points_a.append(tuple(point.Coordinates))
95
+
96
+ for matrix_partition in geometry_class.matrix: # 2) Find the outer boundary points of the matrix partitions
97
+ outer_boundary_points_b = []
98
+ outer_boundary_curves_b = matrix_partition.boundary_curves
99
+ if len(outer_boundary_curves_b) == len(outer_boundary_curves_a): # If the number of boundary curves is different, the surfaces are not the same
100
+ for curve in outer_boundary_curves_b:
101
+ for point in curve.points:
102
+ outer_boundary_points_b.append(tuple(point.pos))
103
+
104
+ if np.allclose(sorted(outer_boundary_points_a), sorted(outer_boundary_points_b)): # If the outer boundary points are the same, the surfaces are the same
105
+ surfaces_excluded_from_TI_tags.append(matrix_partition.physical_surface_tag) # 3) Add the physical surface tag to the list of surfaces to exclude
106
+ break
107
+
108
+ solution_parameters.Surfaces_excluded_from_TI = surfaces_excluded_from_TI_tags # Replace the surface IDs with the physical surface tags
109
+
110
+ else:
111
+ # If the geometry is not loaded from a YAML file, we initialize the solution parameters with an empty model, in which we may store the resistances of the diffusion barriers.
112
+ solution_parameters = geom.SolutionParameters()
113
+
114
+ if self.cacdm.solve.diffusion_barriers.enable:
115
+ # If the diffusion barriers are enabled, we either read the resistances of the barriers from the geometry YAML file or calculate them based on the material properties specified in the input YAML file.
116
+ if self.cacdm.geometry.io_settings.load.load_from_yaml and self.cacdm.solve.diffusion_barriers.load_data_from_yaml:
117
+ # If the diffusion barriers are enabled and the geometry YAML file provides information about the barriers, we can read the resistances from the YAML file.
118
+
119
+ # If the file provides information about the diffusion barriers we determine the resistance associated with the barrier from: Material, Circumferences, Thicknesses and periodicity length, ell.
120
+ # The code below loops trough all surfaces provided in the geometry file and determines which filaments each surface corresponds to.
121
+ # The resistances of the barriers are then calculated and stored in the solution parameters data structure which is accessed by the .pro template.
122
+ # The resistances are sorted by the physical surface tag of the surface to ensure that the order is consistent with the order of the surfaces in the .regions file.
123
+
124
+ # 1) Loop through all the areas in the geometry file
125
+ filament_barrier_resistances = []
126
+ filament_barrier_areas = []
127
+ for area_ID, area in Conductor_dm.Geometry.Areas.items():
128
+ # 2) If the area contains a BoundaryThickness, we continue
129
+ # print(f'Area ID: {area_ID}, area: {area}')
130
+ if area.BoundaryThickness:
131
+ # 3) We find the physical surface tag of the area by comparing the outer boundary points of the area with the outer boundary points of all surfaces
132
+ # surfaces = set(geometry_class.matrix + sum(geometry_class.filaments, [])) # All surfaces in the geometry
133
+ surfaces = set(sum(geometry_class.filaments, [])) # All surfaces in the geometry (only filaments are currently considered for diffusion barriers)
134
+ outer_boundary_points_a = []
135
+ outer_boundary_curves_a = area.Boundary
136
+ for curve_ID in outer_boundary_curves_a:
137
+ curve = Conductor_dm.Geometry.Curves[curve_ID]
138
+ for point_ID in curve.Points:
139
+ point = Conductor_dm.Geometry.Points[point_ID]
140
+ outer_boundary_points_a.append(tuple(point.Coordinates))
141
+
142
+ for surface in surfaces:
143
+ outer_boundary_points_b = []
144
+ outer_boundary_curves_b = surface.boundary_curves
145
+ if len(outer_boundary_curves_b) == len(outer_boundary_curves_a):
146
+ for curve in outer_boundary_curves_b:
147
+ for point in curve.points:
148
+ outer_boundary_points_b.append(tuple(point.pos))
149
+
150
+ if np.allclose(sorted(outer_boundary_points_a), sorted(outer_boundary_points_b)):
151
+ # 4) If the outer boundary points are the same we have found the surface corresponding to the area
152
+ # We calculate the resistance of the barrier and add it to the list of filament resistances
153
+ if self.cacdm.solve.formulation_parameters.two_ell_periodicity:
154
+ correctionFactor = 0.827
155
+ ell = 2 * correctionFactor * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.fil_twist_pitch / 6
156
+ else:
157
+ correctionFactor = 0.9549
158
+ ell = correctionFactor * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.fil_twist_pitch / 6
159
+ # If the .yaml contains information about the material of the diffusion barrier, use the given material property for this material...
160
+ if area.BoundaryMaterial:
161
+ material = Conductor_dm.Solution.Materials[area.BoundaryMaterial]
162
+ if material.Resistivity:
163
+ resistivity = float(material.Resistivity)
164
+ else:
165
+ resistivity = self.cacdm.solve.diffusion_barriers.resistivity
166
+ # ... otherwise, use the single (unique) value given in the model .yaml
167
+ else:
168
+ resistivity = self.cacdm.solve.diffusion_barriers.resistivity
169
+ circumference = surface.get_circumference()
170
+ thickness = area.BoundaryThickness
171
+
172
+ resistance = float(resistivity * thickness / (ell * circumference)) # R = rho * L / A. A is chosen as the area of the surface of the filament over the periodicity length
173
+ filament_barrier_resistances.append((surface.physical_surface_tag, resistance)) # Add the resistance to the list of filament resistances (along with the physical surface tag)
174
+ filament_barrier_areas.append(float(thickness * circumference))
175
+
176
+ surfaces = surfaces - {surface} # Remove the surface from the set of surfaces to speed up the search
177
+ break
178
+
179
+ filament_barrier_resistances.sort(key=lambda x: x[0]) # Sort the resistances by the physical surface tags to ensure that the order is consistent with the order of the surfaces in the .regions file
180
+
181
+ solution_parameters.DiffusionBarriers.FilamentResistances = [resistance for _, resistance in filament_barrier_resistances]
182
+ solution_parameters.DiffusionBarriers.DiffusionBarrierAreas = filament_barrier_areas
183
+
184
+ else:
185
+ # If the diffusion barriers are enabled but should not be read from any geometry YAML file, we need to calculate the resistances of the barriers according to the material properties specified in the YAML file.
186
+ # In this case we will also write a MaterialProperties.yaml file to the solution folder, but only with the resistances of the barriers.
187
+ # Note that we do this for each filament individually, as the resistances of the barriers may differ between filaments if their circumferences are different.
188
+ # The steps are the following:
189
+ # 1) First we get the circumference of each filament.
190
+ # 2) Then we calculate the resistance of the barrier for each filament, using the resistivity of the material, the thickness of the barrier, and the periodicity length, ell.
191
+ # 3) We then write the resistances to the MaterialProperties.yaml file.
192
+
193
+ if self.cacdm.solve.formulation_parameters.two_ell_periodicity:
194
+ correctionFactor = 0.827
195
+ ell = 2 * correctionFactor * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.fil_twist_pitch / 6
196
+ else:
197
+ correctionFactor = 0.9549
198
+ ell = correctionFactor * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.fil_twist_pitch / 6
199
+ barrier_resistivity = self.cacdm.solve.diffusion_barriers.resistivity
200
+ barrier_thickness = self.cacdm.solve.diffusion_barriers.thickness
201
+
202
+ filament_barrier_resistances = [] # List to store the resistances of the barriers
203
+ filament_barrier_areas = [] # List to store the surface areas of the barriers
204
+ for filament in sum(geometry_class.filaments, []): # Loop through all filaments
205
+ circumference = filament.get_circumference() # The circumference of the filament
206
+ resistance = float(barrier_resistivity * barrier_thickness / (ell * circumference)) # R = rho * L / A. A is determined as the area of the surface of the filament over the periodicity length
207
+ filament_barrier_resistances.append(resistance) # Add the resistance to the list of resistances
208
+ filament_barrier_areas.append(float(barrier_thickness * circumference)) # Add the surface area to the list of resistances
209
+
210
+ solution_parameters.DiffusionBarriers.FilamentResistances = filament_barrier_resistances # Add the resistances to the solution parameters
211
+ solution_parameters.DiffusionBarriers.DiffusionBarrierAreas = filament_barrier_areas # Add the surface areas to the solution parameters
212
+
213
+ if self.cacdm.solve.global_diffusion_barrier.enable:
214
+ # 1) Retrieve the tag of the correct matrix partition, it should be the only one with filaments inside.
215
+ # Here, we just take the partition with more than one internal boundary: it should be the correct one.
216
+ for matrix_partition in geometry_class.matrix:
217
+ # print(matrix_partition.inner_curve_loop_tags)
218
+ if len(matrix_partition.inner_curve_loop_tags) > 1:
219
+ tag = matrix_partition.physical_boundary_tag
220
+ surface_tag = matrix_partition.physical_surface_tag
221
+ # print(tag)
222
+ break
223
+ solution_parameters.GlobalDiffusionBarrier.RegionTag = tag
224
+ solution_parameters.GlobalDiffusionBarrier.InternalRegionTag = surface_tag
225
+ # 2) Compute the contact resistivity based on either the information in the .yaml input for geometry, or the data in the .yaml input for the main model
226
+ if self.cacdm.geometry.io_settings.load.load_from_yaml and self.cacdm.solve.global_diffusion_barrier.load_data_from_yaml:
227
+ # Find the corresponding surface from the .yaml input for geometry (the one with many inner surfaces)
228
+ for area_ID, area in Conductor_dm.Geometry.Areas.items():
229
+ if len(area.InnerBoundaries) > 1: # Same as above, we take the surface area which has more than one internal boundary, whose boundary should be the global diffusion barrier
230
+ thickness = area.BoundaryThickness
231
+ material = Conductor_dm.Solution.Materials[area.BoundaryMaterial]
232
+ if material.Resistivity:
233
+ resistivity = float(material.Resistivity)
234
+ else:
235
+ resistivity = self.cacdm.solve.global_diffusion_barrier.resistivity
236
+ solution_parameters.GlobalDiffusionBarrier.ContactResistivity = resistivity * thickness
237
+ else:
238
+ solution_parameters.GlobalDiffusionBarrier.ContactResistivity = self.cacdm.solve.global_diffusion_barrier.resistivity * self.cacdm.solve.global_diffusion_barrier.thickness
239
+
240
+ if self.cacdm.geometry.io_settings.load.load_from_yaml or self.cacdm.solve.diffusion_barriers.enable or self.cacdm.solve.global_diffusion_barrier.enable:
241
+ # If the geometry is loaded from a YAML file or the diffusion barriers are enabled, we need to write these solution parameters to a data-structure in the solution folder, which can be read by the template.
242
+ FilesAndFolders.write_data_to_yaml(os.path.join(self.solution_folder, "MaterialProperties.yaml"), solution_parameters.model_dump())
243
+ self.material_properties_model = solution_parameters
244
+
245
+ if self.cacdm.geometry.type == 'periodic_square' and not (self.cacdm.solve.source_parameters.sine.current_amplitude == 0.0):
246
+ raise ValueError(
247
+ f"FiQuS does not support periodic_square geometry type with non zero current amplitude!"
248
+ )
249
+
250
+ def assemble_pro(self):
251
+ print("Assembling .pro file")
252
+ self.ass_pro.assemble_combined_pro(template=self.cacdm.solve.pro_template, rm=self.regions_model, dm=self.fdm, ed=self.ed, mp=self.material_properties_model)
253
+
254
+ def run_getdp(self, solve=True, postOperation=True, gui=False):
255
+
256
+ command = ["-v2", "-verbose", str(self.fdm.run.verbosity_GetDP), "-mat_mumps_icntl_14", "100"]
257
+ if solve:
258
+ command += ["-solve", "MagDyn"]
259
+ if self.cacdm.solve.formulation_parameters.formulation == "CATI" and self.cacdm.solve.formulation_parameters.dynamic_correction:
260
+ command += ["-pos", "MagDyn", "MagDyn_dynCorr"]
261
+ else:
262
+ command += ["-pos", "MagDyn"]
263
+
264
+ if self.cacdm.solve.general_parameters.noOfMPITasks:
265
+ mpi_prefix = ["mpiexec", "-np", str(self.cacdm.solve.general_parameters.noOfMPITasks)]
266
+ else:
267
+ mpi_prefix = []
268
+
269
+ startTime = timeit.default_timer()
270
+
271
+ getdpProcess = subprocess.Popen(mpi_prefix + [self.GetDP_path, self.pro_file, "-msh", self.mesh_file] + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
272
+
273
+ with getdpProcess.stdout:
274
+ for line in iter(getdpProcess.stdout.readline, b""):
275
+ line = line.decode("utf-8").rstrip()
276
+ line = line.split("\r")[-1]
277
+ if not "Test" in line:
278
+ if line.startswith("Info"):
279
+ parsedLine = re.sub(r"Info\s+:\s+", "", line)
280
+ logger.info(parsedLine)
281
+ elif line.startswith("Warning"):
282
+ parsedLine = re.sub(r"Warning\s+:\s+", "", line)
283
+ logger.warning(parsedLine)
284
+ elif line.startswith("Error"):
285
+ parsedLine = re.sub(r"Error\s+:\s+", "", line)
286
+ logger.error(parsedLine)
287
+ logger.error("Solving CAC failed.")
288
+ # raise Exception(parsedLine)
289
+ elif re.match("##", line):
290
+ logger.critical(line)
291
+ else:
292
+ logger.info(line)
293
+
294
+ simulation_time = timeit.default_timer() - startTime
295
+ # Save simulation time:
296
+ if solve:
297
+ logger.info(f"Solving CAC_1 has finished in {round(simulation_time, 3)} seconds.")
298
+ with open(os.path.join(self.solution_folder, 'text_output', 'simulation_time.txt'), 'w') as file:
299
+ file.write(str(simulation_time))
300
+
301
+ if gui and ((postOperation and not solve) or (solve and postOperation and self.cacdm.postproc.pos_files.quantities is not [])):
302
+ # gmsh.option.setNumber("Geometry.Volumes", 1)
303
+ # gmsh.option.setNumber("Geometry.Surfaces", 1)
304
+ # gmsh.option.setNumber("Geometry.Curves", 1)
305
+ # gmsh.option.setNumber("Geometry.Points", 0)
306
+ posFiles = [
307
+ fileName
308
+ for fileName in os.listdir(self.solution_folder)
309
+ if fileName.endswith(".pos")
310
+ ]
311
+ for posFile in posFiles:
312
+ gmsh.open(os.path.join(self.solution_folder, posFile))
313
+ self.gu.launch_interactive_GUI()
314
+ else:
315
+ if gmsh.isInitialized():
316
+ gmsh.clear()
317
+ gmsh.finalize()
318
+
319
+ def cleanup(self):
320
+ """
321
+ This function is used to remove .msh, .pre and .res files from the solution folder, as they may be large and not needed.
322
+ """
323
+ magnet_name = self.fdm.general.magnet_name
324
+ cleanup = self.cacdm.postproc.cleanup
325
+
326
+ if cleanup.remove_res_file:
327
+ res_file_path = os.path.join(self.solution_folder, f"{magnet_name}.res")
328
+ if os.path.exists(res_file_path):
329
+ os.remove(res_file_path)
330
+ logger.info(f"Removed {magnet_name}.res")
331
+
332
+ if cleanup.remove_pre_file:
333
+ pre_file_path = os.path.join(self.solution_folder, f"{magnet_name}.pre")
334
+ if os.path.exists(pre_file_path):
335
+ os.remove(pre_file_path)
336
+ logger.info(f"Removed {magnet_name}.pre")
337
+
338
+ if cleanup.remove_msh_file:
339
+ msh_file_path = os.path.join(self.mesh_folder, f"{magnet_name}.msh")
340
+ if os.path.exists(msh_file_path):
341
+ os.remove(msh_file_path)
202
342
  logger.info(f"Removed {magnet_name}.msh")
@@ -110,12 +110,12 @@ class Solve(Base):
110
110
  self.mesh_data_file, Pancake3DMesh
111
111
  )
112
112
 
113
- if previousGeo.dict() != self.geo.dict():
113
+ if previousGeo.model_dump() != self.geo.model_dump():
114
114
  raise ValueError(
115
115
  "Geometry data has been changed. Please regenerate the geometry or load"
116
116
  " the previous geometry data."
117
117
  )
118
- elif previousMesh.dict() != self.mesh.dict():
118
+ elif previousMesh.model_dump() != self.mesh.model_dump():
119
119
  raise ValueError(
120
120
  "Mesh data has been changed. Please regenerate the mesh or load the"
121
121
  " previous mesh data."