fiqus 2026.1.0__py3-none-any.whl → 2026.1.2__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 +1 -8
- fiqus/data/DataConductor.py +4 -8
- fiqus/data/DataFiQuSMultipole.py +358 -167
- 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/GeometryMultipole.py +751 -54
- fiqus/getdp_runners/RunGetdpMultipole.py +181 -31
- fiqus/mains/MainMultipole.py +109 -17
- fiqus/mesh_generators/MeshCCT.py +209 -209
- 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/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/CC_Module.pro +1213 -0
- fiqus/pro_templates/combined/ConductorAC_template.pro +1025 -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-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/METADATA +64 -68
- {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/RECORD +42 -40
- {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/WHEEL +1 -1
- tests/test_geometry_generators.py +29 -32
- tests/test_mesh_generators.py +35 -34
- tests/test_solvers.py +32 -31
- tests/utils/fiqus_test_classes.py +396 -147
- tests/utils/generate_reference_files_ConductorAC.py +57 -57
- tests/utils/helpers.py +76 -1
- {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/LICENSE.txt +0 -0
- {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/top_level.txt +0 -0
|
@@ -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
|
fiqus/mains/MainMultipole.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import gmsh
|
|
3
4
|
import time
|
|
4
5
|
|
|
6
|
+
from fiqus.plotters.PlotPythonMultipole import PlotPythonMultipole
|
|
5
7
|
from fiqus.utils.Utils import GmshUtils
|
|
6
8
|
from fiqus.utils.Utils import FilesAndFolders as Util
|
|
7
9
|
from fiqus.data import DataFiQuS as dF
|
|
@@ -11,11 +13,10 @@ from fiqus.mesh_generators.MeshMultipole import Mesh
|
|
|
11
13
|
from fiqus.getdp_runners.RunGetdpMultipole import RunGetdpMultipole
|
|
12
14
|
from fiqus.getdp_runners.RunGetdpMultipole import AssignNaming
|
|
13
15
|
from fiqus.post_processors.PostProcessMultipole import PostProcess
|
|
14
|
-
from fiqus.plotters.PlotPythonMultipole import PlotPythonMultipole
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MainMultipole:
|
|
18
|
-
def __init__(self, fdm: dF.FDM = None, rgd_path: str = None, verbose: bool = None):
|
|
19
|
+
def __init__(self, fdm: dF.FDM = None, rgd_path: str = None, verbose: bool = None, inputs_folder_path = None):
|
|
19
20
|
"""
|
|
20
21
|
Main class for working with simulations for multipole type magnets
|
|
21
22
|
:param fdm: FiQuS data model
|
|
@@ -30,11 +31,13 @@ class MainMultipole:
|
|
|
30
31
|
self.geom_folder = None
|
|
31
32
|
self.mesh_folder = None
|
|
32
33
|
self.solution_folder = None
|
|
34
|
+
self.inputs_folder_path = inputs_folder_path
|
|
33
35
|
|
|
34
36
|
def force_symmetry(self):
|
|
35
37
|
fdm = self.fdm.__deepcopy__()
|
|
36
38
|
fdm.magnet.geometry.electromagnetics.symmetry = 'x'
|
|
37
39
|
return fdm
|
|
40
|
+
|
|
38
41
|
def generate_geometry(self, gui: bool = False):
|
|
39
42
|
geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
|
|
40
43
|
fdm = self.force_symmetry() if 'solenoid' in geom.Roxie_Data.coil.coils[1].type else self.fdm # todo: this should be handled by pydantic
|
|
@@ -43,14 +46,16 @@ class MainMultipole:
|
|
|
43
46
|
plotter.plot_coil_wedges()
|
|
44
47
|
gg = Geometry(data=fdm, geom=geom, geom_folder=self.geom_folder, verbose=self.verbose)
|
|
45
48
|
gg.saveHalfTurnCornerPositions()
|
|
49
|
+
|
|
46
50
|
geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
|
|
47
51
|
geometry_type_list = []
|
|
48
|
-
|
|
49
|
-
if
|
|
52
|
+
|
|
53
|
+
if geometry_settings['EM'].create: geometry_type_list.append('EM')
|
|
54
|
+
if geometry_settings['TH'].create: geometry_type_list.append('TH')
|
|
50
55
|
for geometry_type in geometry_type_list:
|
|
51
56
|
gg.saveStrandPositions(geometry_type)
|
|
52
|
-
if geometry_settings[geometry_type].
|
|
53
|
-
gg.constructIronGeometry(geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
|
|
57
|
+
if any(geometry_settings[geometry_type].areas):
|
|
58
|
+
gg.constructIronGeometry(geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none', geometry_settings[geometry_type], run_type = geometry_type)
|
|
54
59
|
gg.constructCoilGeometry(geometry_type)
|
|
55
60
|
if geometry_settings[geometry_type].with_wedges:
|
|
56
61
|
gg.constructWedgeGeometry(geometry_settings[geometry_type].use_TSA if geometry_type == 'TH' else False)
|
|
@@ -60,9 +65,17 @@ class MainMultipole:
|
|
|
60
65
|
gg.constructThinShells(geometry_settings[geometry_type].with_wedges)
|
|
61
66
|
else:
|
|
62
67
|
gg.constructInsulationGeometry()
|
|
68
|
+
if geometry_settings[geometry_type].use_TSA_new:
|
|
69
|
+
gg.constructAdditionalThinShells()
|
|
70
|
+
|
|
63
71
|
gg.buildDomains(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
|
|
64
72
|
if geometry_type == 'EM':
|
|
65
73
|
gg.fragment()
|
|
74
|
+
if geometry_type =='TH' and 'poles' in geometry_settings[geometry_type].areas:
|
|
75
|
+
# make sure geometry is connected
|
|
76
|
+
gmsh.model.occ.removeAllDuplicates()
|
|
77
|
+
gmsh.model.occ.synchronize()
|
|
78
|
+
|
|
66
79
|
gg.saveBoundaryRepresentationFile(geometry_type)
|
|
67
80
|
gg.loadBoundaryRepresentationFile(geometry_type)
|
|
68
81
|
gg.updateTags(geometry_type, geometry_settings[geometry_type].symmetry if geometry_type == 'EM' else 'none')
|
|
@@ -91,29 +104,84 @@ class MainMultipole:
|
|
|
91
104
|
gmsh.open(model_file + f'_{run_type}.brep')
|
|
92
105
|
|
|
93
106
|
def mesh(self, gui: bool = False):
|
|
94
|
-
|
|
107
|
+
def _create_physical_group_for_reference(self):
|
|
108
|
+
"""
|
|
109
|
+
This code generates the reference models for the FALCOND_C
|
|
110
|
+
"""
|
|
111
|
+
### hardcoded, we need only one reference
|
|
112
|
+
if self.fdm.general.magnet_name == 'TEST_MULTIPOLE_FALCOND_C_TSA_COLLAR_POLE':
|
|
113
|
+
CUT_REFERENCE = True # self specify loop and surf
|
|
114
|
+
|
|
115
|
+
L_col = [56, 57, 58, 35, 16, 49, 48, 47]
|
|
116
|
+
l1 = gmsh.model.occ.addLine(746,48)
|
|
117
|
+
l2 = gmsh.model.occ.addLine(41,691)
|
|
118
|
+
L_inner = list(range(854, 754-1, -1))
|
|
119
|
+
loop1 = gmsh.model.occ.addCurveLoop([l1] + L_col + [l2] + L_inner)
|
|
120
|
+
|
|
121
|
+
R_col = [52, 53, 54, 26, 6, 45, 44, 43]
|
|
122
|
+
r1 = gmsh.model.occ.addLine(38,902)
|
|
123
|
+
r2 = gmsh.model.occ.addLine(847,45)
|
|
124
|
+
R_inner = list(range(910, 1010+1))
|
|
125
|
+
loop2 = gmsh.model.occ.addCurveLoop([r2]+ R_col + [r1] + R_inner)
|
|
126
|
+
|
|
127
|
+
surf1 = gmsh.model.occ.addPlaneSurface([loop1])
|
|
128
|
+
surf2 = gmsh.model.occ.addPlaneSurface([loop2])
|
|
129
|
+
surf = [surf1, surf2] # tag
|
|
130
|
+
|
|
131
|
+
else: raise Exception("Reference meshing is not implemented for this magnet.")
|
|
132
|
+
gmsh.model.occ.synchronize()
|
|
133
|
+
|
|
134
|
+
#add to physical groups / domains -> write to aux file
|
|
135
|
+
file = os.path.join(self.geom_folder, self.fdm.general.magnet_name + '_TH.aux')
|
|
136
|
+
with open(file) as f:
|
|
137
|
+
lines = f.readlines()
|
|
138
|
+
updated_lines = []
|
|
139
|
+
flag = 0
|
|
140
|
+
for line in lines:
|
|
141
|
+
updated_lines.append(line)
|
|
142
|
+
if flag == 0 and 'groups_entities:' in line:
|
|
143
|
+
flag = 1
|
|
144
|
+
if flag==1 and ('ref_mesh: {}' in line):
|
|
145
|
+
if type(surf) == list:
|
|
146
|
+
updated_lines[-1] = f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: {surf}\n'
|
|
147
|
+
else:
|
|
148
|
+
updated_lines[-1] = f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: [{surf}]\n'
|
|
149
|
+
#if not CUT_REFERENCE else f' ref_mesh:\n {self.fdm.magnet.solve.thermal.insulation_TSA.between_collar.material}: '+ str(surf) +'\n'
|
|
150
|
+
flag = -1
|
|
151
|
+
|
|
152
|
+
with open(file, 'w') as f:
|
|
153
|
+
f.writelines(updated_lines)
|
|
154
|
+
logger = logging.getLogger('FiQuS')
|
|
155
|
+
logger.warning("Overwrite the .aux file to include the new domain")
|
|
156
|
+
|
|
157
|
+
mm = Mesh(data=self.fdm, mesh_folder=self.mesh_folder, verbose=self.verbose) ## same mesh object is used for both thermal and EM
|
|
95
158
|
geom = Util.read_data_from_yaml(self.rgd, FiQuSGeometry)
|
|
96
159
|
fdm = self.force_symmetry() if 'solenoid' in geom.Roxie_Data.coil.coils[1].type else self.fdm
|
|
97
160
|
geometry_settings = {'EM': fdm.magnet.geometry.electromagnetics, 'TH': self.fdm.magnet.geometry.thermal}
|
|
98
161
|
mesh_settings = {'EM': fdm.magnet.mesh.electromagnetics, 'TH': fdm.magnet.mesh.thermal}
|
|
162
|
+
|
|
99
163
|
mesh_type_list = []
|
|
100
|
-
if
|
|
101
|
-
if
|
|
164
|
+
if mesh_settings['EM'].create: mesh_type_list.append('EM')
|
|
165
|
+
if mesh_settings['TH'].create: mesh_type_list.append('TH')
|
|
102
166
|
for physics_solved in mesh_type_list:
|
|
103
167
|
self.load_geometry_for_mesh(physics_solved)
|
|
104
|
-
if physics_solved == 'TH' and
|
|
105
|
-
|
|
168
|
+
if physics_solved == 'TH' and mesh_settings['TH'].reference.enabled and 'collar' in geometry_settings['TH'].areas:
|
|
169
|
+
if self.fdm.magnet.geometry.thermal.use_TSA_new or self.fdm.magnet.geometry.thermal.use_TSA:
|
|
170
|
+
raise Exception('Reference solution is not implemented for collar with TSA')
|
|
171
|
+
if 'iron_yoke' in geometry_settings['TH'].areas:
|
|
172
|
+
raise Exception('Reference solution is intended (read: hardcoded) without iron yoke')
|
|
173
|
+
_create_physical_group_for_reference(self)
|
|
106
174
|
mm.loadAuxiliaryFile(physics_solved)
|
|
107
|
-
if geometry_settings[physics_solved].
|
|
108
|
-
mm.getIronCurvesTags()
|
|
175
|
+
if any(geometry_settings[physics_solved].areas):
|
|
176
|
+
mm.getIronCurvesTags(physics_solved)
|
|
109
177
|
mm.defineMesh(geometry_settings[physics_solved], mesh_settings[physics_solved], physics_solved)
|
|
110
178
|
mm.createPhysicalGroups(geometry_settings[physics_solved])
|
|
111
179
|
mm.updateAuxiliaryFile(physics_solved)
|
|
112
180
|
if geometry_settings[physics_solved].model_dump().get('use_TSA', False):
|
|
113
|
-
mm.rearrangeThinShellsData()
|
|
181
|
+
mm.rearrangeThinShellsData() # rearrange data for the pro file, technically optional
|
|
114
182
|
mm.assignRegionsTags(geometry_settings[physics_solved], mesh_settings[physics_solved])
|
|
115
183
|
mm.saveRegionFile(physics_solved)
|
|
116
|
-
mm.setMeshOptions(
|
|
184
|
+
mm.setMeshOptions()
|
|
117
185
|
mm.generateMesh()
|
|
118
186
|
mm.checkMeshQuality()
|
|
119
187
|
mm.saveMeshFile(physics_solved)
|
|
@@ -121,7 +189,10 @@ class MainMultipole:
|
|
|
121
189
|
mm.saveClosestNeighboursList()
|
|
122
190
|
if self.fdm.magnet.mesh.thermal.isothermal_conductors: mm.selectMeshNodes(elements='conductors')
|
|
123
191
|
if self.fdm.magnet.geometry.thermal.with_wedges and self.fdm.magnet.mesh.thermal.isothermal_wedges: mm.selectMeshNodes(elements='wedges')
|
|
124
|
-
|
|
192
|
+
if geometry_settings[physics_solved].model_dump().get('use_TSA_new', False):
|
|
193
|
+
mm.saveClosestNeighboursList_new_TSA()
|
|
194
|
+
mm.saveHalfTurnCornerPositions()
|
|
195
|
+
mm.saveRegionCoordinateFile(physics_solved)
|
|
125
196
|
mm.clear()
|
|
126
197
|
mm.ending_step(gui)
|
|
127
198
|
return mm.mesh_parameters
|
|
@@ -134,16 +205,37 @@ class MainMultipole:
|
|
|
134
205
|
|
|
135
206
|
def solve_and_postprocess_getdp(self, gui: bool = False):
|
|
136
207
|
an = AssignNaming(data=self.fdm)
|
|
137
|
-
rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path,
|
|
208
|
+
rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path,
|
|
209
|
+
verbose=self.verbose)
|
|
210
|
+
rg.loadRegionFiles()
|
|
211
|
+
if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
|
|
212
|
+
rg.loadRegionCoordinateFile()
|
|
213
|
+
rg.assemblePro()
|
|
214
|
+
start_time = time.time()
|
|
215
|
+
rg.solve_and_postprocess()
|
|
216
|
+
rg.ending_step(gui)
|
|
217
|
+
return time.time() - start_time
|
|
218
|
+
|
|
219
|
+
def solve_and_postprocess_getdp(self, gui: bool = False):
|
|
220
|
+
an = AssignNaming(data=self.fdm)
|
|
221
|
+
rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path,
|
|
222
|
+
verbose=self.verbose)
|
|
138
223
|
rg.loadRegionFiles()
|
|
139
224
|
if self.fdm.magnet.solve.thermal.solve_type and self.fdm.magnet.geometry.thermal.use_TSA:
|
|
140
225
|
rg.loadRegionCoordinateFile()
|
|
226
|
+
rg.read_aux_file(os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}_EM.aux"))
|
|
227
|
+
rg.extract_half_turn_blocks()
|
|
228
|
+
if self.fdm.magnet.geometry.thermal.use_TSA_new:
|
|
229
|
+
rg.read_aux_file(os.path.join(self.mesh_folder,
|
|
230
|
+
f"{self.fdm.general.magnet_name}_TH.aux")) # now load the thermal aux file
|
|
231
|
+
rg.extract_specific_TSA_lines()
|
|
141
232
|
rg.assemblePro()
|
|
142
233
|
start_time = time.time()
|
|
143
234
|
rg.solve_and_postprocess()
|
|
144
235
|
rg.ending_step(gui)
|
|
145
236
|
return time.time() - start_time
|
|
146
237
|
|
|
238
|
+
|
|
147
239
|
def post_process_getdp(self, gui: bool = False):
|
|
148
240
|
an = AssignNaming(data=self.fdm)
|
|
149
241
|
rg = RunGetdpMultipole(data=an, solution_folder=self.solution_folder, GetDP_path=self.GetDP_path, verbose=self.verbose)
|