fiqus 2025.12.0__py3-none-any.whl → 2026.1.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 (52) hide show
  1. fiqus/MainFiQuS.py +4 -8
  2. fiqus/data/DataConductor.py +108 -11
  3. fiqus/data/DataFiQuS.py +2 -1
  4. fiqus/data/DataFiQuSConductorAC_CC.py +345 -0
  5. fiqus/data/DataFiQuSConductorAC_Strand.py +3 -3
  6. fiqus/data/DataFiQuSMultipole.py +363 -165
  7. fiqus/data/DataModelCommon.py +30 -15
  8. fiqus/data/DataMultipole.py +33 -10
  9. fiqus/data/DataWindingsCCT.py +37 -37
  10. fiqus/data/RegionsModelFiQuS.py +1 -1
  11. fiqus/geom_generators/GeometryConductorAC_CC.py +1906 -0
  12. fiqus/geom_generators/GeometryMultipole.py +751 -54
  13. fiqus/getdp_runners/RunGetdpConductorAC_CC.py +123 -0
  14. fiqus/getdp_runners/RunGetdpMultipole.py +181 -31
  15. fiqus/mains/MainConductorAC_CC.py +148 -0
  16. fiqus/mains/MainMultipole.py +109 -17
  17. fiqus/mesh_generators/MeshCCT.py +209 -209
  18. fiqus/mesh_generators/MeshConductorAC_CC.py +1305 -0
  19. fiqus/mesh_generators/MeshMultipole.py +938 -263
  20. fiqus/parsers/ParserCOND.py +2 -1
  21. fiqus/parsers/ParserDAT.py +16 -16
  22. fiqus/parsers/ParserGetDPOnSection.py +212 -212
  23. fiqus/parsers/ParserGetDPTimeTable.py +134 -134
  24. fiqus/parsers/ParserMSH.py +53 -53
  25. fiqus/parsers/ParserRES.py +142 -142
  26. fiqus/plotters/PlotPythonCCT.py +133 -133
  27. fiqus/plotters/PlotPythonMultipole.py +18 -18
  28. fiqus/post_processors/PostProcessAC_CC.py +65 -0
  29. fiqus/post_processors/PostProcessMultipole.py +16 -6
  30. fiqus/pre_processors/PreProcessCCT.py +175 -175
  31. fiqus/pro_assemblers/ProAssembler.py +3 -3
  32. fiqus/pro_material_functions/ironBHcurves.pro +246 -246
  33. fiqus/pro_templates/combined/CAC_CC_template.pro +542 -0
  34. fiqus/pro_templates/combined/CC_Module.pro +1213 -0
  35. fiqus/pro_templates/combined/Multipole_template.pro +2738 -1338
  36. fiqus/pro_templates/combined/TSA_materials.pro +102 -2
  37. fiqus/pro_templates/combined/materials.pro +54 -3
  38. fiqus/utils/Utils.py +18 -25
  39. fiqus/utils/update_data_settings.py +1 -1
  40. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/METADATA +81 -77
  41. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/RECORD +52 -44
  42. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/WHEEL +1 -1
  43. tests/test_geometry_generators.py +47 -30
  44. tests/test_mesh_generators.py +69 -30
  45. tests/test_solvers.py +67 -29
  46. tests/utils/fiqus_test_classes.py +396 -147
  47. tests/utils/generate_reference_files_ConductorAC.py +57 -57
  48. tests/utils/helpers.py +76 -1
  49. /fiqus/pro_templates/combined/{ConductorACRutherford_template.pro → CAC_Rutherford_template.pro} +0 -0
  50. /fiqus/pro_templates/combined/{ConductorAC_template.pro → CAC_Strand_template.pro} +0 -0
  51. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info/licenses}/LICENSE.txt +0 -0
  52. {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,123 @@
1
+ import timeit
2
+ import logging
3
+ from enum import Enum
4
+ import os
5
+ import subprocess
6
+ import re
7
+ import pandas as pd
8
+
9
+ import gmsh
10
+ import numpy as np
11
+
12
+ #from fiqus.data import RegionsModelFiQuS
13
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
14
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
15
+ # import fiqus.data.DataConductorACGeom as geom
16
+
17
+ from fiqus.pro_assemblers.ProAssembler import ASS_PRO
18
+
19
+ logger = logging.getLogger('FiQuS')
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 assemble_pro(self):
47
+ logger.info("Assembling .pro file")
48
+ 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)
49
+
50
+ def read_excitation(self, inputs_folder_path):
51
+ """
52
+ Function for reading a CSV file for the 'piecewise' excitation case.
53
+
54
+ :param inputs_folder_path: The full path to the folder with input files.
55
+ :type inputs_folder_path: str
56
+ """
57
+ if self.cacdm.solve.source_parameters.source_type == 'piecewise' and self.cacdm.solve.source_parameters.piecewise.source_csv_file:
58
+ input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.piecewise.source_csv_file)
59
+ logger.info(f'Using excitation from file: {input_file}')
60
+ df = pd.read_csv(input_file, delimiter=',', engine='python')
61
+ excitation_time = df['time'].to_numpy(dtype='float').tolist()
62
+ self.ed['time'] = excitation_time
63
+ excitation_b = df['b'].to_numpy(dtype='float').tolist()
64
+ self.ed['b'] = excitation_b
65
+ excitation_I = df['I'].to_numpy(dtype='float').tolist()
66
+ self.ed['I'] = excitation_I
67
+
68
+ def run_getdp(self, solve = True, postOperation = True, gui = False):
69
+ command = ["-v2", "-verbose", "3"]
70
+ if solve:
71
+ command += ["-solve", "MagDyn", "-mat_mumps_icntl_14","100"] # icntl for mumps just by precaution
72
+ command += ["-pos", "MagDyn"]
73
+
74
+ logger.info(f"Running GetDP with command: {command}")
75
+ startTime = timeit.default_timer()
76
+
77
+ if self.cacdm.solve.general_parameters.noOfMPITasks:
78
+ mpi_prefix = ["mpiexec", "-np", str(self.cacdm.solve.general_parameters.noOfMPITasks)]
79
+ else:
80
+ mpi_prefix = []
81
+
82
+ getdpProcess = subprocess.Popen(mpi_prefix + [self.GetDP_path] + [self.pro_file] + command + ["-msh"] + [self.mesh_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
83
+
84
+ with getdpProcess.stdout:
85
+ for line in iter(getdpProcess.stdout.readline, b""):
86
+ line = line.decode("utf-8").rstrip()
87
+ line = line.split("\r")[-1]
88
+ if not "Test" in line:
89
+ if line.startswith("Info"):
90
+ parsedLine = re.sub(r"Info\s+:\s+", "", line)
91
+ logger.info(parsedLine)
92
+ elif line.startswith("Warning"):
93
+ parsedLine = re.sub(r"Warning\s+:\s+", "", line)
94
+ logger.warning(parsedLine)
95
+ elif line.startswith("Error"):
96
+ parsedLine = re.sub(r"Error\s+:\s+", "", line)
97
+ logger.error(parsedLine)
98
+ logger.error("Solving CAC failed.")
99
+ # raise Exception(parsedLine)
100
+ elif re.match("##", line):
101
+ logger.critical(line)
102
+ else:
103
+ logger.info(line)
104
+
105
+ simulation_time = timeit.default_timer()-startTime
106
+
107
+ if gui and ((postOperation and not solve) or (solve and postOperation)): # and self.cacdm.postproc.generate_pos_files
108
+ # gmsh.option.setNumber("Geometry.Volumes", 1)
109
+ # gmsh.option.setNumber("Geometry.Surfaces", 1)
110
+ # gmsh.option.setNumber("Geometry.Curves", 1)
111
+ # gmsh.option.setNumber("Geometry.Points", 0)
112
+ posFiles = [
113
+ fileName
114
+ for fileName in os.listdir(self.solution_folder)
115
+ if fileName.endswith(".pos")
116
+ ]
117
+ for posFile in posFiles:
118
+ gmsh.open(os.path.join(self.solution_folder, posFile))
119
+ self.gu.launch_interactive_GUI()
120
+ else:
121
+ if gmsh.isInitialized():
122
+ gmsh.clear()
123
+ gmsh.finalize()
@@ -4,6 +4,7 @@ import subprocess
4
4
  import logging
5
5
  import timeit
6
6
  import re
7
+ import pandas as pd
7
8
 
8
9
  import gmsh
9
10
  from fiqus.pro_assemblers.ProAssembler import ASS_PRO as aP
@@ -13,8 +14,9 @@ from fiqus.data import DataFiQuS as dF
13
14
  from fiqus.data import RegionsModelFiQuS as rM
14
15
  from fiqus.data import DataMultipole as dM
15
16
 
16
- logger = logging.getLogger(__name__)
17
+ logger = logging.getLogger('FiQuS')
17
18
 
19
+ trigger_time_deactivated = 99999
18
20
 
19
21
  class AssignNaming:
20
22
  def __init__(self, data: dF.FDM() = None):
@@ -24,16 +26,30 @@ class AssignNaming:
24
26
  """
25
27
  self.data: dF.FDM() = data
26
28
 
27
- self.naming_conv = {'omega': 'Omega', 'boundary': 'Bd_', 'powered': '_p', 'induced': '_i', 'air': '_a',
28
- 'air_far_field': '_aff', 'iron': '_bh', 'conducting': '_c', 'insulator': '_ins', 'terms': 'Terms'}
29
+ self.naming_conv = {'omega': 'Omega',
30
+ 'boundary': 'Bnd_',
31
+ 'powered': '_p', 'induced': '_i',
32
+ 'air': '_a',
33
+ 'air_far_field': '_aff',
34
+ 'conducting': '_c',
35
+ 'insulator': '_ins',
36
+ 'terms': 'Terms',
37
+ 'iron_yoke': '_bh', #todo: consider renaming this
38
+ 'collar': '_collar',
39
+ 'ref_mesh': '_refmesh',
40
+ 'poles': '_poles'}
41
+
29
42
  self.data.magnet.postproc.electromagnetics.volumes = \
30
- [self.naming_conv['omega'] + (self.naming_conv[var] if var != 'omega' else '') for var in self.data.magnet.postproc.electromagnetics.volumes]
43
+ [self.naming_conv['omega'] + (self.naming_conv[var] if var != 'omega' else '') for var in
44
+ self.data.magnet.postproc.electromagnetics.volumes]
31
45
  self.data.magnet.postproc.thermal.volumes = \
32
- [self.naming_conv['omega'] + (self.naming_conv[var] if var != 'omega' else '') for var in self.data.magnet.postproc.thermal.volumes]
46
+ [self.naming_conv['omega'] + (self.naming_conv[var] if var != 'omega' else '') for var in
47
+ self.data.magnet.postproc.thermal.volumes]
33
48
 
34
49
 
35
50
  class RunGetdpMultipole:
36
- def __init__(self, data: AssignNaming = None, solution_folder: str = None, GetDP_path: str = None, verbose: bool = False):
51
+ def __init__(self, data: AssignNaming = None, solution_folder: str = None, GetDP_path: str = None,
52
+ verbose: bool = False):
37
53
  """
38
54
  Class to solve pro file
39
55
  :param data: FiQuS data model
@@ -48,62 +64,116 @@ class RunGetdpMultipole:
48
64
  self.solution_folder = solution_folder
49
65
  self.GetDP_path = GetDP_path
50
66
  self.verbose: bool = verbose
51
- self.call_method = 'subprocess' # or onelab
67
+ self.call_method = 'subprocess' # or onelab or subprocess or alt_solver
52
68
 
53
69
  self.rm_EM = rM.RegionsModel()
54
70
  self.rm_TH = rM.RegionsModel()
55
- self.rc = dM.MultipoleRegionCoordinate()\
71
+ self.aux = {
72
+ "half_turns": {
73
+ "ht": {}, # Dictionary to store half-turn data
74
+ "max_reg": None, # Maximum region number to avoid overlaps of physical regions
75
+ "block": {} # Dictionary that stores the relation between magnet blocks(groups) and half-turn areas
76
+
77
+ },
78
+ "e_cliq": {}, # Dictionary that stores the E-CLIQ source current LUTs from a csv
79
+ "tsa_collar": {},
80
+ "sim_info": {
81
+ 'date': '',
82
+ 'author': ''
83
+ }
84
+ }
85
+ self.material_properties_model = None
86
+ self.rohm = {} # rohm parameter dictionary
87
+ self.rohf = {} # rohf parameter dictionary
88
+ self.rc = dM.MultipoleRegionCoordinate() \
56
89
  if self.data.magnet.mesh.thermal.isothermal_conductors and self.data.magnet.solve.thermal.solve_type else None
57
90
 
58
91
  self.gu = GmshUtils(self.solution_folder, self.verbose)
59
92
  self.gu.initialize(verbosity_Gmsh=self.data.run.verbosity_Gmsh)
60
93
  self.occ = gmsh.model.occ
61
94
  self.mesh = gmsh.model.mesh
62
-
63
- self.brep_iron_curves = {1: set(), 2: set(), 3: set(), 4: set()}
95
+ self.brep_curves = {}
96
+ for name in self.data.magnet.geometry.electromagnetics.areas:
97
+ self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
64
98
  self.mesh_files = os.path.join(os.path.dirname(self.solution_folder), self.data.general.magnet_name)
65
99
  self.model_file = os.path.join(self.solution_folder, 'Center_line.csv')
66
100
 
67
101
  self.mf = {'EM': f"{self.mesh_files}_EM.msh", 'TH': f"{self.mesh_files}_TH.msh"}
68
102
 
69
103
  def loadRegionFiles(self):
104
+ """
105
+ Read the regions' files and store the information in a variable. used for extracting the maximum region number.
106
+ :param
107
+ :return: Data from the regions files
108
+ """
70
109
  if self.data.magnet.solve.electromagnetics.solve_type:
71
110
  self.rm_EM = Util.read_data_from_yaml(f"{self.mesh_files}_EM.reg", rM.RegionsModel)
72
111
  if self.data.magnet.solve.thermal.solve_type:
73
112
  self.rm_TH = Util.read_data_from_yaml(f"{self.mesh_files}_TH.reg", rM.RegionsModel)
74
113
 
75
114
  def loadRegionCoordinateFile(self):
115
+ """
116
+ Read the reco file and store the information in a variable.
117
+ :param
118
+ :return: Data from the reco file
119
+ """
76
120
  self.rc = Util.read_data_from_yaml(f"{self.mesh_files}_TH.reco", dM.MultipoleRegionCoordinate)
77
121
 
78
122
  def assemblePro(self):
123
+ """
124
+ Assemble the .pro file using the right template depending on the inputs from the yaml
125
+ :param
126
+ :return: Assembled .pro file
127
+ """
79
128
  logger.info(f"Assembling pro file...")
80
129
  start_time = timeit.default_timer()
81
- ap = aP(file_base_path=os.path.join(self.solution_folder, self.data.general.magnet_name), naming_conv=self.naming_conv)
82
- BH_curves_path = os.path.join(pathlib.Path(os.path.dirname(__file__)).parent, 'pro_material_functions', 'ironBHcurves.pro')
83
- ap.assemble_combined_pro(template='Multipole_template.pro', rm_EM=self.rm_EM, rm_TH=self.rm_TH, rc=self.rc, dm=self.data, mf=self.mf, BH_curves_path=BH_curves_path)
130
+ ap = aP(file_base_path=os.path.join(self.solution_folder, self.data.general.magnet_name),
131
+ naming_conv=self.naming_conv)
132
+ BH_curves_path = os.path.join(pathlib.Path(os.path.dirname(__file__)).parent, 'pro_material_functions',
133
+ 'ironBHcurves.pro')
134
+ template = 'Multipole_template.pro'
135
+ ap.assemble_combined_pro(template=template, rm_EM=self.rm_EM, rm_TH=self.rm_TH, rc=self.rc, dm=self.data,
136
+ mf=self.mf, BH_curves_path=BH_curves_path, aux=self.aux)
84
137
  logger.info(
85
138
  f"Assembling pro file took"
86
139
  f" {timeit.default_timer() - start_time:.2f} s."
87
140
  )
88
141
 
89
142
  def solve_and_postprocess(self):
143
+ """
144
+ Generates the necessary GetDP commands to solve and postprocess the model, and runs them.
145
+ :param
146
+ :return: None
147
+ """
90
148
  commands = None
91
149
  if self.call_method == 'onelab':
92
150
  commands = f"-solve -v2 -pos -verbose {self.data.run.verbosity_GetDP}"
93
151
  elif self.call_method == 'subprocess':
94
- commands = []
95
- commands.append(["-solve", 'resolution', "-v2", "-pos", "Dummy", "-verbose", str(self.data.run.verbosity_GetDP)])
96
-
152
+ commands = [["-solve", 'resolution', "-v2", "-pos", "Dummy", "-verbose", str(self.data.run.verbosity_GetDP),
153
+ "-mat_mumps_icntl_14", "250"]]
97
154
  self._run(commands=commands)
98
155
 
99
156
  def postprocess(self):
157
+ """
158
+ Generates the necessary GetDP commands to postprocess the model, and runs them.
159
+ :param
160
+ :return: None
161
+ """
100
162
  if self.call_method == 'onelab':
101
- command = "-v2 -pos -verbose {self.data.run.verbosity_GetDP}"
163
+ commands = f"-v2 -pos -verbose {self.data.run.verbosity_GetDP}"
102
164
  elif self.call_method == 'subprocess':
103
- command = [["-v2", "-pos", "-verbose", str(self.data.run.verbosity_GetDP)]]
104
- self._run(commands=command)
165
+ commands = [["-v2", "-pos", "Dummy", "-verbose", str(self.data.run.verbosity_GetDP)]]
166
+ elif self.call_method == 'alt_solver':
167
+ commands = [["-solve", 'resolution', "-v2", "-pos", "Dummy"]]
168
+
169
+ self._run(commands=commands)
105
170
 
106
171
  def _run(self, commands):
172
+ """
173
+ runs GetDP with the specified commands. Additionally it captures and logs the output.
174
+ :param commands: List of commands to run GetDP with
175
+ :return: None
176
+ """
107
177
  logger.info("Solving...")
108
178
  start_time = timeit.default_timer()
109
179
  if self.call_method == 'onelab':
@@ -111,7 +181,7 @@ class RunGetdpMultipole:
111
181
  gmsh.onelab.run(f"{self.data.general.magnet_name}",
112
182
  f"{self.GetDP_path} {os.path.join(self.solution_folder, self.data.general.magnet_name)}.pro {command}")
113
183
  gmsh.onelab.setChanged("GetDP", 0)
114
- elif self.call_method == 'subprocess':
184
+ elif self.call_method == 'subprocess' or self.call_method == 'alt_solver':
115
185
  # subprocess.call([f"{self.GetDP_path}", f"{os.path.join(self.solution_folder, self.data.general.magnet_name)}.pro"] + command + ["-msh", f"{self.mesh_files}.msh"])
116
186
 
117
187
  # view_tag = gmsh.view.getTags() # this should be b
@@ -122,14 +192,26 @@ class RunGetdpMultipole:
122
192
  mpi_prefix = ["mpiexec", "-np", str(self.data.magnet.solve.noOfMPITasks)]
123
193
  else:
124
194
  mpi_prefix = []
125
-
195
+
126
196
  for command in commands:
127
- getdpProcess = subprocess.Popen(
128
- mpi_prefix + [f"{self.GetDP_path}", f"{os.path.join(self.solution_folder, self.data.general.magnet_name)}.pro"] +
129
- command,
130
- stdout=subprocess.PIPE,
131
- stderr=subprocess.STDOUT,
132
- )
197
+ use_predefined_pro = False # If True, one can use a predefined .pro file
198
+ if use_predefined_pro:
199
+ logger.warning("Using predefined .pro file")
200
+ getdpProcess = subprocess.Popen(
201
+ mpi_prefix + [f"{self.GetDP_path}",
202
+ f"{os.path.join(os.path.abspath(os.path.join(self.solution_folder, '..', '..', '..')), '_out/TEST')}.pro"] +
203
+ command,
204
+ stdout=subprocess.PIPE,
205
+ stderr=subprocess.STDOUT,
206
+ )
207
+ else:
208
+ getdpProcess = subprocess.Popen(
209
+ mpi_prefix + [f"{self.GetDP_path}",
210
+ f"{os.path.join(self.solution_folder, self.data.general.magnet_name)}.pro"] +
211
+ command,
212
+ stdout=subprocess.PIPE,
213
+ stderr=subprocess.STDOUT,
214
+ )
133
215
  with getdpProcess.stdout:
134
216
  for line in iter(getdpProcess.stdout.readline, b""):
135
217
  line = line.decode("utf-8").rstrip()
@@ -139,8 +221,7 @@ class RunGetdpMultipole:
139
221
  logger.info(parsedLine)
140
222
  elif "Warning" in line:
141
223
  parsedLine = re.sub(r"Warning\s*:\s*", "", line)
142
- if "Unknown" not in parsedLine:
143
- logger.warning(parsedLine)
224
+ logger.warning(parsedLine)
144
225
  elif "Error" in line:
145
226
  parsedLine = re.sub(r"Error\s*:\s*", "", line)
146
227
  logger.error(parsedLine)
@@ -163,8 +244,77 @@ class RunGetdpMultipole:
163
244
  )
164
245
 
165
246
  def ending_step(self, gui: bool = False):
247
+ """
248
+ Finalize the GetDP runner, either by launching the GUI or finalizing gmsh.
249
+ :param gui: If True, launches the interactive GUI; otherwise finalizes gmsh.
250
+ :return: None
251
+ """
166
252
  if gui:
167
253
  self.gu.launch_interactive_GUI()
168
254
  else:
169
- gmsh.clear()
170
- gmsh.finalize()
255
+ if gmsh.isInitialized():
256
+ gmsh.clear()
257
+ gmsh.finalize()
258
+
259
+ def read_aux_file(self, aux_file_path):
260
+ """
261
+ Read the auxiliary file and store the information in a variable. Necessary to extract half-turn information from blocks.
262
+ :param aux_file_path: Path to the auxiliary file
263
+ :return: Data from the auxiliary file
264
+ """
265
+ try:
266
+ self.aux_data = Util.read_data_from_yaml(aux_file_path, dM.MultipoleData)
267
+ logger.info(f"Auxiliary data loaded from {aux_file_path}")
268
+ except FileNotFoundError:
269
+ logger.error(f"Auxiliary file not found: {aux_file_path}")
270
+ except Exception as e:
271
+ logger.error(f"Error reading auxiliary file: {e}")
272
+
273
+ def extract_half_turn_blocks(self):
274
+ """
275
+ Extract a dictionary from the .aux file where the keys are integers corresponding to the block number
276
+ and the dictionary entries for these keys are a list of all the half-turn areas included in that block.
277
+ :return: Dictionary with block numbers as keys and lists of half-turn areas as values
278
+ """
279
+ aux_data = self.aux_data
280
+
281
+ half_turn_areas = {}
282
+ info_block = {}
283
+ coils = aux_data.geometries.coil.coils
284
+ for coil_nr, coil_data in coils.items():
285
+ poles = coil_data.poles
286
+ for pole_nr, pole_data in poles.items():
287
+ layers = pole_data.layers
288
+ for layer_nr, layer_data in layers.items():
289
+ windings = layer_data.windings
290
+ for winding_nr, winding_data in windings.items():
291
+ blocks = winding_data.blocks
292
+ for block_nr, block_data in blocks.items():
293
+ half_turns = block_data.half_turns.areas
294
+ info_block[block_nr] = [int(area) for area in half_turns.keys()]
295
+ half_turn_areas[int(block_nr)] = [int(area) for area in half_turns.keys()]
296
+
297
+ self.aux["half_turns"]["ht"] = half_turn_areas
298
+ self.aux["half_turns"]["block"] = info_block
299
+ pass
300
+
301
+ def extract_specific_TSA_lines(self):
302
+ """
303
+ Extract a dictionary from the thermal .aux file, collects the outer lines of the half-turns and wedges used for the TSA collar
304
+ """
305
+ aux_data = self.aux_data
306
+ pg = aux_data.domains.physical_groups
307
+ max_layer = len([k for k in self.aux_data.geometries.coil.coils[1].poles[1].layers.keys()]) # todo : more elegant way ? @emma
308
+ tags = []
309
+ for block in pg.blocks.values():
310
+ for ht in block.half_turns.values():
311
+ if ht.group[-1] == str(max_layer): # r1_a2 or r2_a2
312
+ tags.append(ht.lines['o'])
313
+ self.aux["tsa_collar"]['ht_lines'] = tags
314
+
315
+ tags = []
316
+ for wedge in pg.wedges.values():
317
+ if wedge.group[-1] == str(max_layer): # r1_a2 or r2_a2
318
+ tags.append(wedge.lines['o'])
319
+ self.aux["tsa_collar"]['wedge_lines'] = tags
320
+ pass
@@ -0,0 +1,148 @@
1
+
2
+ """MainConductorAC_CC.py:"""
3
+
4
+ import os
5
+ import sys
6
+
7
+ from fiqus.geom_generators.GeometryConductorAC_CC import Generate_geometry
8
+ from fiqus.mesh_generators.MeshConductorAC_CC import Mesh
9
+ from fiqus.getdp_runners.RunGetdpConductorAC_CC import Solve
10
+ from fiqus.post_processors.PostProcessAC_CC import Post_Process
11
+
12
+ if len(sys.argv) == 3:
13
+ sys.path.insert(0, os.path.join(os.getcwd(), 'steam-fiqus-dev'))
14
+ from fiqus.data.DataFiQuS import FDM
15
+
16
+ class MainConductorAC_CC:
17
+ def __init__(self, fdm, inputs_folder_path='', verbose=True):
18
+ """
19
+ Main class for working with simulations for CAC_CC type magnets
20
+ :param fdm: FiQuS data model
21
+ :type fdm: FDM
22
+ :param inputs_folder_path: full path to folder with input files, i.e. conductor and STEP files
23
+ :type inputs_folder_path: str
24
+ :param verbose: if True, more info is printed in the console
25
+ :type verbose: bool
26
+ :return: nothing, only saves files on disk
27
+ :rtype: none
28
+ """
29
+ self.verbose = verbose
30
+ self.fdm = fdm
31
+ self.inputs_folder_path = inputs_folder_path
32
+ self.GetDP_path = None
33
+ self.geom_folder = None
34
+ self.mesh_folder = None
35
+ self.solution_folder = None
36
+ self.model_file = None
37
+ self.model_folder = None
38
+
39
+ def generate_geometry(self, gui=False):
40
+ """
41
+ Main method for loading the geometry of CAC_CC models
42
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
43
+ :type gui: bool
44
+ :return: nothing, only saves files on disk
45
+ :rtype: none
46
+ """
47
+ os.chdir(self.geom_folder)
48
+ gg = Generate_geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
49
+ gg.generate_HTS_layer()
50
+ gg.generate_silver_top_layer()
51
+ gg.generate_substrate_layer()
52
+ gg.generate_copper_top_layer()
53
+ gg.generate_silver_bottom_layer()
54
+ gg.generate_copper_bottom_layer()
55
+ gg.generate_copper_left_layer()
56
+ gg.generate_copper_right_layer()
57
+ gg.generate_air_region()
58
+ gg.finalize_and_write()
59
+
60
+ def load_geometry(self, gui=False):
61
+ """
62
+ Main method for loading the geometry of CAC_CC models
63
+ """
64
+ os.chdir(self.geom_folder)
65
+ gg = Generate_geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
66
+ self.geometry = gg
67
+ gg.load_geometry(gui=gui)
68
+
69
+ def pre_process(self, gui=False):
70
+ """
71
+ Main method for preprocessing of CAC_CC models
72
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
73
+ :type gui: bool
74
+ :return: nothing, only saves files on disk
75
+ :rtype: none
76
+ """
77
+ os.chdir(self.geom_folder)
78
+
79
+
80
+ def mesh(self, gui=False):
81
+ """
82
+ Main method for building the mesh of CAC_CC models
83
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
84
+ :type gui: bool
85
+ :return: dictionary with mesh quality stats
86
+ :rtype: dict
87
+ """
88
+ os.chdir(self.mesh_folder)
89
+ m = Mesh(self.fdm)
90
+ m.generate_mesh(self.geom_folder)
91
+ return {"gamma": 0}
92
+
93
+
94
+ def load_mesh(self, gui=False):
95
+ """
96
+ Main method for loading the mesh of CAC_CC models
97
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
98
+ :type gui: bool
99
+ :return: Nothing, only saves files on disk
100
+ :rtype: none
101
+ """
102
+ os.chdir(self.mesh_folder)
103
+
104
+ def solve_and_postprocess_getdp(self, gui: bool = False):
105
+ """
106
+ Assembles the .pro-file from the template, then runs the simulation and the post-processing steps using GetDP.
107
+ """
108
+ os.chdir(self.solution_folder)
109
+
110
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
111
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
112
+ s.assemble_pro()
113
+ s.run_getdp(solve = True, postOperation = True, gui = gui)
114
+
115
+ def post_process_getdp(self, gui: bool = False):
116
+ """
117
+ Runs the post-processing steps trough GetDP.
118
+ """
119
+ os.chdir(self.solution_folder)
120
+
121
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
122
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
123
+ s.assemble_pro()
124
+ s.run_getdp(solve = False, postOperation = True, gui = gui)
125
+
126
+ def post_process_python(self, gui=False):
127
+ """
128
+ Main method for postprocessing using python (without solving) of CAC_CC models
129
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
130
+ :type gui: bool
131
+ :return: Nothing, only saves files on disk
132
+ :rtype: none
133
+ """
134
+ os.chdir(self.solution_folder)
135
+ p=Post_Process(self.fdm, verbose=self.verbose)
136
+ p.cleanup()
137
+ return {'overall_error': 0}
138
+
139
+ def plot_python(self, gui=False):
140
+ """
141
+ Main method for making python plots related to CAC_CC models
142
+ :param gui: if true, graphical user interface (gui) of Gmsh is opened at the end
143
+ :type gui: bool
144
+ :return: Nothing, only saves files on disk
145
+ :rtype: none
146
+ """
147
+ os.chdir(self.solution_folder)
148
+