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.
- fiqus/MainFiQuS.py +4 -8
- fiqus/data/DataConductor.py +108 -11
- fiqus/data/DataFiQuS.py +2 -1
- fiqus/data/DataFiQuSConductorAC_CC.py +345 -0
- fiqus/data/DataFiQuSConductorAC_Strand.py +3 -3
- fiqus/data/DataFiQuSMultipole.py +363 -165
- fiqus/data/DataModelCommon.py +30 -15
- fiqus/data/DataMultipole.py +33 -10
- fiqus/data/DataWindingsCCT.py +37 -37
- fiqus/data/RegionsModelFiQuS.py +1 -1
- fiqus/geom_generators/GeometryConductorAC_CC.py +1906 -0
- fiqus/geom_generators/GeometryMultipole.py +751 -54
- fiqus/getdp_runners/RunGetdpConductorAC_CC.py +123 -0
- fiqus/getdp_runners/RunGetdpMultipole.py +181 -31
- fiqus/mains/MainConductorAC_CC.py +148 -0
- fiqus/mains/MainMultipole.py +109 -17
- fiqus/mesh_generators/MeshCCT.py +209 -209
- fiqus/mesh_generators/MeshConductorAC_CC.py +1305 -0
- fiqus/mesh_generators/MeshMultipole.py +938 -263
- fiqus/parsers/ParserCOND.py +2 -1
- fiqus/parsers/ParserDAT.py +16 -16
- fiqus/parsers/ParserGetDPOnSection.py +212 -212
- fiqus/parsers/ParserGetDPTimeTable.py +134 -134
- fiqus/parsers/ParserMSH.py +53 -53
- fiqus/parsers/ParserRES.py +142 -142
- fiqus/plotters/PlotPythonCCT.py +133 -133
- fiqus/plotters/PlotPythonMultipole.py +18 -18
- fiqus/post_processors/PostProcessAC_CC.py +65 -0
- fiqus/post_processors/PostProcessMultipole.py +16 -6
- fiqus/pre_processors/PreProcessCCT.py +175 -175
- fiqus/pro_assemblers/ProAssembler.py +3 -3
- fiqus/pro_material_functions/ironBHcurves.pro +246 -246
- fiqus/pro_templates/combined/CAC_CC_template.pro +542 -0
- fiqus/pro_templates/combined/CC_Module.pro +1213 -0
- fiqus/pro_templates/combined/Multipole_template.pro +2738 -1338
- fiqus/pro_templates/combined/TSA_materials.pro +102 -2
- fiqus/pro_templates/combined/materials.pro +54 -3
- fiqus/utils/Utils.py +18 -25
- fiqus/utils/update_data_settings.py +1 -1
- {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/METADATA +81 -77
- {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/RECORD +52 -44
- {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info}/WHEEL +1 -1
- tests/test_geometry_generators.py +47 -30
- tests/test_mesh_generators.py +69 -30
- tests/test_solvers.py +67 -29
- tests/utils/fiqus_test_classes.py +396 -147
- tests/utils/generate_reference_files_ConductorAC.py +57 -57
- tests/utils/helpers.py +76 -1
- /fiqus/pro_templates/combined/{ConductorACRutherford_template.pro → CAC_Rutherford_template.pro} +0 -0
- /fiqus/pro_templates/combined/{ConductorAC_template.pro → CAC_Strand_template.pro} +0 -0
- {fiqus-2025.12.0.dist-info → fiqus-2026.1.1.dist-info/licenses}/LICENSE.txt +0 -0
- {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(
|
|
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',
|
|
28
|
-
'
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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),
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
+
commands = f"-v2 -pos -verbose {self.data.run.verbosity_GetDP}"
|
|
102
164
|
elif self.call_method == 'subprocess':
|
|
103
|
-
|
|
104
|
-
self.
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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.
|
|
170
|
-
|
|
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
|
+
|