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
@@ -1,4 +1,6 @@
1
1
  import os
2
+ import re
3
+
2
4
  import gmsh
3
5
  import copy
4
6
  import numpy as np
@@ -12,7 +14,7 @@ from fiqus.data import DataFiQuS as dF
12
14
  from fiqus.data import DataMultipole as dM
13
15
  from fiqus.data import RegionsModelFiQuS as rM
14
16
 
15
- logger = logging.getLogger(__name__)
17
+ logger = logging.getLogger('FiQuS')
16
18
 
17
19
  class Mesh:
18
20
  def __init__(self, data: dF.FDM() = None, mesh_folder: str = None,
@@ -36,7 +38,11 @@ class Mesh:
36
38
  self.occ = gmsh.model.occ
37
39
  self.mesh = gmsh.model.mesh
38
40
 
39
- self.brep_iron_curves = {1: set(), 2: set(), 3: set(), 4: set()}
41
+ self.brep_curves = {}
42
+ for name in list(
43
+ set(self.data.magnet.geometry.electromagnetics.areas + self.data.magnet.geometry.thermal.areas)):
44
+ self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
45
+
40
46
  self.mesh_parameters = dict.fromkeys(['SJ', 'SICN', 'SIGE', 'Gamma', 'nodes'])
41
47
  self.geom_files = os.path.join(os.path.dirname(self.mesh_folder), self.data.general.magnet_name)
42
48
  self.model_file = os.path.join(self.mesh_folder, self.data.general.magnet_name)
@@ -45,9 +51,11 @@ class Mesh:
45
51
  self.ins_type_cond = {}
46
52
  # Insulation sequence involving quench heaters (outlying or mid-layer/pole)
47
53
  qh_keys = {key: {} for key in range(1, self.data.quench_protection.quench_heaters.N_strips + 1)}
48
- self.ins_type_qh = {'internal_double': {}, 'internal': copy.deepcopy(qh_keys), 'external': copy.deepcopy(qh_keys)}
54
+ self.ins_type_qh = {'internal_double': {}, 'internal': copy.deepcopy(qh_keys),
55
+ 'external': copy.deepcopy(qh_keys)}
49
56
  # Insulation sequence between blocks (layer-to-layer, pole-to-pole)
50
- self.ins_type = {'mid_pole': {}, 'mid_winding': {}, 'mid_layer': {}, 'aux': {}}
57
+ self.ins_type = {'mid_pole': {}, 'mid_winding': {}, 'mid_layer': {}, 'aux': {}, 'collar': {}, 'poles': {}}
58
+
51
59
  self.qh_data, self.wedge_cond = {}, {}
52
60
 
53
61
  self.colors = {'wedges': [86, 180, 233], # sky blue
@@ -63,7 +71,8 @@ class Mesh:
63
71
  # key
64
72
  'BHiron3': [220, 208, 46], # dark yellow
65
73
  # [230, 159, 0], # orange
66
- # collar
74
+ 'BH_air': [255, 128, 0], # also orange
75
+ 'Air': [255, 128, 0], # also orange
67
76
  'BHiron5': [204, 121, 167], # hopbush
68
77
  'BHiron6': [0, 114, 178], # blue
69
78
  'BHiron7': [204, 121, 167]} # reddish purple
@@ -72,23 +81,30 @@ class Mesh:
72
81
  self.md = dM.MultipoleData()
73
82
  self.rc = dM.MultipoleRegionCoordinate()
74
83
  self.rm = rM.RegionsModel()
84
+ for name in self.brep_curves.keys():
85
+ self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
75
86
  gmsh.clear()
76
87
 
77
88
  def ending_step(self, gui: bool = False):
78
89
  if gui:
79
90
  self.gu.launch_interactive_GUI()
80
91
  else:
81
- gmsh.clear()
82
- gmsh.finalize()
92
+ if gmsh.isInitialized():
93
+ gmsh.clear()
94
+ gmsh.finalize()
83
95
 
84
96
  def loadStrandPositions(self, run_type):
85
- self.strands = json.load(open(f"{self.geom_files}_{run_type}.strs"))
97
+ with open(f"{self.geom_files}_{run_type}.strs", 'r') as f:
98
+ self.strands = json.load(f)
86
99
 
87
100
  def loadAuxiliaryFile(self, run_type):
88
101
  self.md = Util.read_data_from_yaml(f"{self.geom_files}_{run_type}.aux", dM.MultipoleData)
89
102
 
90
103
  def updateAuxiliaryFile(self, run_type):
91
104
  Util.write_data_to_yaml(f'{self.model_file}_{run_type}.aux', self.md.model_dump())
105
+ # md2 = Util.read_data_from_yaml(f"{self.geom_files}.aux", dM.MultipoleData)
106
+ # md2.domains.physical_groups = self.md.domains.physical_groups
107
+ # Util.write_data_to_yaml(f"{self.geom_files}.aux", md2.dict())
92
108
 
93
109
  def saveMeshFile(self, run_type):
94
110
  gmsh.write(f'{self.model_file}_{run_type}.msh')
@@ -99,11 +115,12 @@ class Mesh:
99
115
  def saveRegionCoordinateFile(self, run_type):
100
116
  Util.write_data_to_yaml(f'{self.model_file}_{run_type}.reco', self.rc.model_dump())
101
117
 
102
- def getIronCurvesTags(self):
103
- for quadrant, qq in self.md.geometries.iron.quadrants.items():
104
- for _, area in qq.areas.items():
105
- if area.surface:
106
- self.brep_iron_curves[quadrant] |= set(gmsh.model.getAdjacencies(2, area.surface)[1])
118
+ def getIronCurvesTags(self, physics_solved):
119
+ for t in self.data.magnet.geometry.electromagnetics.areas if physics_solved == 'EM' else self.data.magnet.geometry.thermal.areas:
120
+ for quadrant, qq in getattr(self.md.geometries, t).quadrants.items():
121
+ for ar_name, area in qq.areas.items():
122
+ if area.surface and not re.match(r"^ar.h", ar_name):
123
+ self.brep_curves[t][quadrant] |= set(gmsh.model.getAdjacencies(2, area.surface)[1])
107
124
 
108
125
  def defineMesh(self, geometry, mesh, run_type):
109
126
  thresholds = []
@@ -112,10 +129,14 @@ class Mesh:
112
129
  if mesh.conductors.field.enabled:
113
130
  distance_conductors = self.mesh.field.add("Distance")
114
131
  self.mesh.field.setNumbers(distance_conductors, "CurvesList",
115
- [line for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items() for layer_nr, layer in coil.layers.items()
116
- for _, block_order in enumerate(layer) for _, line in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
117
- layer_nr].windings[block_order.winding].blocks[block_order.block].half_turns.lines.items()]
118
- )
132
+ [line for coil_nr, coil in
133
+ self.md.geometries.coil.anticlockwise_order.coils.items() for layer_nr, layer in
134
+ coil.layers.items()
135
+ for _, block_order in enumerate(layer) for _, line in
136
+ self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
137
+ layer_nr].windings[block_order.winding].blocks[
138
+ block_order.block].half_turns.lines.items()]
139
+ )
119
140
  self.mesh.field.setNumber(distance_conductors, "Sampling", 100)
120
141
 
121
142
  threshold_conductors = self.mesh.field.add("Threshold")
@@ -130,8 +151,10 @@ class Mesh:
130
151
  if mesh.wedges.field.enabled:
131
152
  distance_wedges = self.mesh.field.add("Distance")
132
153
  self.mesh.field.setNumbers(distance_wedges, "CurvesList",
133
- [line for _, coil in self.md.geometries.wedges.coils.items() for _, layer in coil.layers.items() for _, wdg in layer.wedges.items() for _, line in wdg.lines.items()]
134
- )
154
+ [line for _, coil in self.md.geometries.wedges.coils.items() for _, layer in
155
+ coil.layers.items() for _, wdg in layer.wedges.items() for _, line in
156
+ wdg.lines.items()]
157
+ )
135
158
  self.mesh.field.setNumber(distance_wedges, "Sampling", 100)
136
159
 
137
160
  # raise Exception(f"cannot set threshold for wedges field: {[line for _, coil in self.md.geometries.wedges.coils.items() for _, layer in coil.layers.items() for _, wdg in layer.wedges.items() for _, line in wdg.lines.items()]}")
@@ -145,28 +168,37 @@ class Mesh:
145
168
  self.mesh.field.setNumber(threshold_wedges, "StopAtDistMax", 1)
146
169
  thresholds.append(threshold_wedges)
147
170
 
148
- if geometry.with_iron_yoke:
149
- distance_iron = self.mesh.field.add("Distance")
150
- self.mesh.field.setNumbers(distance_iron, "CurvesList", [line for _, qq in self.brep_iron_curves.items() for line in qq])
151
- self.mesh.field.setNumber(distance_iron, "Sampling", 100)
152
-
153
- if mesh.iron_field.enabled:
154
-
155
- threshold_iron = self.mesh.field.add("Threshold")
156
- self.mesh.field.setNumber(threshold_iron, "InField", distance_iron)
157
- self.mesh.field.setNumber(threshold_iron, "SizeMin", mesh.iron_field.SizeMin)
158
- self.mesh.field.setNumber(threshold_iron, "SizeMax", mesh.iron_field.SizeMax)
159
- self.mesh.field.setNumber(threshold_iron, "DistMin", mesh.iron_field.DistMin)
160
- self.mesh.field.setNumber(threshold_iron, "DistMax", mesh.iron_field.DistMax)
161
- # the iron field is typically rather coarse so that 'overwriting' other fields is not a problem
162
- # its distMax however would need to be large for StopAtDistMax to be effective
163
- #self.mesh.field.setNumber(threshold_iron, "StopAtDistMax", 1)
164
- thresholds.append(threshold_iron)
171
+ for area in geometry.areas:
172
+ distance = self.mesh.field.add("Distance")
173
+ if not (area == 'collar' and self.data.magnet.mesh.thermal.collar.Enforce_TSA_mapping and run_type == 'TH'):
174
+ self.mesh.field.setNumbers(distance, "CurvesList",
175
+ [line for _, qq in self.brep_curves[area].items() for line in qq])
176
+ else:
177
+ # make sure this does not interfere with the enforced TSA mapping. Apply threshold to the inner collar points and the cooling holes
178
+ vals = [x for sublist in self.md.geometries.collar.inner_boundary_tags.values() for x in
179
+ sublist] ## flatten
180
+ self.mesh.field.setNumbers(distance, "PointsList", list(set([point[1] for point in
181
+ gmsh.model.getBoundary(
182
+ [(1, line) for line in vals],
183
+ combined=False, oriented=False)])))
184
+
185
+ self.mesh.field.setNumber(distance, "Sampling", 100)
186
+
187
+ k = area if area != 'iron_yoke' else 'iron_field'
188
+ if getattr(mesh, k).enabled:
189
+ threshold = self.mesh.field.add("Threshold")
190
+ self.mesh.field.setNumber(threshold, "InField", distance)
191
+ self.mesh.field.setNumber(threshold, "SizeMin", getattr(mesh, k).SizeMin)
192
+ self.mesh.field.setNumber(threshold, "SizeMax", getattr(mesh, k).SizeMax)
193
+ self.mesh.field.setNumber(threshold, "DistMin", getattr(mesh, k).DistMin)
194
+ self.mesh.field.setNumber(threshold, "DistMax", getattr(mesh, k).DistMax)
195
+ thresholds.append(threshold)
165
196
 
166
197
  if run_type == 'EM' and mesh.bore_field.enabled:
167
-
168
198
  distance_bore = self.mesh.field.add("Distance")
169
- self.mesh.field.setNumbers(distance_bore, "PointsList", [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore' in pnt_name])
199
+ self.mesh.field.setNumbers(distance_bore, "PointsList",
200
+ [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if
201
+ 'bore' in pnt_name])
170
202
  self.mesh.field.setNumber(distance_bore, "Sampling", 100)
171
203
 
172
204
  threshold_bore = self.mesh.field.add("Threshold")
@@ -178,11 +210,34 @@ class Mesh:
178
210
  self.mesh.field.setNumber(threshold_bore, "StopAtDistMax", 1)
179
211
  thresholds.append(threshold_bore)
180
212
 
213
+ if run_type == 'TH' and mesh.reference.enabled: # add reference meshing between collar and insulation
214
+ """
215
+ (hardcoded) REFERENCE MESH for the FalconD-type magnet
216
+ """
217
+ if not 'collar' in geometry.areas:
218
+ raise Exception("Adding the reference segment without collar is not intended.")
219
+ distance_ref = self.mesh.field.add("Distance")
220
+ if self.data.general.magnet_name == 'TEST_MULTIPOLE_FALCOND_C_TSA_COLLAR_POLE':
221
+ lines = list(range(754, 855)) + list(range(910, 1011))
222
+ self.mesh.field.setNumbers(distance_ref, "CurvesList", lines)
223
+ else:
224
+ raise Exception("Reference meshing is not implemented for this magnet.")
225
+
226
+ self.mesh.field.setNumber(distance_ref, "Sampling", 100)
227
+
228
+ threshold_ref = self.mesh.field.add("Threshold")
229
+ self.mesh.field.setNumber(threshold_ref, "InField", distance_ref)
230
+ self.mesh.field.setNumber(threshold_ref, "SizeMin", mesh.reference.SizeMin)
231
+ self.mesh.field.setNumber(threshold_ref, "SizeMax", mesh.reference.SizeMax)
232
+ self.mesh.field.setNumber(threshold_ref, "DistMin", mesh.reference.DistMin)
233
+ self.mesh.field.setNumber(threshold_ref, "DistMax", mesh.reference.DistMax)
234
+ self.mesh.field.setNumber(threshold_ref, "StopAtDistMax", 1)
235
+ thresholds.append(threshold_ref)
236
+
181
237
  insulation_mesh_fields = []
182
238
  if run_type == 'TH':
183
239
  for coil_nr, coil in self.md.geometries.insulation.coils.items():
184
240
  for _, group in coil.group.items():
185
- # Areas
186
241
  for area_name, area in group.ins.areas.items():
187
242
  if area_name.isdigit():
188
243
  insulation_mesh_fields.append(self.mesh.field.add("Constant"))
@@ -206,40 +261,85 @@ class Mesh:
206
261
  if mesh.model_dump().get('isothermal_conductors', False):
207
262
  elements = 1
208
263
  elif any([i in line_key for i in ['i', 'o']]):
209
- elements = max(1, round(cable.bare_cable_height_mean / mesh.conductors.transfinite.curve_target_size_height))
264
+ elements = max(1, round(
265
+ cable.bare_cable_height_mean / mesh.conductors.transfinite.curve_target_size_height))
210
266
  elif any([i in line_key for i in ['l', 'h']]):
211
- elements = max(1, round(cable.bare_cable_width / mesh.conductors.transfinite.curve_target_size_width))
212
-
267
+ elements = max(1, round(
268
+ cable.bare_cable_width / mesh.conductors.transfinite.curve_target_size_width))
269
+
213
270
  self.mesh.setTransfiniteCurve(line, elements)
214
- if mesh.conductors.transfinite.enabled_for=='curves_and_surfaces' or mesh.model_dump().get('isothermal_conductors', False):
271
+ if mesh.conductors.transfinite.enabled_for == 'curves_and_surfaces' or mesh.model_dump().get(
272
+ 'isothermal_conductors', False):
215
273
  for _, area in winding.blocks[block_order.block].half_turns.areas.items():
216
274
  self.mesh.setTransfiniteSurface(area.surface)
217
275
  self.mesh.setRecombine(2, area.surface)
218
-
276
+
219
277
  if 'insulation' in mesh.model_dump() and 'TSA' in mesh.model_dump()["insulation"]:
220
278
  # Apply transfinite curves to thin shell lines
221
279
  if geometry.use_TSA:
222
280
  gts = self.md.geometries.thin_shells
223
- conductor_target_sizes = {'width': mesh.conductors.transfinite.curve_target_size_width, 'height': mesh.conductors.transfinite.curve_target_size_height}
224
- wedge_target_sizes = {'width': mesh.wedges.transfinite.curve_target_size_width, 'height': mesh.wedges.transfinite.curve_target_size_height}
225
- for ts_group, side in zip([gts.mid_layers_ht_to_ht, gts.mid_layers_ht_to_wdg, gts.mid_layers_wdg_to_ht, gts.mid_layers_wdg_to_wdg,
226
- gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn, gts.mid_layers_aux],
227
- ['height', 'height', 'height', 'height', 'width', 'width', 'width', 'width', 'height']):
281
+ conductor_target_sizes = {'width': mesh.conductors.transfinite.curve_target_size_width,
282
+ 'height': mesh.conductors.transfinite.curve_target_size_height}
283
+ wedge_target_sizes = {'width': mesh.wedges.transfinite.curve_target_size_width,
284
+ 'height': mesh.wedges.transfinite.curve_target_size_height}
285
+ for ts_group, side in zip([gts.mid_layers_ht_to_ht, gts.mid_layers_ht_to_wdg, gts.mid_layers_wdg_to_ht,
286
+ gts.mid_layers_wdg_to_wdg,
287
+ gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn,
288
+ gts.mid_layers_aux],
289
+ ['height', 'height', 'height', 'height', 'width', 'width', 'width', 'width',
290
+ 'height']):
228
291
  for ts_name, ts in ts_group.items():
229
292
  for _, line in ts.lines.items() if isinstance(ts, dM.Region) else ts.mid_layers.lines.items():
230
293
  if mesh.isothermal_conductors or mesh.isothermal_wedges:
231
294
  elements = 1
232
295
  else:
233
- coords = gmsh.model.getValue(1, line, [i[0] for i in gmsh.model.getParametrizationBounds(1, line)])
234
- target_size = wedge_target_sizes[side] if ts_name.count('w') == 2 else conductor_target_sizes[side]
296
+ coords = gmsh.model.getValue(1, line, [i[0] for i in
297
+ gmsh.model.getParametrizationBounds(1, line)])
298
+ target_size = wedge_target_sizes[side] if ts_name.count('w') == 2 else \
299
+ conductor_target_sizes[side]
235
300
  elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size))
236
-
237
301
  # it's a wedge
238
- if ts_name.count('w') == 2 and mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
302
+ if ts_name.count('w') == 2 and mesh.wedges.transfinite.enabled_for in ['curves',
303
+ 'curves_and_surfaces']:
239
304
  self.mesh.setTransfiniteCurve(line, elements)
240
- elif ts_name.count('w') != 2 and mesh.conductors.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
241
- self.mesh.setTransfiniteCurve(line, elements)
242
- # COMMENTED since this overwrites also the cable transfinite meshes and in general,
305
+ elif ts_name.count('w') != 2 and mesh.conductors.transfinite.enabled_for in ['curves',
306
+ 'curves_and_surfaces']:
307
+ self.mesh.setTransfiniteCurve(line, elements)
308
+
309
+ if geometry.use_TSA_new:
310
+ if not self.data.magnet.mesh.thermal.collar.Enforce_TSA_mapping:
311
+ r_tmp = np.abs(Func.points_distance(coords[:2], [0, 0]))
312
+ gts = self.md.geometries.thin_shells.collar_layers
313
+ # conductor and wedge target sizes are defined above
314
+ collar_size = None
315
+ # accounts for the distance between the collar and the TSA line: should be col2 = col1*r2/r1. r_tmp is approx. distance outer ht to centre
316
+ for ts_name, ts in gts.items():
317
+ for name, line in ts.lines.items():
318
+ coords = gmsh.model.getValue(1, line,
319
+ [i[0] for i in gmsh.model.getParametrizationBounds(1, line)])
320
+ if collar_size is None:
321
+ collar_size = r_tmp / Func.points_distance(coords[:2], [0,
322
+ 0]) * self.data.magnet.mesh.thermal.collar.SizeMin
323
+ target_size = min(
324
+ wedge_target_sizes['width'] if ts_name.startswith('w') else conductor_target_sizes[
325
+ 'width'], collar_size)
326
+ elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size))
327
+
328
+ self.mesh.setTransfiniteCurve(line, elements)
329
+ else: # since we force the mesh on the collar side anyway
330
+ gts = self.md.geometries.thin_shells.collar_layers
331
+ # conductor and wedge target sizes are defined above
332
+ collar_size = self.data.magnet.mesh.thermal.collar.SizeMin
333
+ for ts_name, ts in gts.items():
334
+ for name, line in ts.lines.items():
335
+ coords = gmsh.model.getValue(1, line,
336
+ [i[0] for i in gmsh.model.getParametrizationBounds(1, line)])
337
+ target_size = collar_size
338
+ elements = max(1, round(Func.points_distance(coords[:2], coords[3:-1]) / target_size)) + 1
339
+ if elements % 2 == 1: elements += 1
340
+ self.mesh.setTransfiniteCurve(line, elements)
341
+
342
+ # COMMENTED since this overwrites also the cable transfinite meshes and in general,
243
343
  # restricting the insulation boundaries to be transfinite seems very restrictive due to their complex geometry
244
344
  # Apply transfinite curves to insulation boundaries
245
345
  # else:
@@ -264,24 +364,30 @@ class Mesh:
264
364
  for layer_nr, layer in coil.layers.items():
265
365
  for _, wedge in layer.wedges.items():
266
366
  pnts = gmsh.model.getAdjacencies(1, wedge.lines['i'])[1]
267
- inner_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], []))
367
+ inner_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
368
+ gmsh.model.getValue(0, pnts[1], []))
268
369
  pnts = gmsh.model.getAdjacencies(1, wedge.lines['l'])[1]
269
- width = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], []))
370
+ width = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
371
+ gmsh.model.getValue(0, pnts[1], []))
270
372
  pnts = gmsh.model.getAdjacencies(1, wedge.lines['o'])[1]
271
- outer_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []), gmsh.model.getValue(0, pnts[1], []))
373
+ outer_height = Func.points_distance(gmsh.model.getValue(0, pnts[0], []),
374
+ gmsh.model.getValue(0, pnts[1], []))
272
375
  for line_key, line in wedge.lines.items():
273
376
  if mesh.model_dump().get('isothermal_wedges', False):
274
377
  elements = 1
275
378
  elif 'i' in line_key:
276
- elements = max(1, round(inner_height / mesh.wedges.transfinite.curve_target_size_height))
379
+ elements = max(1, round(
380
+ inner_height / mesh.wedges.transfinite.curve_target_size_height))
277
381
  elif 'o' in line_key:
278
- elements = max(1, round((inner_height if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']
279
- else outer_height) / mesh.wedges.transfinite.curve_target_size_height))
382
+ elements = max(1, round((inner_height if mesh.wedges.transfinite.enabled_for in [
383
+ 'curves', 'curves_and_surfaces']
384
+ else outer_height) / mesh.wedges.transfinite.curve_target_size_height))
280
385
  elif any([i in line_key for i in ['l', 'h']]):
281
386
  elements = max(1, round(width / mesh.wedges.transfinite.curve_target_size_width))
282
387
  if mesh.wedges.transfinite.enabled_for in ['curves', 'curves_and_surfaces']:
283
388
  self.mesh.setTransfiniteCurve(line, elements)
284
- if mesh.wedges.transfinite.enabled_for=='curves_and_surfaces' or mesh.model_dump().get('isothermal_wedges', False):
389
+ if mesh.wedges.transfinite.enabled_for == 'curves_and_surfaces' or mesh.model_dump().get(
390
+ 'isothermal_wedges', False):
285
391
  self.mesh.setTransfiniteSurface(list(wedge.areas.values())[0].surface)
286
392
  self.mesh.setRecombine(2, list(wedge.areas.values())[0].surface)
287
393
 
@@ -291,31 +397,45 @@ class Mesh:
291
397
  """
292
398
  offset: int = 1 if 'symmetry' in geometry.model_dump() else int(1e6)
293
399
  pg_tag = offset
400
+ point_offset = 4000000
401
+ point_tag = offset
294
402
 
295
403
  # Create physical groups of iron yoke regions and block insulation
296
404
  pg = self.md.domains.physical_groups
297
- for group_name, surfaces in self.md.domains.groups_entities.iron.items():
298
- pg.iron.surfaces[group_name] = gmsh.model.addPhysicalGroup(2, surfaces, pg_tag)
299
- gmsh.model.setPhysicalName(2, pg.iron.surfaces[group_name], group_name)
300
- color = self.colors[group_name]
301
- gmsh.model.setColor([(2, i) for i in surfaces], color[0], color[1], color[2])
302
- pg_tag += 1
405
+ ge = self.md.domains.groups_entities
406
+
407
+ # Create the physical groups of iron + set color
408
+ group_keys = geometry.areas + ['ref_mesh']
409
+ for key in group_keys:
410
+ group_surfaces = getattr(ge, key)
411
+ pg_surfaces = getattr(pg, key).surfaces
412
+ for group_name, surfaces in group_surfaces.items():
413
+ pg_surfaces[group_name] = gmsh.model.addPhysicalGroup(2, surfaces, pg_tag)
414
+ gmsh.model.setPhysicalName(2, pg_surfaces[group_name], group_name)
415
+ if group_name not in self.colors:
416
+ logger.warning(f"Color for group '{group_name}' not defined, using default color [0, 0, 0].")
417
+ color = self.colors.get(group_name, [0, 0, 0])
418
+ gmsh.model.setColor([(2, i) for i in surfaces], *color)
419
+ pg_tag += 1
303
420
 
304
- # Create the physical group of air infinite
421
+ # Create the physical group of air infinite + set color of entities.air
305
422
  if 'symmetry' in geometry.model_dump():
306
423
  gmsh.model.setPhysicalName(0, gmsh.model.addPhysicalGroup(
307
- 0, [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore_field' in pnt_name], pg_tag), 'bore_centers')
424
+ 0, [pnt for pnt_name, pnt in self.md.geometries.air.points.items() if 'bore_field' in pnt_name],
425
+ pg_tag), 'bore_centers')
308
426
  pg_tag += 1
309
427
  pg.air_inf_bnd = gmsh.model.addPhysicalGroup(1, [self.md.geometries.air_inf.lines['outer']], pg_tag)
310
428
  gmsh.model.setPhysicalName(1, pg.air_inf_bnd, 'air_inf_bnd')
311
429
  pg_tag += 1
312
430
  pg.air_inf = gmsh.model.addPhysicalGroup(2, [self.md.geometries.air_inf.areas['outer'].surface], pg_tag)
313
431
  gmsh.model.setPhysicalName(2, pg.air_inf, 'air_inf')
314
- gmsh.model.setColor([(2, self.md.geometries.air_inf.areas['outer'].surface)], self.colors['air_inf'][0], self.colors['air_inf'][1], self.colors['air_inf'][2])
432
+ gmsh.model.setColor([(2, self.md.geometries.air_inf.areas['outer'].surface)], self.colors['air_inf'][0],
433
+ self.colors['air_inf'][1], self.colors['air_inf'][2])
315
434
  pg_tag += 1
316
- pg.air = gmsh.model.addPhysicalGroup(2, self.md.domains.groups_entities.air, pg_tag)
435
+ pg.air = gmsh.model.addPhysicalGroup(2, ge.air, pg_tag)
317
436
  gmsh.model.setPhysicalName(2, pg.air, 'air')
318
- gmsh.model.setColor([(2, i) for i in self.md.domains.groups_entities.air], self.colors['air'][0], self.colors['air'][1], self.colors['air'][2])
437
+ gmsh.model.setColor([(2, i) for i in ge.air], self.colors['air'][0], self.colors['air'][1],
438
+ self.colors['air'][2])
319
439
  pg_tag += 1
320
440
 
321
441
  # Create physical groups of half turns
@@ -324,6 +444,7 @@ class Mesh:
324
444
  lyr_list_group.extend(['cl' + str(coil_nr) + 'ly' + str(lyr) for lyr in list(coil.layers.keys())])
325
445
  for layer_nr, layer in coil.layers.items():
326
446
  ht_list_group = []
447
+ ht_nodes_group = []
327
448
  for nr, block_order in enumerate(layer):
328
449
  wnd = self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
329
450
  layer_nr].windings[block_order.winding]
@@ -348,35 +469,64 @@ class Mesh:
348
469
  gmsh.model.setPhysicalName(2, ht_pg.tag, ht_key)
349
470
  gmsh.model.setColor([(2, ht.surface)], color[0], color[1], color[2])
350
471
  pg_tag += 1
472
+
351
473
  # Assign thin-shell group
352
474
  # the check for reversed block coil is not tested well
353
- if geometry.model_dump().get('correct_block_coil_tsa_checkered_scheme', False) and self.md.geometries.coil.coils[coil_nr].type == 'reversed-block-coil':
475
+ if geometry.model_dump().get('correct_block_coil_tsa_checkered_scheme', False) and \
476
+ self.md.geometries.coil.coils[coil_nr].type == 'reversed-block-coil':
354
477
  azimuthal = 'a1' if list(wnd.blocks.keys()).index(block_nr) % 2 == 0 else 'a2'
355
478
  else:
356
- azimuthal = 'a1' if lyr_list_group.index('cl' + str(coil_nr) + 'ly' + str(layer_nr)) % 2 == 0 else 'a2'
479
+ azimuthal = 'a1' if lyr_list_group.index(
480
+ 'cl' + str(coil_nr) + 'ly' + str(layer_nr)) % 2 == 0 else 'a2'
357
481
  radial = 'r1' if ht_list_group.index(ht_key) % 2 == 0 else 'r2'
358
482
  ht_pg.group = radial + '_' + azimuthal
359
- if geometry.model_dump().get('use_TSA', False):
360
- # Create 1D physical groups of thin shells
361
- for ht_line_key, ht_line in block.half_turns.lines.items():
362
- ht_nr = ht_line_key[:-1]
363
- # Create half turn line groups
364
- line_pg = gmsh.model.addPhysicalGroup(1, [ht_line], pg_tag)
365
- gmsh.model.setPhysicalName(1, line_pg, ht_line_key)
366
- color = [0, 0, 0] if blk_pg.half_turns[int(ht_nr)].group[:2] == 'r1' else [150, 150, 150]
367
- gmsh.model.setColor([(1, ht_line)], color[0], color[1], color[2])
368
- pg_tag += 1
369
- # Store thin shell tags
370
- blk_pg.half_turns[int(ht_nr)].lines[ht_line_key[-1]] = line_pg
483
+
484
+ # Create 1D physical groups of thin shells
485
+ for ht_line_key, ht_line in block.half_turns.lines.items():
486
+ ht_nr = ht_line_key[:-1]
487
+ # Create half turn line groups
488
+ line_pg = gmsh.model.addPhysicalGroup(1, [ht_line], pg_tag)
489
+ gmsh.model.setPhysicalName(1, line_pg, ht_line_key)
490
+ color = [0, 0, 0] if blk_pg.half_turns[int(ht_nr)].group[:2] == 'r1' else [150, 150, 150]
491
+ gmsh.model.setColor([(1, ht_line)], color[0], color[1], color[2])
492
+ pg_tag += 1
493
+ # Store thin shell tags
494
+ blk_pg.half_turns[int(ht_nr)].lines[ht_line_key[-1]] = line_pg
495
+
496
+ # Add single_node per half turn for EM mesh
497
+ for ht_key, ht in block.half_turns.areas.items():
498
+ inner_line = \
499
+ gmsh.model.getEntitiesForPhysicalGroup(1, blk_pg.half_turns[int(ht_key)].lines['i'])[0]
500
+ inner_nodes = gmsh.model.getBoundary([(1, inner_line)])
501
+
502
+ higher_line = \
503
+ gmsh.model.getEntitiesForPhysicalGroup(1, blk_pg.half_turns[int(ht_key)].lines['h'])[0]
504
+ higher_nodes = gmsh.model.getBoundary([(1, higher_line)])
505
+
506
+ single_node = list(set(inner_nodes) & set(higher_nodes))[0][1]
507
+ blk_pg.half_turns[int(ht_key)].single_node = gmsh.model.addPhysicalGroup(0, [single_node],
508
+ point_offset + point_tag,
509
+ f"{ht_key}_single_node")
510
+
511
+ point_tag += 1
371
512
 
372
513
  # Create points region for projection
373
514
  if 'use_TSA' in geometry.model_dump():
374
- self.md.domains.physical_groups.half_turns_points = gmsh.model.addPhysicalGroup(
375
- 0, [gmsh.model.getAdjacencies(1, gmsh.model.getAdjacencies(2, ht.surface)[1][0])[1][0]
376
- for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items()
377
- for layer_nr, layer in coil.layers.items() for block_order in layer
378
- for ht_key, ht in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
379
- layer_nr].windings[block_order.winding].blocks[block_order.block].half_turns.areas.items()], int(4e6))
515
+ point_list = [gmsh.model.getAdjacencies(1, gmsh.model.getAdjacencies(2, ht.surface)[1][0])[1][0]
516
+ for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items()
517
+ for layer_nr, layer in coil.layers.items() for block_order in layer
518
+ for ht_key, ht in self.md.geometries.coil.coils[coil_nr].poles[block_order.pole].layers[
519
+ layer_nr].windings[block_order.winding].blocks[
520
+ block_order.block].half_turns.areas.items()]
521
+ ### add one point per wedge
522
+ wdg_points = []
523
+ for coil_nr, coil in self.md.geometries.wedges.coils.items():
524
+ for layer_nr, layer in coil.layers.items():
525
+ for wedge_nr, wedge in layer.wedges.items():
526
+ wdg_points.append(list(gmsh.model.getAdjacencies(2, wedge.areas[str(wedge_nr)].surface)[1])[0])
527
+
528
+ self.md.domains.physical_groups.half_turns_points = gmsh.model.addPhysicalGroup(0, wdg_points + point_list,
529
+ int(4e6))
380
530
  gmsh.model.setPhysicalName(0, self.md.domains.physical_groups.half_turns_points, 'points')
381
531
 
382
532
  # Create physical groups of insulations
@@ -388,13 +538,16 @@ class Mesh:
388
538
  if area_name.isdigit():
389
539
  pg.insulations.surfaces[area_name] = gmsh.model.addPhysicalGroup(2, [area.surface], pg_tag)
390
540
  gmsh.model.setPhysicalName(2, pg.insulations.surfaces[area_name], 'insul_' + area_name)
391
- gmsh.model.setColor([(2, area.surface)], self.colors['insul'][0], self.colors['insul'][1], self.colors['insul'][2])
541
+ gmsh.model.setColor([(2, area.surface)], self.colors['insul'][0], self.colors['insul'][1],
542
+ self.colors['insul'][2])
392
543
  pg_tag += 1
393
-
544
+
394
545
  # Boundaries
395
546
  area_name = list(group.ins.areas.keys())[0] # todo: test for Mono
396
547
  pg.insulations.curves['ext' + area_name] = gmsh.model.addPhysicalGroup(
397
- 1, [bnd[1] for bnd in gmsh.model.getBoundary([(2, list(group.ins.areas.values())[0].surface)], combined=False, oriented=False)[:len(group.ins.lines)]], pg_tag)
548
+ 1, [bnd[1] for bnd in
549
+ gmsh.model.getBoundary([(2, list(group.ins.areas.values())[0].surface)], combined=False,
550
+ oriented=False)[:len(group.ins.lines)]], pg_tag)
398
551
  gmsh.model.setPhysicalName(1, pg.insulations.curves['ext' + area_name], 'insul_ext' + area_name)
399
552
  pg_tag += 1
400
553
  # todo: NOT COMPLETED: would work if the tags were updated in the Geometry script after saving and loading brep
@@ -424,14 +577,13 @@ class Mesh:
424
577
  else:
425
578
  prev_group = prev_block_hts[list(prev_block_hts.keys())[0]].group
426
579
  wedge_pg.group = ('r1' if prev_group[1] == '2' else 'r2') + prev_group[prev_group.index('_'):]
427
- if geometry.model_dump().get('use_TSA', False):
428
- # Create 1D physical groups of thin shells
429
- for line_key, line in wedge.lines.items():
430
- wedge_pg.lines[line_key] = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
431
- gmsh.model.setPhysicalName(1, wedge_pg.lines[line_key], 'w' + str(wedge_nr) + line_key)
432
- color = [0, 0, 0] if wedge_pg.group[:2] == 'r1' else [150, 150, 150]
433
- gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
434
- pg_tag += 1
580
+
581
+ for line_key, line in wedge.lines.items():
582
+ wedge_pg.lines[line_key] = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
583
+ gmsh.model.setPhysicalName(1, wedge_pg.lines[line_key], 'w' + str(wedge_nr) + line_key)
584
+ color = [0, 0, 0] if wedge_pg.group[:2] == 'r1' else [150, 150, 150]
585
+ gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
586
+ pg_tag += 1
435
587
 
436
588
  # Create physical groups of thin shells
437
589
  if geometry.model_dump().get('use_TSA', False):
@@ -441,8 +593,11 @@ class Mesh:
441
593
  for ts_name, ts in gts.mid_layers_ht_to_ht.items():
442
594
  blk_i, blk_o = ts_name[:ts_name.index('_')], ts_name[ts_name.index('_') + 1:]
443
595
  for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
444
- if (any(int(blk_i) == blk_order.block for blk_order in self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1]) and
445
- any(int(blk_o) == blk_order.block for blk_order in self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1])): # block-coil mid-pole case
596
+ if (any(int(blk_i) == blk_order.block for blk_order in
597
+ self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[1]) and
598
+ any(int(blk_o) == blk_order.block for blk_order in
599
+ self.md.geometries.coil.anticlockwise_order.coils[coil_nr].layers[
600
+ 1])): # block-coil mid-pole case
446
601
  block_coil_flag = True
447
602
  for line_name, line in ts.mid_layers.lines.items():
448
603
  line_pg = gmsh.model.addPhysicalGroup(1, [line], pg_tag)
@@ -470,7 +625,8 @@ class Mesh:
470
625
  gmsh.model.setPhysicalName(1, line_pg, line_name)
471
626
  pg_tag += 1
472
627
  pg.wedges[int(wdg[1:])].mid_layer_lines.outer[line_name] = line_pg
473
- ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[line_name.index('_') + 1:]
628
+ ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[
629
+ line_name.index('_') + 1:]
474
630
  pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.inner[line_name] = line_pg
475
631
 
476
632
  # Create physical groups of block-to-wedge mid-layer lines
@@ -481,7 +637,8 @@ class Mesh:
481
637
  gmsh.model.setPhysicalName(1, line_pg, line_name)
482
638
  pg_tag += 1
483
639
  pg.wedges[int(wdg[1:])].mid_layer_lines.inner[line_name] = line_pg
484
- ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[line_name.index('_') + 1:]
640
+ ht = line_name[:line_name.index('_')] if line_name[line_name.index('_') + 1:] == wdg else line_name[
641
+ line_name.index('_') + 1:]
485
642
  pg.blocks[int(blk)].half_turns[int(ht)].mid_layer_lines.outer[line_name] = line_pg
486
643
 
487
644
  # Create physical groups of wedge-to-wedge mid-layer lines
@@ -495,7 +652,8 @@ class Mesh:
495
652
 
496
653
  # Create non-mid-layer thin shells
497
654
  for ts_group_name, ts_group in zip(['mid_pole', 'mid_winding', 'mid_turn', 'mid_turn', 'aux'],
498
- [gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks, gts.mid_wedge_turn, gts.mid_layers_aux]):
655
+ [gts.mid_poles, gts.mid_windings, gts.mid_turn_blocks,
656
+ gts.mid_wedge_turn, gts.mid_layers_aux]):
499
657
  for ts_name, ts in ts_group.items():
500
658
  if '_' in ts_name:
501
659
  el_name1, el_name2 = ts_name.split('_')
@@ -510,11 +668,81 @@ class Mesh:
510
668
  ht1 = int(list(ts.points.keys())[0][:-1])
511
669
  else:
512
670
  ht1, ht2 = line_name.split('_')
513
- pg.blocks[int(el_name2)].half_turns[int(ht2)].__dict__[ts_group_name + '_lines'][line_name] = line_pg
671
+ pg.blocks[int(el_name2)].half_turns[int(ht2)].__dict__[ts_group_name + '_lines'][
672
+ line_name] = line_pg
514
673
  if ts_name[0] == 'w':
515
674
  pg.wedges[int(el_name1)].__dict__[ts_group_name + '_lines'][line_name] = line_pg
516
675
  else:
517
- pg.blocks[int(el_name1)].half_turns[int(ht1)].__dict__[ts_group_name + '_lines'][line_name] = line_pg
676
+ pg.blocks[int(el_name1)].half_turns[int(ht1)].__dict__[ts_group_name + '_lines'][
677
+ line_name] = line_pg
678
+
679
+ # Create physical groups for TSL
680
+ # add the physical group inner collar lines, only nonempty for the thermal solution with collar
681
+ lines = []
682
+ for quad, tags in self.md.geometries.collar.outer_boundary_tags.items():
683
+ if 'poles' in self.data.magnet.geometry.thermal.areas:
684
+ lines.extend(tags[:-1]) # skip the last line, this is the line between the pole and collar
685
+ else:
686
+ lines.extend(tags)
687
+ gmsh.model.occ.synchronize()
688
+ tag = gmsh.model.addPhysicalGroup(1, lines)
689
+ gmsh.model.setPhysicalName(1, tag, "outer_col")
690
+ pg.outer_col = tag
691
+ gmsh.model.occ.synchronize()
692
+
693
+ if geometry.model_dump().get('use_TSA_new', False):
694
+ # line names are defined earlier when creating the geometry (so wedge and coil names are already separated)
695
+ # f'{ht_nr}_x' for ht to mid and f'w{wedge_idx}_x' for wdg to mid
696
+ # poles look like f'p{pole_idx}_x'
697
+ layer = self.md.geometries.thin_shells.collar_layers
698
+ for name, l in layer.items():
699
+ line = l.lines['1'] # default line name, contains only one line
700
+ line_pg = gmsh.model.addPhysicalGroup(1, [line])
701
+ gmsh.model.setPhysicalName(1, line_pg, name)
702
+ color = [0, 0, 0]
703
+ gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
704
+ # self.ins_type_col['mid_lines'][name] = line_pg
705
+ self.ins_type['collar'][name] = line_pg
706
+
707
+ # only apply to TSA
708
+ # add the physical group inner collar lines
709
+ lines = []
710
+ for quad, tags in self.md.geometries.collar.inner_boundary_tags.items():
711
+ lines.extend(tags)
712
+ gmsh.model.occ.synchronize()
713
+ tag = gmsh.model.addPhysicalGroup(1, lines)
714
+ gmsh.model.setPhysicalName(1, tag, "inner_col")
715
+ pg.inner_col = tag
716
+ gmsh.model.occ.synchronize()
717
+
718
+ # do the same for the poles, if they are defined
719
+ if 'poles' in self.data.magnet.geometry.thermal.areas:
720
+ layer = self.md.geometries.thin_shells.pole_layers
721
+ for name, l in layer.items():
722
+ line = l.lines['1'] # default line name, contains only one line
723
+ line_pg = gmsh.model.addPhysicalGroup(1, [line])
724
+ gmsh.model.setPhysicalName(1, line_pg, name)
725
+ color = [0, 0, 0]
726
+ gmsh.model.setColor([(1, line)], color[0], color[1], color[2])
727
+ self.ins_type['poles'][name] = line_pg
728
+
729
+ lines = []
730
+ for quad, tags in self.brep_curves['poles'].items():
731
+ lines.extend(tags)
732
+ # maybe only select the lines relevant for TSA?
733
+ gmsh.model.occ.synchronize()
734
+ tag = gmsh.model.addPhysicalGroup(1, lines)
735
+ gmsh.model.setPhysicalName(1, tag, "pole_boundary")
736
+ pg.poles.curves["bdry"] = tag
737
+ gmsh.model.occ.synchronize()
738
+
739
+ # Create physical group of collar cooling boudnary
740
+ if self.data.magnet.solve.thermal.collar_cooling.enabled and 'use_TSA' in geometry.model_dump():
741
+ ## make physical group for collar cooling
742
+ tag = gmsh.model.addPhysicalGroup(1, self.md.geometries.collar.cooling_tags)
743
+ gmsh.model.setPhysicalName(1, tag, "collar cooling")
744
+ pg.collar_cooling = tag
745
+ gmsh.model.occ.synchronize()
518
746
 
519
747
  # Create physical groups of symmetric boundaries
520
748
  if geometry.model_dump().get('symmetry', 'none') != 'none':
@@ -524,8 +752,8 @@ class Mesh:
524
752
  line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.y
525
753
  line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x
526
754
  elif len(self.md.geometries.coil.coils[1].poles) == 4: # todo: think about other multi-pole types
527
- line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x +\
528
- self.md.domains.groups_entities.symmetric_boundaries.y
755
+ line_tags_tangent_free = self.md.domains.groups_entities.symmetric_boundaries.x + \
756
+ self.md.domains.groups_entities.symmetric_boundaries.y
529
757
  elif geometry.symmetry == 'x':
530
758
  if 'solenoid' in self.md.geometries.coil.coils[1].type:
531
759
  line_tags_normal_free = self.md.domains.groups_entities.symmetric_boundaries.x
@@ -551,18 +779,21 @@ class Mesh:
551
779
  qh = self.data.quench_protection.quench_heaters
552
780
 
553
781
  def _store_qh_data(position, thin_shell, ts_tag):
554
- qh_ins = self.ins_type_qh[position][qh.ids[ht_index]]
782
+ qh_ins = self.ins_type_qh[position][qh.iQH_toHalfTurn_From[ht_index]]
555
783
  if thin_shell not in qh_ins: qh_ins[thin_shell] = []
556
784
  qh_ins[thin_shell].append(ts_tag)
557
- if thin_shell not in self.qh_data[qh.ids[ht_index]]:
558
- self.qh_data[qh.ids[ht_index]][thin_shell] = {'conductor': blk_pg.conductor, 'ht_side': qh_side}
785
+ if thin_shell not in self.qh_data[qh.iQH_toHalfTurn_From[ht_index]]:
786
+ self.qh_data[qh.iQH_toHalfTurn_From[ht_index]][thin_shell] = {'conductor': blk_pg.conductor,
787
+ 'ht_side': qh_side}
559
788
 
560
789
  def _store_ts_tags(pg_el, geom_ts_name='', geom_ts_name2=None, ts_grp='', lines='', lines_side=None):
561
790
  geom_ts = self.md.geometries.thin_shells.model_dump()[geom_ts_name]
562
- for ln_name, ln_tag in (pg_el.model_dump()[lines][lines_side] if lines_side else pg_el.model_dump()[lines]).items():
791
+ for ln_name, ln_tag in (
792
+ pg_el.model_dump()[lines][lines_side] if lines_side else pg_el.model_dump()[lines]).items():
563
793
  for ts_name in ts_groups[ts_grp]:
564
794
  if ts_name in geom_ts:
565
- if ln_name in (geom_ts[ts_name][geom_ts_name2]['lines'] if geom_ts_name2 else geom_ts[ts_name]['lines']):
795
+ if ln_name in (
796
+ geom_ts[ts_name][geom_ts_name2]['lines'] if geom_ts_name2 else geom_ts[ts_name]['lines']):
566
797
  if ts_name not in self.ins_type[ts_grp]: self.ins_type[ts_grp][ts_name] = []
567
798
  self.ins_type[ts_grp][ts_name].append(ln_tag)
568
799
  break
@@ -570,21 +801,30 @@ class Mesh:
570
801
  # Collect thin shell tags
571
802
  # Half turns
572
803
  for blk_pg_nr, blk_pg in pg.blocks.items():
573
- ts_groups = {'mid_wedge_turn': [ts_name for ts_name in self.md.geometries.thin_shells.mid_wedge_turn if blk_pg_nr == int(ts_name.split('_')[1])],
804
+ ts_groups = {'mid_wedge_turn': [ts_name for ts_name in self.md.geometries.thin_shells.mid_wedge_turn if
805
+ blk_pg_nr == int(ts_name.split('_')[1])],
574
806
  'aux': [ts_name for ts_name in ins_th.mid_layer if str(blk_pg_nr) in ts_name.split('_')],
575
- 'mid_winding': [ts_name for ts_name in ins_th.mid_winding if blk_pg_nr == int(ts_name.split('_')[0])],
576
- 'mid_pole': [ts_name for ts_name in ins_th.mid_pole if blk_pg_nr == int(ts_name.split('_')[0])],
577
- 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if ts_name[0] != 'w' and blk_pg_nr == int(ts_name.split('_')[0])
578
- or ts_name[0] == 'w' and ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(ts_name.split('_')[1])],
579
- 'mid_layer_qh_i': [ts_name for ts_name in ins_th.mid_layer if ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(ts_name.split('_')[1])]}
807
+ 'mid_winding': [ts_name for ts_name in ins_th.mid_winding if
808
+ blk_pg_nr == int(ts_name.split('_')[0])],
809
+ 'mid_pole': [ts_name for ts_name in ins_th.mid_pole if
810
+ blk_pg_nr == int(ts_name.split('_')[0])],
811
+ 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if
812
+ ts_name[0] != 'w' and blk_pg_nr == int(ts_name.split('_')[0])
813
+ or ts_name[0] == 'w' and ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(
814
+ ts_name.split('_')[1])],
815
+ 'mid_layer_qh_i': [ts_name for ts_name in ins_th.mid_layer if
816
+ ts_name.split('_')[1][0] != 'w' and blk_pg_nr == int(
817
+ ts_name.split('_')[1])]}
580
818
  ht_list = list(blk_pg.half_turns.keys())
581
819
  self.ins_type_cond[str(blk_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [], 'mid_turn': []}
582
820
  for ht_nr, ht in blk_pg.half_turns.items():
583
- if ht_nr in qh.turns and qh.N_strips > 0: # check if a quench heater strip touches the current half-turn
584
- ht_index = qh.turns.index(ht_nr)
821
+ if ht_nr in qh.iQH_toHalfTurn_To and qh.N_strips > 0: # check if a quench heater strip touches the current half-turn
822
+ ht_index = qh.iQH_toHalfTurn_To.index(ht_nr)
585
823
  qh_side = qh.turns_sides[ht_index]
586
- if qh.ids[ht_index] not in self.qh_data: self.qh_data[qh.ids[ht_index]] = {}
587
- else: qh_side = ''
824
+ if qh.iQH_toHalfTurn_From[ht_index] not in self.qh_data: self.qh_data[
825
+ qh.iQH_toHalfTurn_From[ht_index]] = {}
826
+ else:
827
+ qh_side = ''
588
828
 
589
829
  # find conductor type of ht adjacent to wedge
590
830
  if ht_list.index(ht_nr) == len(ht_list) - 1 and ht.mid_turn_lines:
@@ -594,7 +834,8 @@ class Mesh:
594
834
  self.wedge_cond[int(ts_name[1:ts_name.index('_')])] = blk_pg.conductor
595
835
  break
596
836
 
597
- self.ins_type_cond[str(blk_pg_nr)]['mid_turn'].extend(list(ht.mid_turn_lines.values())) # mid-turn insulation
837
+ self.ins_type_cond[str(blk_pg_nr)]['mid_turn'].extend(
838
+ list(ht.mid_turn_lines.values())) # mid-turn insulation
598
839
 
599
840
  if ht.aux_lines: # outer mid-layer insulation
600
841
  _store_ts_tags(ht, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines')
@@ -606,12 +847,14 @@ class Mesh:
606
847
  ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
607
848
  elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_ht:
608
849
  ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines
609
- else: ts_lines = []
850
+ else:
851
+ ts_lines = []
610
852
  if line_name in ts_lines:
611
853
  _store_qh_data('internal', ts_name, line_tag)
612
854
  break
613
855
  elif ht.mid_layer_lines.inner: # block-coil (!) inner mid-layer insulation
614
- _store_ts_tags(ht, geom_ts_name='mid_layers_ht_to_ht', geom_ts_name2='mid_layers', ts_grp='mid_layer', lines='mid_layer_lines', lines_side='inner')
856
+ _store_ts_tags(ht, geom_ts_name='mid_layers_ht_to_ht', geom_ts_name2='mid_layers',
857
+ ts_grp='mid_layer', lines='mid_layer_lines', lines_side='inner')
615
858
  elif not ht.mid_layer_lines.inner and qh_side == 'i': # quench heater inner insulation
616
859
  _store_qh_data('external', blk_pg_nr, ht.lines['i'])
617
860
  elif not ht.mid_layer_lines.inner: # inner insulation
@@ -624,7 +867,8 @@ class Mesh:
624
867
  ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
625
868
  elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg:
626
869
  ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines
627
- else: ts_lines = []
870
+ else:
871
+ ts_lines = []
628
872
  if line_name in ts_lines:
629
873
  _store_qh_data('internal', ts_name, line_tag)
630
874
  break
@@ -635,7 +879,8 @@ class Mesh:
635
879
  ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_ht[ts_name].mid_layers.lines
636
880
  elif ts_name in self.md.geometries.thin_shells.mid_layers_ht_to_wdg:
637
881
  ts_lines = self.md.geometries.thin_shells.mid_layers_ht_to_wdg[ts_name].lines
638
- else: ts_lines = []
882
+ else:
883
+ ts_lines = []
639
884
  if line_name in ts_lines:
640
885
  if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = []
641
886
  self.ins_type['mid_layer'][ts_name].append(line_tag)
@@ -651,20 +896,29 @@ class Mesh:
651
896
  # mid-winding insulation
652
897
  _store_ts_tags(ht, geom_ts_name='mid_windings', ts_grp='mid_winding', lines='mid_winding_lines')
653
898
 
654
- if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # lower angle external side insulation
655
- if qh_side == 'l': _store_qh_data('external', blk_pg_nr, ht.lines['l'])
656
- else: self.ins_type_cond[str(blk_pg_nr)]['lower'].append(ht.lines['l'])
657
- if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # higher angle external side insulation
658
- if qh_side == 'h': _store_qh_data('external', blk_pg_nr, ht.lines['h'])
659
- else: self.ins_type_cond[str(blk_pg_nr)]['higher'].append(ht.lines['h'])
899
+ if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(
900
+ ht.mid_pole_lines) == 1: # lower angle external side insulation
901
+ if qh_side == 'l':
902
+ _store_qh_data('external', blk_pg_nr, ht.lines['l'])
903
+ else:
904
+ self.ins_type_cond[str(blk_pg_nr)]['lower'].append(ht.lines['l'])
905
+ if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(
906
+ ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1: # higher angle external side insulation
907
+ if qh_side == 'h':
908
+ _store_qh_data('external', blk_pg_nr, ht.lines['h'])
909
+ else:
910
+ self.ins_type_cond[str(blk_pg_nr)]['higher'].append(ht.lines['h'])
660
911
 
661
912
  # Wedges
662
913
  for wdg_pg_nr, wdg_pg in pg.wedges.items():
663
914
  ts_groups = {'aux': [ts_name for ts_name in ins_th.mid_layer if 'w' + str(wdg_pg_nr) in ts_name.split('_')],
664
- 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if 'w' + str(wdg_pg_nr) == ts_name.split('_')[0]]}
665
- self.ins_type_cond['w' + str(wdg_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [], 'mid_turn': []}
915
+ 'mid_layer': [ts_name for ts_name in ins_th.mid_layer if
916
+ 'w' + str(wdg_pg_nr) == ts_name.split('_')[0]]}
917
+ self.ins_type_cond['w' + str(wdg_pg_nr)] = {'inner': [], 'outer': [], 'higher': [], 'lower': [],
918
+ 'mid_turn': []}
666
919
  if wdg_pg.aux_lines: _store_ts_tags(wdg_pg, geom_ts_name='mid_layers_aux', ts_grp='aux', lines='aux_lines')
667
- if not wdg_pg.mid_layer_lines.inner: self.ins_type_cond['w' + str(wdg_pg_nr)]['inner'].append(wdg_pg.lines['i'])
920
+ if not wdg_pg.mid_layer_lines.inner: self.ins_type_cond['w' + str(wdg_pg_nr)]['inner'].append(
921
+ wdg_pg.lines['i'])
668
922
  if wdg_pg.mid_layer_lines.outer:
669
923
  for line_name, line_tag in wdg_pg.mid_layer_lines.outer.items():
670
924
  for ts_name in ts_groups['mid_layer']:
@@ -672,12 +926,14 @@ class Mesh:
672
926
  ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_ht[ts_name].lines
673
927
  elif ts_name in self.md.geometries.thin_shells.mid_layers_wdg_to_wdg:
674
928
  ts_lines = self.md.geometries.thin_shells.mid_layers_wdg_to_wdg[ts_name].lines
675
- else: ts_lines = []
929
+ else:
930
+ ts_lines = []
676
931
  if line_name in ts_lines:
677
932
  if ts_name not in self.ins_type['mid_layer']: self.ins_type['mid_layer'][ts_name] = []
678
933
  self.ins_type['mid_layer'][ts_name].append(line_tag)
679
934
  break
680
- else: self.ins_type_cond['w' + str(wdg_pg_nr)]['outer'].append(wdg_pg.lines['o'])
935
+ else:
936
+ self.ins_type_cond['w' + str(wdg_pg_nr)]['outer'].append(wdg_pg.lines['o'])
681
937
 
682
938
  # Collect common thin shells for double qh mid-layers
683
939
  for qh_nr, ts_groups in self.ins_type_qh['internal'].items():
@@ -689,22 +945,28 @@ class Mesh:
689
945
  common_tags = list(set(tags) & set(tags2))
690
946
  for tag in common_tags:
691
947
  tags.remove(tag), tags2.remove(tag)
692
- if self.qh_data[qh_nr2][ts]['ht_side'] == 'i': qh_name = str(qh_nr) + '_' + str(qh_nr2)
693
- else: qh_name = str(qh_nr2) + '_' + str(qh_nr)
694
- if qh_name not in self.ins_type_qh['internal_double']: self.ins_type_qh['internal_double'][qh_name] = {}
948
+ if self.qh_data[qh_nr2][ts]['ht_side'] == 'i':
949
+ qh_name = str(qh_nr) + '_' + str(qh_nr2)
950
+ else:
951
+ qh_name = str(qh_nr2) + '_' + str(qh_nr)
952
+ if qh_name not in self.ins_type_qh['internal_double']: self.ins_type_qh['internal_double'][
953
+ qh_name] = {}
695
954
  qh_ins_id = self.ins_type_qh['internal_double'][qh_name]
696
955
  if ts not in qh_ins_id: qh_ins_id[ts] = []
697
956
  qh_ins_id[ts].append(tag)
698
957
 
699
958
  def assignRegionsTags(self, geometry, mesh):
700
959
  def _get_input_insulation_data(i_name, i_type=None):
701
- ow_idx = next((index for index, couple in enumerate(self.data.magnet.solve.thermal.insulation_TSA.block_to_block.blocks_connection_overwrite)
960
+ ow_idx = next((index for index, couple in enumerate(
961
+ self.data.magnet.solve.thermal.insulation_TSA.block_to_block.blocks_connection_overwrite)
702
962
  if all(element in couple for element in i_name.split('_'))), None)
703
- if i_type == 'mid_winding': mid_mat, mid_th = [self.data.magnet.solve.wedges.material], []
963
+ if i_type == 'mid_winding':
964
+ mid_mat, mid_th = [self.data.magnet.solve.wedges.material], []
704
965
  elif ow_idx is not None:
705
966
  mid_mat = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.materials_overwrite[ow_idx]
706
967
  mid_th = self.data.magnet.solve.thermal.insulation_TSA.block_to_block.thicknesses_overwrite[ow_idx]
707
- else: mid_mat, mid_th = [self.data.magnet.solve.thermal.insulation_TSA.block_to_block.material], []
968
+ else:
969
+ mid_mat, mid_th = [self.data.magnet.solve.thermal.insulation_TSA.block_to_block.material], []
708
970
  return mid_mat, mid_th
709
971
 
710
972
  def _compute_insulation_thicknesses(tot_th, known_ins_th):
@@ -712,12 +974,17 @@ class Mesh:
712
974
  mid_lyrs = [Func.sig_dig(tot_th - known_ins_th) / len(mid_materials)] * len(mid_materials)
713
975
  elif None in mid_thicknesses:
714
976
  input_ths = sum([th for th in mid_thicknesses if th is not None])
715
- mid_lyrs = [th if th is not None else Func.sig_dig(tot_th - known_ins_th - input_ths) / mid_thicknesses.count(None) for th in mid_thicknesses]
716
- else: mid_lyrs = mid_thicknesses
977
+ mid_lyrs = [
978
+ th if th is not None else Func.sig_dig(tot_th - known_ins_th - input_ths) / mid_thicknesses.count(
979
+ None) for th in mid_thicknesses]
980
+ else:
981
+ mid_lyrs = mid_thicknesses
717
982
  zeros = [nbr for nbr, th in enumerate(mid_lyrs) if th < 1e-8]
718
983
  if tot_th - known_ins_th - sum(mid_lyrs) < -1e-8:
719
- raise ValueError("Layer-to-layer insulation exceeds the space between blocks: check 'solve'->'insulation_TSA'->'block_to_block'->'thicknesses_overwrite'")
720
- else: return mid_lyrs, zeros
984
+ raise ValueError(
985
+ "Layer-to-layer insulation exceeds the space between blocks: check 'solve'->'insulation_TSA'->'block_to_block'->'thicknesses_overwrite'")
986
+ else:
987
+ return mid_lyrs, zeros
721
988
 
722
989
  pg = self.md.domains.physical_groups
723
990
  qh = self.data.quench_protection.quench_heaters
@@ -725,9 +992,9 @@ class Mesh:
725
992
  # Air and air far field
726
993
  if 'bore_field' in mesh.model_dump():
727
994
  self.rm.air_far_field.vol.radius_out = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies(
728
- 1, self.md.geometries.air_inf.lines['outer'])[1][0], []), key=abs)))
995
+ 1, self.md.geometries.air_inf.lines['outer'])[1][0], []), key=abs)))
729
996
  self.rm.air_far_field.vol.radius_in = float(abs(max(gmsh.model.getValue(0, gmsh.model.getAdjacencies(
730
- 1, self.md.geometries.air_inf.lines['inner'])[1][0], []), key=abs)))
997
+ 1, self.md.geometries.air_inf.lines['inner'])[1][0], []), key=abs)))
731
998
  self.rm.air.vol.name = "Air"
732
999
  self.rm.air.vol.number = pg.air
733
1000
  self.rm.air_far_field.vol.names = ["AirInf"]
@@ -757,31 +1024,46 @@ class Mesh:
757
1024
  if 'bore_field' in mesh.model_dump():
758
1025
  initial_current = self.data.power_supply.I_initial
759
1026
  self.rm.powered[group].vol.currents = []
1027
+ self.rm.powered[group].curve.names = []
1028
+ self.rm.powered[group].curve.numbers = []
1029
+ self.rm.powered[group].surf_in.names = []
1030
+ self.rm.powered[group].surf_in.numbers = []
1031
+ if geometry.with_wedges:
1032
+ self.rm.induced[group].surf_in.names = []
1033
+ self.rm.induced[group].surf_in.numbers = []
760
1034
  if geometry.model_dump().get('use_TSA', False):
761
- self.rm.powered[group].surf_in.names = []
762
- self.rm.powered[group].surf_in.numbers = []
763
1035
  self.rm.powered[group].surf_out.names = []
764
1036
  self.rm.powered[group].surf_out.numbers = []
765
1037
  if geometry.with_wedges:
766
- self.rm.induced[group].surf_in.names = []
767
- self.rm.induced[group].surf_in.numbers = []
768
1038
  self.rm.induced[group].surf_out.names = []
769
1039
  self.rm.induced[group].surf_out.numbers = []
770
- if geometry.with_iron_yoke:
771
- self.rm.iron_yoke.vol.names = []
772
- self.rm.iron_yoke.vol.numbers = []
1040
+
1041
+ for attr in geometry.areas:
1042
+ h = getattr(self.rm, attr).vol
1043
+ h.names = []
1044
+ h.numbers = []
1045
+
1046
+ # initialise reference
1047
+ if self.data.magnet.mesh.thermal.reference.enabled:
1048
+ self.rm.ref_mesh.vol.names = []
1049
+ self.rm.ref_mesh.vol.numbers = []
1050
+
1051
+ self.rm.thin_shells.normals_directed['azimuthally'] = []
1052
+ self.rm.thin_shells.normals_directed['radially'] = []
1053
+
773
1054
  if geometry.model_dump().get('use_TSA', False):
774
1055
  unique_thin_shells = []
775
1056
  self.rm.thin_shells.second_group_is_next['azimuthally'] = []
776
1057
  self.rm.thin_shells.second_group_is_next['radially'] = []
777
- self.rm.thin_shells.normals_directed['azimuthally'] = []
778
- self.rm.thin_shells.normals_directed['radially'] = []
779
1058
  else:
780
1059
  self.rm.insulator.vol.names = []
781
1060
  self.rm.insulator.vol.numbers = []
782
1061
  self.rm.insulator.surf.names = []
783
1062
  self.rm.insulator.surf.numbers = []
784
1063
 
1064
+ # always
1065
+ if 'collar' in geometry.areas:
1066
+ self.rm.thin_shells.bdry_curves['outer_collar'] = [pg.outer_col]
785
1067
  if geometry.model_dump().get('use_TSA', False):
786
1068
  # Categorize insulation types
787
1069
  min_h = mesh.insulation.global_size
@@ -790,36 +1072,80 @@ class Mesh:
790
1072
  # min_h = min([self.data.conductors[conductor].cable.th_insulation_along_height,
791
1073
  # self.data.conductors[conductor].cable.th_insulation_along_width, min_h])
792
1074
  min_h_QH = mesh.insulation.TSA.global_size_QH if mesh.insulation.TSA.global_size_QH else min_h
1075
+ min_h_COL = mesh.insulation.TSA.global_size_COL if mesh.insulation.TSA.global_size_QH else min_h
793
1076
 
794
1077
  # Conductor insulation layers
1078
+ max_layer = len(
1079
+ [k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()]) # todo: more elegant way ?
795
1080
  for el, ins in self.ins_type_cond.items():
796
- cond = self.data.conductors[self.wedge_cond[int(el[1:])]].cable if 'w' in el else self.data.conductors[pg.blocks[int(el)].conductor].cable
1081
+ cond = self.data.conductors[self.wedge_cond[int(el[1:])]].cable if 'w' in el else self.data.conductors[
1082
+ pg.blocks[int(el)].conductor].cable
797
1083
  for ins_side, tags in ins.items():
798
1084
  if tags:
799
1085
  side_ins_type = [cond.material_insulation]
800
- if ins_side in ['inner', 'outer']: side_ins = [cond.th_insulation_along_width]
801
- elif ins_side in ['higher', 'lower']: side_ins = [cond.th_insulation_along_height]
1086
+ if ins_side in ['inner', 'outer']:
1087
+ side_ins = [cond.th_insulation_along_width]
1088
+ elif ins_side in ['higher', 'lower']:
1089
+ side_ins = [cond.th_insulation_along_height]
802
1090
  else: # mid_turn
803
1091
  side_ins = [cond.th_insulation_along_height, cond.th_insulation_along_height]
804
1092
  side_ins_type.append(cond.material_insulation)
805
- if ins_side[0] in 'iohl' and el + ins_side[0] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
806
- add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(el + ins_side[0])
807
- side_ins.extend(self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx])
808
- side_ins_type.extend(self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx])
1093
+ if ins_side[0] in 'iohl' and el + ins_side[
1094
+ 0] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
1095
+ add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(
1096
+ el + ins_side[0])
1097
+ side_ins.extend(
1098
+ self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx])
1099
+ side_ins_type.extend(
1100
+ self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx])
809
1101
  if ins_side[0] in 'il': side_ins.reverse(), side_ins_type.reverse()
810
1102
  self.rm.thin_shells.insulation_types.layers_number.append(0)
811
1103
  self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags)))
812
1104
  self.rm.thin_shells.insulation_types.thicknesses.append([])
813
1105
  self.rm.thin_shells.insulation_types.layers_material.append([])
1106
+ if ins_side == 'outer': # scale thin shell lines linked to the collar
1107
+ if el[0] == 'w': # no wedge correction
1108
+ DUMMY = 1.0
1109
+ self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1110
+ else:
1111
+ bare_to_ins = (self.data.conductors[
1112
+ pg.blocks[int(el)].conductor].cable.bare_cable_height_high + 2 *
1113
+ self.data.conductors[
1114
+ pg.blocks[int(el)].conductor].cable.th_insulation_along_height) / \
1115
+ self.data.conductors[
1116
+ pg.blocks[int(el)].conductor].cable.bare_cable_height_high
1117
+ for key, ht in pg.blocks[int(el)].half_turns.items(): # try only first key
1118
+ if str(ht.group[-1]) == str(max_layer): # ensure outer layer !
1119
+ DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1120
+ if DUMMY < 0.0: # default value
1121
+ DUMMY = bare_to_ins
1122
+ else: # inner layer
1123
+ # print(ht.lines['o']) #-> without the break this prints the linetags of the desired lines
1124
+ # we are within the ins_side = "outer" loop so
1125
+ DUMMY = bare_to_ins
1126
+ self.rm.thin_shells.insulation_types.correction_factors.append(
1127
+ DUMMY) # default value if not outer layer
1128
+ break # break after first key
1129
+ elif ins_side[0] in 'hl': # hl, long ht side adjacent to the poles
1130
+ DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_azimuthal)
1131
+ if DUMMY < 0.0: # default value
1132
+ DUMMY = 1.0
1133
+ self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1134
+ else:
1135
+ self.rm.thin_shells.insulation_types.correction_factors.append(1.0) # default value
814
1136
  for nr, ins_lyr in enumerate(side_ins):
815
1137
  tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h))
816
1138
  self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers
817
- self.rm.thin_shells.insulation_types.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
818
- self.rm.thin_shells.insulation_types.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1139
+ self.rm.thin_shells.insulation_types.thicknesses[-1].extend(
1140
+ [ins_lyr / tsa_layers] * tsa_layers)
1141
+ self.rm.thin_shells.insulation_types.layers_material[-1].extend(
1142
+ [side_ins_type[nr]] * tsa_layers)
819
1143
 
820
1144
  # Mid-pole, mid-winding, and mid-layer insulation layers
821
1145
  ins_th_dict = self.md.geometries.thin_shells.ins_thickness.model_dump()
822
1146
  for ins_type, ins in self.ins_type.items():
1147
+ if ins_type in ['collar', 'poles']:
1148
+ continue # these are added later
823
1149
  for ins_name, tags in ins.items():
824
1150
  # Get conductors insulation
825
1151
  if ins_name.count('w') == 2:
@@ -841,7 +1167,9 @@ class Mesh:
841
1167
  cond_ins1, cond_ins2 = cond1.th_insulation_along_height, cond2.th_insulation_along_height
842
1168
  # Get insulation layer thickness
843
1169
  mid_materials, mid_thicknesses = _get_input_insulation_data(ins_name, i_type=ins_type)
844
- ins_thickness = ins_th_dict['mid_layer'][ins_name] / 2 if ins_type == 'aux' else ins_th_dict[ins_type][ins_name]
1170
+ # for aux: 1/2 due to triangular insulation shape, 1/2 because the triangle height is half the radial distance between the points of the ht insulation
1171
+ ins_thickness = 1 / 2 * ins_th_dict['mid_layer'][ins_name] / 2 if ins_type == 'aux' else \
1172
+ ins_th_dict[ins_type][ins_name]
845
1173
  mid_lyr_th, null_idx = _compute_insulation_thicknesses(ins_thickness, cond_ins1 + cond_ins2)
846
1174
  for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
847
1175
  side_ins = [cond_ins1] + mid_lyr_th + [cond_ins2]
@@ -852,11 +1180,14 @@ class Mesh:
852
1180
  self.rm.thin_shells.insulation_types.thin_shells.append(list(set(tags)))
853
1181
  self.rm.thin_shells.insulation_types.thicknesses.append([])
854
1182
  self.rm.thin_shells.insulation_types.layers_material.append([])
1183
+ self.rm.thin_shells.insulation_types.correction_factors.append(1.0) # default value
1184
+
855
1185
  for nr, ins_lyr in enumerate(side_ins):
856
1186
  tsa_layers = max(mesh.insulation.TSA.minimum_discretizations, round(ins_lyr / min_h))
857
1187
  self.rm.thin_shells.insulation_types.layers_number[-1] += tsa_layers
858
1188
  self.rm.thin_shells.insulation_types.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
859
- self.rm.thin_shells.insulation_types.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1189
+ self.rm.thin_shells.insulation_types.layers_material[-1].extend(
1190
+ [side_ins_type[nr]] * tsa_layers)
860
1191
 
861
1192
  # Quench heater insulation layers
862
1193
  for ins_type, ins in self.ins_type_qh.items():
@@ -866,30 +1197,54 @@ class Mesh:
866
1197
  if tags:
867
1198
  if ins_type == 'external':
868
1199
  data = self.qh_data[qh_nr]
869
- if str(ts_name) + data[ts_name]['ht_side'] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
870
- add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(str(ts_name) + data[ts_name]['ht_side'])
871
- additional_ths = self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[add_mat_idx]
872
- additional_mats = self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx]
873
- else: additional_ths, additional_mats = [], []
1200
+ if str(ts_name) + data[ts_name][
1201
+ 'ht_side'] in self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks:
1202
+ add_mat_idx = self.data.magnet.solve.thermal.insulation_TSA.exterior.blocks.index(
1203
+ str(ts_name) + data[ts_name]['ht_side'])
1204
+ additional_ths = \
1205
+ self.data.magnet.solve.thermal.insulation_TSA.exterior.thicknesses_append[
1206
+ add_mat_idx]
1207
+ additional_mats = \
1208
+ self.data.magnet.solve.thermal.insulation_TSA.exterior.materials_append[add_mat_idx]
1209
+ else:
1210
+ additional_ths, additional_mats = [], []
874
1211
  cond = self.data.conductors[data[ts_name]['conductor']].cable
875
- ht_ins_th = cond.th_insulation_along_width if data[ts_name]['ht_side'] in 'io' else cond.th_insulation_along_height
876
- side_ins = [ht_ins_th] + [h_ins for h_ins in qh.h_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr - 1]] + additional_ths
877
- side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr - 1]] + ['SS'] +\
878
- [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr - 1]] + additional_mats
1212
+ ht_ins_th = cond.th_insulation_along_width if data[ts_name][
1213
+ 'ht_side'] in 'io' else cond.th_insulation_along_height
1214
+ side_ins = [ht_ins_th] + [s_ins for s_ins in qh.s_ins[qh_nr - 1]] + [
1215
+ qh.h[qh_nr - 1]] + [s_ins_He for s_ins_He in
1216
+ qh.s_ins_He[qh_nr - 1]] + additional_ths
1217
+ side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1218
+ qh.type_ins[qh_nr - 1]] + ['SS'] + \
1219
+ [type_ins_He for type_ins_He in
1220
+ qh.type_ins_He[qh_nr - 1]] + additional_mats
879
1221
  if data[ts_name]['ht_side'] in 'il': side_ins.reverse(), side_ins_type.reverse()
880
1222
  qh_list = [qh_nr]
881
1223
  elif ins_type == 'internal':
882
1224
  data = self.qh_data[qh_nr]
883
1225
  cond = self.data.conductors[data[ts_name]['conductor']].cable
884
- cond2 = self.data.conductors[self.wedge_cond[int(ts_name.split('_')[0][1:])] if 'w' in ts_name
885
- else pg.blocks[int(ts_name.split('_')[1]) if data[ts_name]['ht_side'] == 'o' else int(ts_name.split('_')[0])].conductor].cable
886
- side_ins_qh = [h_ins for h_ins in qh.h_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr - 1]]
1226
+ cond2 = self.data.conductors[
1227
+ self.wedge_cond[int(ts_name.split('_')[0][1:])] if 'w' in ts_name
1228
+ else pg.blocks[
1229
+ int(ts_name.split('_')[1]) if data[ts_name]['ht_side'] == 'o' else int(
1230
+ ts_name.split('_')[0])].conductor].cable
1231
+ side_ins_qh = [s_ins for s_ins in qh.s_ins[qh_nr - 1]] + [qh.h[qh_nr - 1]] + [s_ins_He
1232
+ for
1233
+ s_ins_He
1234
+ in
1235
+ qh.s_ins_He[
1236
+ qh_nr - 1]]
887
1237
  mid_lyr_th, null_idx = _compute_insulation_thicknesses(
888
- ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width, cond2.th_insulation_along_width] + side_ins_qh))
1238
+ ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width,
1239
+ cond2.th_insulation_along_width] + side_ins_qh))
889
1240
  for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
890
- side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + [cond2.th_insulation_along_width]
891
- side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr - 1]] + ['SS'] +\
892
- [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr - 1]] + mid_materials + [cond2.material_insulation]
1241
+ side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + [
1242
+ cond2.th_insulation_along_width]
1243
+ side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1244
+ qh.type_ins[qh_nr - 1]] + ['SS'] + \
1245
+ [type_ins_He for type_ins_He in
1246
+ qh.type_ins_He[qh_nr - 1]] + mid_materials + [
1247
+ cond2.material_insulation]
893
1248
  if data[ts_name]['ht_side'] == 'i': side_ins.reverse(), side_ins_type.reverse()
894
1249
  qh_list = [qh_nr]
895
1250
  elif ins_type == 'internal_double':
@@ -897,16 +1252,28 @@ class Mesh:
897
1252
  data, data2 = self.qh_data[qh_nr1], self.qh_data[qh_nr2]
898
1253
  cond = self.data.conductors[data[ts_name]['conductor']].cable
899
1254
  cond2 = self.data.conductors[data2[ts_name]['conductor']].cable
900
- side_ins_qh = [h_ins for h_ins in qh.h_ins[qh_nr1 - 1]] + [qh.h[qh_nr1 - 1]] + [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr1 - 1]]
901
- side_ins_qh2 = [h_ground_ins for h_ground_ins in qh.h_ground_ins[qh_nr2 - 1][::-1]] + [qh.h[qh_nr2 - 1]] + [h_ins for h_ins in qh.h_ins[qh_nr2 - 1][::-1]]
1255
+ side_ins_qh = [s_ins for s_ins in qh.s_ins[qh_nr1 - 1]] + [qh.h[qh_nr1 - 1]] + [s_ins_He
1256
+ for
1257
+ s_ins_He
1258
+ in
1259
+ qh.s_ins_He[
1260
+ qh_nr1 - 1]]
1261
+ side_ins_qh2 = [s_ins_He for s_ins_He in qh.s_ins_He[qh_nr2 - 1][::-1]] + [
1262
+ qh.h[qh_nr2 - 1]] + [s_ins for s_ins in qh.s_ins[qh_nr2 - 1][::-1]]
902
1263
  mid_lyr_th, null_idx = _compute_insulation_thicknesses(
903
- ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width, cond2.th_insulation_along_width] + side_ins_qh + side_ins_qh2))
1264
+ ins_th_dict['mid_layer'][ts_name], sum([cond.th_insulation_along_width,
1265
+ cond2.th_insulation_along_width] + side_ins_qh + side_ins_qh2))
904
1266
  for idx in null_idx: mid_lyr_th.pop(idx), mid_materials.pop(idx)
905
- side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + side_ins_qh2 + [cond2.th_insulation_along_width]
906
- side_ins_type = [cond.material_insulation] + [type_ins for type_ins in qh.type_ins[qh_nr1 - 1]] + ['SS'] +\
907
- [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr1 - 1]] + mid_materials +\
908
- [type_ground_ins for type_ground_ins in qh.type_ground_ins[qh_nr2 - 1][::-1]] + ['SS'] +\
909
- [type_ins for type_ins in qh.type_ins[qh_nr2 - 1][::-1]] + [cond2.material_insulation]
1267
+ side_ins = [cond.th_insulation_along_width] + side_ins_qh + mid_lyr_th + side_ins_qh2 + [
1268
+ cond2.th_insulation_along_width]
1269
+ side_ins_type = [cond.material_insulation] + [type_ins for type_ins in
1270
+ qh.type_ins[qh_nr1 - 1]] + ['SS'] + \
1271
+ [type_ins_He for type_ins_He in
1272
+ qh.type_ins_He[qh_nr1 - 1]] + mid_materials + \
1273
+ [type_ins_He for type_ins_He in qh.type_ins_He[qh_nr2 - 1][::-1]] + [
1274
+ 'SS'] + \
1275
+ [type_ins for type_ins in qh.type_ins[qh_nr2 - 1][::-1]] + [
1276
+ cond2.material_insulation]
910
1277
  qh_list = [qh_nr1, qh_nr2]
911
1278
  qh_labels = [1 if m == 'SS' else None for m in side_ins_type]
912
1279
  ss_indexes = [index for index, value in enumerate(qh_labels) if value == 1]
@@ -916,13 +1283,148 @@ class Mesh:
916
1283
  self.rm.thin_shells.quench_heaters.thicknesses.append([])
917
1284
  self.rm.thin_shells.quench_heaters.layers_material.append([])
918
1285
  self.rm.thin_shells.quench_heaters.label.append([])
1286
+ self.rm.thin_shells.quench_heaters.correction_factors.append(1.0) # default value
919
1287
  for nr, ins_lyr in enumerate(side_ins):
920
- tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_QH, round(ins_lyr / min_h_QH))
1288
+ tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_QH,
1289
+ round(ins_lyr / min_h_QH))
921
1290
  self.rm.thin_shells.quench_heaters.layers_number[-1] += tsa_layers
922
- self.rm.thin_shells.quench_heaters.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
923
- self.rm.thin_shells.quench_heaters.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1291
+ self.rm.thin_shells.quench_heaters.thicknesses[-1].extend(
1292
+ [ins_lyr / tsa_layers] * tsa_layers)
1293
+ self.rm.thin_shells.quench_heaters.layers_material[-1].extend(
1294
+ [side_ins_type[nr]] * tsa_layers)
924
1295
  self.rm.thin_shells.quench_heaters.label[-1].extend([qh_labels[nr]] * tsa_layers)
925
1296
 
1297
+ if geometry.model_dump().get('use_TSA_new', False): # collar
1298
+ total_ins_th_dict = self.md.geometries.thin_shells.ins_thickness.collar
1299
+ for ins_name, tags in self.ins_type['collar'].items(): # self.ins_type_col['mid_lines']
1300
+ qh_th = 0.0 # todo, correct for thickness, total ins th already takes into account the insulation layer but not qh
1301
+ residual_th = total_ins_th_dict[ins_name] - qh_th
1302
+ side_ins = [residual_th] # only one layer of insulation, as the other layer is captured in QH
1303
+ # Get insulation materials
1304
+ side_ins_type = [self.data.magnet.solve.thermal.insulation_TSA.between_collar.material]
1305
+
1306
+ self.rm.thin_shells.collar.layers_number.append(0)
1307
+ self.rm.thin_shells.collar.thin_shells.append([tags])
1308
+ self.rm.thin_shells.collar.thicknesses.append([])
1309
+ self.rm.thin_shells.collar.layers_material.append([])
1310
+
1311
+ cond_name = next(iter(self.data.conductors.keys()))
1312
+ ins_to_bare_ratio = (self.data.conductors[cond_name].cable.bare_cable_height_high + 2 *
1313
+ self.data.conductors[cond_name].cable.th_insulation_along_height) / \
1314
+ self.data.conductors[cond_name].cable.bare_cable_height_high
1315
+ DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1316
+ if DUMMY < 0.0: # default value
1317
+ DUMMY = ins_to_bare_ratio # ins to bare scaling
1318
+ self.rm.thin_shells.insulation_types.correction_factors.append(DUMMY)
1319
+
1320
+ for nr, ins_lyr in enumerate(side_ins):
1321
+ tsa_layers = max(mesh.insulation.TSA.minimum_discretizations_COL, round(ins_lyr / min_h_COL))
1322
+ self.rm.thin_shells.collar.layers_number[-1] += tsa_layers
1323
+ self.rm.thin_shells.collar.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
1324
+ self.rm.thin_shells.collar.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1325
+
1326
+ # mid collar groups
1327
+
1328
+ self.rm.thin_shells.ts_collar_groups['1_1'] = []
1329
+ self.rm.thin_shells.ts_collar_groups['2_1'] = []
1330
+ self.rm.thin_shells.ts_collar_groups['1_2'] = []
1331
+ self.rm.thin_shells.ts_collar_groups['2_2'] = []
1332
+
1333
+ max_layer = len([k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()])
1334
+ for blk_nr, blk in self.md.domains.physical_groups.blocks.items(): # only need this for the outer layer :)
1335
+ for ht_nr, el in blk.half_turns.items():
1336
+ if str(el.group[-1]) == str(
1337
+ max_layer): #### 2nd index is swapped due to the checkboard pattern -> otherwise we have to swap it in the .pro file
1338
+ self.rm.thin_shells.ts_collar_groups[
1339
+ el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)].append(
1340
+ self.ins_type['collar'][f'{ht_nr}_x']) # self.ins_type_col['mid_lines']
1341
+
1342
+ # wedges
1343
+ for wdg_nr, el in self.md.domains.physical_groups.wedges.items():
1344
+ if str(el.group[-1]) == str(max_layer):
1345
+ self.rm.thin_shells.ts_collar_groups[el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)].append(
1346
+ self.ins_type['collar'][f'w{wdg_nr}_x']) # self.ins_type_col['mid_lines']
1347
+
1348
+ # collar
1349
+ self.rm.thin_shells.bdry_curves['collar'] = [pg.inner_col]
1350
+
1351
+ if geometry.model_dump().get('use_TSA', False): # poles
1352
+ total_ins_th_dict = self.md.geometries.thin_shells.ins_thickness.poles
1353
+ for ins_name, tags in self.ins_type['poles'].items():
1354
+ other_corrections = 0.0 # assuming the other corrections are zero #debug
1355
+ residual_th = total_ins_th_dict[ins_name] - other_corrections
1356
+ side_ins = [residual_th]
1357
+ side_ins_type = [self.data.magnet.solve.thermal.insulation_TSA.between_collar.material]
1358
+ # assuming the insulation between the pole and ht are the same as for the collar
1359
+
1360
+ self.rm.thin_shells.poles.layers_number.append(0)
1361
+ self.rm.thin_shells.poles.thin_shells.append([tags])
1362
+ self.rm.thin_shells.poles.thicknesses.append([])
1363
+ self.rm.thin_shells.poles.layers_material.append([])
1364
+
1365
+ # Scaling to the pole lines (2nd insulation layer)
1366
+ if ins_name.startswith('pw'): # wedge pole line
1367
+ DUMMY = 1.0 # default
1368
+ elif ins_name.endswith('_r'): # radial line -> halfturn
1369
+ DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_radial)
1370
+ if DUMMY < 0.0: DUMMY = ins_to_bare_ratio # default value
1371
+ else: # pole lines
1372
+ DUMMY = float(self.data.magnet.mesh.thermal.insulation.TSA.scale_factor_azimuthal)
1373
+ if DUMMY < 0.0: DUMMY = 1.0 # default value
1374
+ self.rm.thin_shells.poles.correction_factors.append(DUMMY)
1375
+
1376
+ for nr, ins_lyr in enumerate(side_ins):
1377
+ tsa_layers = max(mesh.insulation.TSA.minimum_discretizations,
1378
+ round(ins_lyr / min_h)) # use default TSA
1379
+ self.rm.thin_shells.poles.layers_number[-1] += tsa_layers
1380
+ self.rm.thin_shells.poles.thicknesses[-1].extend([ins_lyr / tsa_layers] * tsa_layers)
1381
+ self.rm.thin_shells.poles.layers_material[-1].extend([side_ins_type[nr]] * tsa_layers)
1382
+
1383
+ # checkboard pattern to link with the hts
1384
+ self.rm.thin_shells.ts_pole_groups['a_1_1'] = []
1385
+ self.rm.thin_shells.ts_pole_groups['a_2_1'] = []
1386
+ self.rm.thin_shells.ts_pole_groups['a_1_2'] = []
1387
+ self.rm.thin_shells.ts_pole_groups['a_2_2'] = []
1388
+ self.rm.thin_shells.ts_pole_groups['r_1_2'] = []
1389
+ self.rm.thin_shells.ts_pole_groups['r_2_2'] = []
1390
+ self.rm.thin_shells.ts_pole_groups['r_1_1'] = [] # empty
1391
+ self.rm.thin_shells.ts_pole_groups['r_2_1'] = [] # empty
1392
+
1393
+ for key, tag in self.ins_type['poles'].items():
1394
+ alignment = key[-1] # either r or a aligned lines
1395
+ # find the corresponding half turn
1396
+ nr = key[1:key.index('_')]
1397
+ if nr.startswith('w'):
1398
+ # wedge to pole line
1399
+ for _, gr in self.md.domains.physical_groups.wedges.items():
1400
+ # for name, tag in gr.aux_lines.items():
1401
+ tag = gr.aux_lines.get(nr)
1402
+ if tag is not None:
1403
+ group = gr.group
1404
+ group = group[1] + '_' + str(1 + int(group[-1]) % 2)
1405
+ line_tag = self.ins_type['poles'][f'p{nr}_r'] # need to get the correct tag
1406
+ self.rm.thin_shells.ts_pole_groups['a_' + group].append(line_tag)
1407
+
1408
+ else:
1409
+ ht_nr = int(nr)
1410
+ for blk_nr, blk in self.md.domains.physical_groups.blocks.items(): # only need this for the outer layer :)
1411
+ el = blk.half_turns.get(ht_nr, None)
1412
+ if el is not None:
1413
+ break
1414
+ # alignment : direction of the normal vecetor
1415
+ if alignment == 'r': #### 2nd index is swapped -> radial difference (inner to outer)
1416
+ group = el.group[1] + '_' + str(
1417
+ 1 + int(el.group[-1]) % 2) # group = el.group[1] + '_' + el.group[-1] #
1418
+ self.rm.thin_shells.ts_pole_groups['a_' + group].append(tag)
1419
+ elif alignment == 'a': #### 1st index is swapped -> azimuthal difference (left to right)
1420
+ group = str(1 + int(el.group[1]) % 2) + '_' + el.group[
1421
+ -1] # el.group[1] + '_' + el.group[-1] #
1422
+ self.rm.thin_shells.ts_pole_groups['r_' + group].append(tag)
1423
+ # save boundary line for the TSA
1424
+ tag = pg.poles.curves.get("bdry", None)
1425
+ if tag is not None:
1426
+ self.rm.thin_shells.bdry_curves['poles'] = [tag]
1427
+
926
1428
  # Powered
927
1429
  for blk_nr, blk in pg.blocks.items():
928
1430
  ht_list = list(blk.half_turns.keys())
@@ -933,24 +1435,33 @@ class Mesh:
933
1435
  self.rm.powered[ht.group].vol.numbers.append(ht.tag)
934
1436
  if 'bore_field' in mesh.model_dump():
935
1437
  self.rm.powered[ht.group].vol.currents.append(initial_current * (1 if blk.current_sign > 0 else -1))
1438
+ self.rm.powered[ht.group].curve.names.append(ht_name)
1439
+ self.rm.powered[ht.group].curve.numbers.append(ht.single_node)
1440
+
1441
+ for line in ['l', 'i', 'o', 'h']:
1442
+ # Bare edges
1443
+ self.rm.powered[ht.group].surf_in.names.append(ht_name + line)
1444
+ self.rm.powered[ht.group].surf_in.numbers.append(ht.lines[line])
1445
+ if line in 'io':
1446
+ self.rm.thin_shells.normals_directed['radially'].append(ht.lines[line])
1447
+ else:
1448
+ self.rm.thin_shells.normals_directed['azimuthally'].append(ht.lines[line])
1449
+
936
1450
  if geometry.model_dump().get('use_TSA', False):
937
- for line in ['l', 'i', 'o', 'h']:
938
- # Bare edges
939
- self.rm.powered[ht.group].surf_in.names.append(ht_name + line)
940
- self.rm.powered[ht.group].surf_in.numbers.append(ht.lines[line])
941
- if line in 'io': self.rm.thin_shells.normals_directed['radially'].append(ht.lines[line])
942
- else: self.rm.thin_shells.normals_directed['azimuthally'].append(ht.lines[line])
943
1451
  # Auxiliary thin shells
944
1452
  for line_name, line_tag in ht.aux_lines.items():
945
- self.rm.powered[ht.group].surf_in.names.append(line_name)
946
- self.rm.powered[ht.group].surf_in.numbers.append(line_tag)
1453
+ # update this must be _out instead of in to be not assigned to bare_layers_{i}_{j} in the .pro
1454
+ # (not used in my example, since ht.aux_lines is empty)
1455
+ self.rm.powered[ht.group].surf_out.names.append(line_name)
1456
+ self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
947
1457
  self.rm.thin_shells.normals_directed['radially'].append(line_tag)
948
1458
  # Thin shells
949
1459
  for line_name, line_tag in dict(ht.mid_layer_lines.inner, **ht.mid_layer_lines.outer).items():
950
1460
  self.rm.powered[ht.group].surf_out.names.append(line_name)
951
1461
  self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
952
1462
  self.rm.thin_shells.normals_directed['radially'].append(line_tag)
953
- for line_name, line_tag in dict(ht.mid_pole_lines, **ht.mid_winding_lines, **ht.mid_turn_lines).items():
1463
+ for line_name, line_tag in dict(ht.mid_pole_lines, **ht.mid_winding_lines,
1464
+ **ht.mid_turn_lines).items():
954
1465
  self.rm.powered[ht.group].surf_out.names.append(line_name)
955
1466
  self.rm.powered[ht.group].surf_out.numbers.append(line_tag)
956
1467
  self.rm.thin_shells.normals_directed['azimuthally'].append(line_tag)
@@ -959,14 +1470,16 @@ class Mesh:
959
1470
  if ht.group[1] == '2':
960
1471
  # mid-turn thin shells precede r2
961
1472
  for line_name, line_tag in ht.mid_turn_lines.items():
962
- if (ht_list.index(ht_nr) != 0 and int(line_name[line_name.index('_') + 1:]) == ht_nr) or\
1473
+ if (ht_list.index(ht_nr) != 0 and int(line_name[line_name.index('_') + 1:]) == ht_nr) or \
963
1474
  (ht_list.index(ht_nr) == 0 and 'w' in line_name):
964
1475
  self.rm.thin_shells.second_group_is_next['azimuthally'].append(line_tag)
965
1476
  # mid-pole thin shells precede r2
966
1477
  if ht_list.index(ht_nr) == 0 and ht.mid_pole_lines:
967
- self.rm.thin_shells.second_group_is_next['azimuthally'].append(list(ht.mid_pole_lines.values())[0])
1478
+ self.rm.thin_shells.second_group_is_next['azimuthally'].append(
1479
+ list(ht.mid_pole_lines.values())[0])
968
1480
  # conductor edges precede r2
969
- elif ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1481
+ elif ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(
1482
+ ht.mid_pole_lines) == 1:
970
1483
  self.rm.thin_shells.second_group_is_next['azimuthally'].append(ht.lines['l'])
971
1484
  # mid-winding thin shells precede r2
972
1485
  for line_name, line_tag in ht.mid_winding_lines.items():
@@ -998,19 +1511,22 @@ class Mesh:
998
1511
  wdg_name = f"w{wdg_nr}_{'EM' if 'bore_field' in mesh.model_dump() else 'TH'}"
999
1512
  self.rm.induced[wdg.group].vol.names.append(wdg_name)
1000
1513
  self.rm.induced[wdg.group].vol.numbers.append(wdg.tag)
1514
+ for line in ['l', 'i', 'o', 'h']:
1515
+ self.rm.induced[wdg.group].surf_in.names.append(wdg_name + line)
1516
+ self.rm.induced[wdg.group].surf_in.numbers.append(wdg.lines[line])
1517
+
1001
1518
  if geometry.model_dump().get('use_TSA', False):
1002
1519
  # Bare edges
1003
1520
  for line in ['l', 'i', 'o', 'h']:
1004
- self.rm.induced[wdg.group].surf_in.names.append(wdg_name + line)
1005
- self.rm.induced[wdg.group].surf_in.numbers.append(wdg.lines[line])
1006
1521
  if line in 'io':
1007
1522
  self.rm.thin_shells.normals_directed['radially'].append(wdg.lines[line])
1008
1523
  else:
1009
1524
  self.rm.thin_shells.normals_directed['azimuthally'].append(wdg.lines[line])
1010
1525
  # Auxiliary thin shells
1011
1526
  for line_name, line_tag in wdg.aux_lines.items():
1012
- self.rm.induced[wdg.group].surf_in.names.append(line_name)
1013
- self.rm.induced[wdg.group].surf_in.numbers.append(line_tag)
1527
+ # update this must be _out instead of in to be not assigned to bare_layers_{i}_{j} in the .pro
1528
+ self.rm.induced[wdg.group].surf_out.names.append(line_name)
1529
+ self.rm.induced[wdg.group].surf_out.numbers.append(line_tag)
1014
1530
  self.rm.thin_shells.normals_directed['radially'].append(line_tag)
1015
1531
  # Thin shells
1016
1532
  for line_name, line_tag in dict(wdg.mid_layer_lines.inner, **wdg.mid_layer_lines.outer).items():
@@ -1045,20 +1561,29 @@ class Mesh:
1045
1561
  self.rm.insulator.surf.names.append('ins' + group_name)
1046
1562
  self.rm.insulator.surf.numbers.append(curve)
1047
1563
 
1048
- # Iron
1049
- for group_name, surface in pg.iron.surfaces.items():
1050
- self.rm.iron_yoke.vol.names.append(group_name)
1051
- self.rm.iron_yoke.vol.numbers.append(surface)
1564
+ for attr in geometry.areas:
1565
+ for group_name, surface in getattr(pg, attr).surfaces.items():
1566
+ h = getattr(self.rm, attr).vol
1567
+ h.names.append(group_name)
1568
+ h.numbers.append(surface)
1569
+
1570
+ if self.data.magnet.mesh.thermal.reference.enabled:
1571
+ if not pg.ref_mesh.surfaces == {}:
1572
+ for name, num in pg.ref_mesh.surfaces.items():
1573
+ self.rm.ref_mesh.vol.names.append(name)
1574
+ self.rm.ref_mesh.vol.numbers.append(num)
1052
1575
 
1053
1576
  # Boundary conditions
1054
1577
  if 'insulation' in mesh.model_dump() and 'TSA' in mesh.model_dump()["insulation"]:
1055
1578
  # Initialize lists
1056
- for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions, self.rm.boundaries.thermal): # b.c. type
1579
+ for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions,
1580
+ self.rm.boundaries.thermal): # b.c. type
1057
1581
  bc_rm[1].bc.names = []
1058
1582
  bc_rm[1].bc.numbers = []
1059
1583
  if bc_data[0] == 'cooling':
1060
1584
  bc_rm[1].bc.values = []
1061
- for group in ['1_r1_a1', '2_r1_a1', '1_r2_a1', '2_r2_a1', '1_r1_a2', '2_r1_a2', '1_r2_a2', '2_r2_a2']:
1585
+ for group in ['1_r1_a1', '2_r1_a1', '1_r2_a1', '2_r2_a1', '1_r1_a2', '2_r1_a2', '1_r2_a2',
1586
+ '2_r2_a2']:
1062
1587
  bc_rm[1].groups[group] = []
1063
1588
  else:
1064
1589
  bc_rm[1].bc.value = []
@@ -1067,8 +1592,10 @@ class Mesh:
1067
1592
 
1068
1593
  # Apply general cooling and adiabatic
1069
1594
  if self.data.magnet.solve.thermal.He_cooling.enabled:
1070
- cooling_side = {'i': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in ['inner', 'external']),
1071
- 'o': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in ['outer', 'external']),
1595
+ cooling_side = {'i': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in
1596
+ ['inner', 'external']),
1597
+ 'o': any(coil_side in self.data.magnet.solve.thermal.He_cooling.sides for coil_side in
1598
+ ['outer', 'external']),
1072
1599
  'hl': self.data.magnet.solve.thermal.He_cooling.sides == 'external'}
1073
1600
  else:
1074
1601
  cooling_side = {'i': False, 'o': False, 'hl': False}
@@ -1077,48 +1604,74 @@ class Mesh:
1077
1604
  line_tag = tag if tag else el.lines[side]
1078
1605
  bnd_list_names[bc_type].append(name + side)
1079
1606
  bnd_list_numbers[bc_type].append(line_tag)
1080
- if side in 'io': new_group = el.group[:3] + 'a1' if el.group[4] == '2' else el.group[:3] + 'a2'
1081
- else: new_group = 'r1' + el.group[2:] if el.group[1] == '2' else 'r2' + el.group[2:]
1607
+ if side in 'io':
1608
+ new_group = el.group[:3] + 'a1' if el.group[4] == '2' else el.group[:3] + 'a2'
1609
+ else:
1610
+ new_group = 'r1' + el.group[2:] if el.group[1] == '2' else 'r2' + el.group[2:]
1082
1611
  bc_rm[bc_type].groups[new_group].append(line_tag)
1083
1612
  for group_name, group in self.rm.thin_shells.groups.items():
1084
1613
  if line_tag in group:
1085
1614
  bc_rm[bc_type].groups[el.group[0] + '_' + new_group].append(line_tag)
1086
1615
  break
1087
1616
 
1088
- bc_rm = {'Robin': self.rm.boundaries.thermal.cooling, 'Neumann': self.rm.boundaries.thermal.heat_flux}
1617
+ bc_rm = {'Robin': self.rm.boundaries.thermal.cooling, 'Neumann': self.rm.boundaries.thermal.heat_flux,
1618
+ 'collar': self.rm.boundaries.thermal.collar}
1089
1619
  bnd_list_names = {'Robin': [], 'Neumann': []}
1090
1620
  bnd_list_numbers = {'Robin': [], 'Neumann': []}
1621
+ DISABLE_BNDRY_COND = False # debug: this should always be false
1091
1622
  if geometry.model_dump().get('use_TSA', False):
1092
1623
  # Half turn boundaries
1093
1624
  for coil_nr, coil in self.md.geometries.coil.anticlockwise_order.coils.items():
1094
1625
  for lyr_nr, orders in coil.layers.items():
1095
1626
  for order in orders:
1096
1627
  ht_list = list(pg.blocks[order.block].half_turns.keys())
1097
- for ht_nr, ht in pg.blocks[order.block].half_turns.items():
1628
+ for ht_nr, ht in pg.blocks[
1629
+ order.block].half_turns.items(): # apply bdry condition if no TSL
1098
1630
  if not ht.mid_layer_lines.inner:
1099
- __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'i', 'Robin' if cooling_side['i'] else 'Neumann')
1631
+ __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'i',
1632
+ 'Robin' if cooling_side['i'] else 'Neumann')
1100
1633
  if not ht.mid_layer_lines.outer:
1101
- __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann')
1102
- if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1103
- __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'l', 'Robin' if cooling_side['hl'] else 'Neumann')
1104
- if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1105
- __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'h', 'Robin' if cooling_side['hl'] else 'Neumann')
1634
+ if DISABLE_BNDRY_COND and self.data.magnet.geometry.thermal.use_TSA_new and \
1635
+ ht.group[4] == max_layer: # do not add new boundary condition:
1636
+ logger.warning("\033[93m DISABLING NEUMANN CONDITION")
1637
+ else:
1638
+ __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o',
1639
+ 'Robin' if cooling_side['o'] else 'Neumann')
1640
+ if ht_list.index(ht_nr) == 0 and len(ht.mid_turn_lines) + len(
1641
+ ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1642
+ __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'l',
1643
+ 'Robin' if cooling_side['hl'] else 'Neumann')
1644
+ if ht_list.index(ht_nr) == len(ht_list) - 1 and len(ht.mid_turn_lines) + len(
1645
+ ht.mid_winding_lines) + len(ht.mid_pole_lines) == 1:
1646
+ __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'h',
1647
+ 'Robin' if cooling_side['hl'] else 'Neumann')
1106
1648
  if ht.aux_lines:
1107
- __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann', list(ht.aux_lines.values())[0])
1649
+ __assign_bnd_tag(ht, 'ht' + str(ht_nr), 'o',
1650
+ 'Robin' if cooling_side['hl'] else 'Neumann',
1651
+ list(ht.aux_lines.values())[0])
1652
+
1108
1653
  # Wedge boundaries
1109
1654
  for wdg_nr, wdg in pg.wedges.items():
1110
1655
  if not wdg.mid_layer_lines.inner:
1111
1656
  __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'i', 'Robin' if cooling_side['i'] else 'Neumann')
1112
1657
  if not wdg.mid_layer_lines.outer:
1113
- __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann')
1658
+ if DISABLE_BNDRY_COND and self.data.magnet.geometry.thermal.use_TSA_new and wdg.group[
1659
+ 4] == max_layer:
1660
+ logger.warning("\033[93m DISABLING NEUMANN CONDITION")
1661
+ else:
1662
+ __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['o'] else 'Neumann')
1114
1663
  if wdg.aux_lines:
1115
- __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann', list(wdg.aux_lines.values())[0])
1116
- else: # insulation case
1664
+ __assign_bnd_tag(wdg, 'wd' + str(wdg_nr), 'o', 'Robin' if cooling_side['hl'] else 'Neumann',
1665
+ list(wdg.aux_lines.values())[0])
1666
+
1667
+ else: # insulation case, no TSA
1117
1668
  for curves_group, tag in pg.insulations.curves.items():
1118
1669
  if self.data.magnet.solve.thermal.He_cooling.enabled:
1119
- if self.data.magnet.solve.thermal.He_cooling.sides == 'external': bc_type = 'Robin'
1670
+ if self.data.magnet.solve.thermal.He_cooling.sides == 'external':
1671
+ bc_type = 'Robin'
1120
1672
  else:
1121
- raise ValueError(f"Cooling side '{self.data.magnet.solve.thermal.He_cooling.sides}' is not supported for meshed insulation models.")
1673
+ raise ValueError(
1674
+ f"Cooling side '{self.data.magnet.solve.thermal.He_cooling.sides}' is not supported for meshed insulation models.")
1122
1675
  # bc_type = 'Robin' if (self.data.magnet.solve.thermal.He_cooling.sides == 'external' or
1123
1676
  # ('inner' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'i') or
1124
1677
  # ('outer' in self.data.magnet.solve.thermal.He_cooling.sides and curves_group[-1] == 'o')) else 'Neumann'
@@ -1126,22 +1679,33 @@ class Mesh:
1126
1679
  bc_type = 'Neumann'
1127
1680
  bnd_list_names[bc_type].append('ins' + curves_group)
1128
1681
  bnd_list_numbers[bc_type].append(tag)
1682
+
1129
1683
  if self.data.magnet.solve.thermal.He_cooling.enabled:
1130
1684
  bc_rm['Robin'].bc.names.append(bnd_list_names['Robin'])
1131
1685
  bc_rm['Robin'].bc.numbers.append(bnd_list_numbers['Robin'])
1132
1686
  bc_rm['Robin'].bc.values.append([self.data.magnet.solve.thermal.He_cooling.heat_transfer_coefficient,
1133
1687
  self.data.magnet.solve.thermal.init_temperature])
1688
+
1689
+ if self.data.magnet.solve.thermal.collar_cooling.enabled:
1690
+ cool = self.data.magnet.solve.thermal.collar_cooling
1691
+ bc_rm['collar'].bc.numbers = [pg.collar_cooling] # only one physical group for the collar cooling
1692
+ bc_rm['collar'].bc.values = [self.data.magnet.solve.thermal.collar_cooling.heat_transfer_coefficient,
1693
+ cool.ref_temperature if cool.ref_temperature is not None else self.data.magnet.solve.thermal.init_temperature] # [coef or functionname, inittemp]
1694
+
1695
+ # save the boundary names and numbers
1134
1696
  if bnd_list_names['Neumann']:
1135
1697
  bc_rm['Neumann'].bc.names.append(bnd_list_names['Neumann'])
1136
1698
  bc_rm['Neumann'].bc.numbers.append(bnd_list_numbers['Neumann'])
1137
1699
  bc_rm['Neumann'].bc.value.append(0.)
1138
1700
 
1139
1701
  # Apply specific boundary conditions
1140
- for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions, self.rm.boundaries.thermal): # b.c. type
1702
+ for bc_data, bc_rm in zip(self.data.magnet.solve.thermal.overwrite_boundary_conditions,
1703
+ self.rm.boundaries.thermal): # b.c. type
1141
1704
  # bc_data is a tuple like: ('temperature', {'const_T1': boundaries, value)})
1142
1705
  # bc_rm is a tuple like: ('temperature', DirichletCondition(names, numbers, value))
1143
1706
 
1144
- for _, bc in bc_data[1].items(): # all boundary conditions of one b.c. type (e.g., Dirichlet with different temperatures)
1707
+ for _, bc in bc_data[
1708
+ 1].items(): # all boundary conditions of one b.c. type (e.g., Dirichlet with different temperatures)
1145
1709
  bnd_list_names = []
1146
1710
  bnd_list_numbers = []
1147
1711
  if geometry.model_dump().get('use_TSA', False):
@@ -1193,17 +1757,17 @@ class Mesh:
1193
1757
  bc_rm[1].bc.names.append(bnd_list_names)
1194
1758
  bc_rm[1].bc.numbers.append(bnd_list_numbers)
1195
1759
  if bc_data[0] == 'cooling':
1196
- bc_rm[1].bc.values.append([bc.heat_transfer_coefficient, self.data.magnet.solve.thermal.init_temperature])
1760
+ bc_rm[1].bc.values.append(
1761
+ [bc.heat_transfer_coefficient, self.data.magnet.solve.thermal.init_temperature])
1197
1762
  elif bc_data[0] == 'temperature':
1198
1763
  bc_rm[1].bc.value.append(bc.const_temperature)
1199
1764
  elif bc_data[0] == 'heat_flux':
1200
1765
  bc_rm[1].bc.value.append(bc.const_heat_flux)
1201
1766
 
1202
- def setMeshOptions(self, run_type):
1767
+ def setMeshOptions(self):
1203
1768
  """
1204
1769
  Meshes the generated domain
1205
1770
  """
1206
- mesh = self.data.magnet.mesh
1207
1771
  gmsh.option.setNumber("Mesh.MeshSizeExtendFromBoundary", 0)
1208
1772
  gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
1209
1773
  gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
@@ -1227,14 +1791,6 @@ class Mesh:
1227
1791
  self.mesh_parameters['Gamma'] = min(self.mesh.getElementQualities(elementTags=tags, qualityName='gamma'))
1228
1792
  self.mesh_parameters['nodes'] = len(self.mesh.getNodes()[0])
1229
1793
 
1230
- # gmsh.plugin.setNumber("AnalyseMeshQuality", "JacobianDeterminant", 1)
1231
- # gmsh.plugin.setNumber("AnalyseMeshQuality", "CreateView", 100)
1232
- # test = gmsh.plugin.run("AnalyseMeshQuality")
1233
- # test2 = gmsh.view.getModelData(test, test)
1234
-
1235
- # gmsh.logger.getLastError()
1236
- # gmsh.logger.get()
1237
-
1238
1794
  def saveClosestNeighboursList(self):
1239
1795
 
1240
1796
  def _closest_node_on_reference(origin_points, reference_points):
@@ -1249,7 +1805,8 @@ class Mesh:
1249
1805
  for x in range(0, len(origin_points), 3):
1250
1806
  origin_point = origin_points[x:x + 3]
1251
1807
  # Compute distance list between origin point and reference point list
1252
- dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in range(0, len(reference_points), 3)]
1808
+ dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in
1809
+ range(0, len(reference_points), 3)]
1253
1810
  min_idx = 3 * np.argmin(dist_lst)
1254
1811
  closest_node.append(origin_point + reference_points[min_idx:min_idx + 3])
1255
1812
  return closest_node
@@ -1260,7 +1817,8 @@ class Mesh:
1260
1817
  self.rc.neighbouring_nodes.groups[group].extend(
1261
1818
  [node for node_list in _closest_node_on_reference(origin_list, reference_list) for node in node_list])
1262
1819
 
1263
- logger.info(f"Info : {self.data.general.magnet_name} - F i n d i n g C l o s e s t N e i g h b o u r s . . .")
1820
+ logger.info(
1821
+ f"Info : {self.data.general.magnet_name} - F i n d i n g C l o s e s t N e i g h b o u r s . . .")
1264
1822
  logger.info(f"Info : Finding closest reference nodes ...")
1265
1823
 
1266
1824
  self.rc.neighbouring_nodes.groups['1_1'] = []
@@ -1275,10 +1833,12 @@ class Mesh:
1275
1833
  for line_name, mid_layer in el.mid_layer_lines.inner.items(): _get_closest_nodes('i')
1276
1834
  for line_name, mid_layer in el.mid_layer_lines.outer.items(): _get_closest_nodes('o')
1277
1835
  for line_name, mid_layer in el.aux_lines.items(): _get_closest_nodes('o')
1278
- for line_name, mid_layer in el.mid_pole_lines.items(): _get_closest_nodes('l' if ht_list.index(ht_nr) == 0 else 'h')
1279
- for line_name, mid_layer in el.mid_winding_lines.items(): _get_closest_nodes('l' if ht_list.index(ht_nr) == 0 else 'h')
1836
+ for line_name, mid_layer in el.mid_pole_lines.items(): _get_closest_nodes(
1837
+ 'l' if ht_list.index(ht_nr) == 0 else 'h')
1838
+ for line_name, mid_layer in el.mid_winding_lines.items(): _get_closest_nodes(
1839
+ 'l' if ht_list.index(ht_nr) == 0 else 'h')
1280
1840
  for line_name, mid_layer in el.mid_turn_lines.items():
1281
- high = ht_list.index(ht_nr) == len(ht_list) - 1 if 'w' in line_name\
1841
+ high = ht_list.index(ht_nr) == len(ht_list) - 1 if 'w' in line_name \
1282
1842
  else int(line_name[:line_name.index('_')]) == ht_nr
1283
1843
  _get_closest_nodes('h' if high else 'l')
1284
1844
  for wdg_nr, el in self.md.domains.physical_groups.wedges.items():
@@ -1289,7 +1849,122 @@ class Mesh:
1289
1849
  for line_name, mid_layer in el.mid_turn_lines.items():
1290
1850
  _get_closest_nodes('l' if line_name == list(el.mid_turn_lines.keys())[0] else 'h')
1291
1851
 
1292
- logger.info(f"Info : {self.data.general.magnet_name} - E n d F i n d i n g C l o s e s t N e i g h b o u r s")
1852
+ logger.info(
1853
+ f"Info : {self.data.general.magnet_name} - E n d F i n d i n g C l o s e s t N e i g h b o u r s")
1854
+
1855
+ def saveClosestNeighboursList_new_TSA(self):
1856
+ def _closest_node_on_reference(origin_points, reference_points):
1857
+ closest_node = []
1858
+ for x in range(0, len(origin_points), 3):
1859
+ origin_point = origin_points[x:x + 3]
1860
+ # Compute distance list between origin point and reference point list
1861
+ dist_lst = [Func.points_distance(origin_point, reference_points[y:y + 3]) for y in
1862
+ range(0, len(reference_points), 3)]
1863
+ min_idx = 3 * np.argmin(dist_lst)
1864
+ closest_node.append(origin_point + reference_points[min_idx:min_idx + 3])
1865
+ if not closest_node:
1866
+ raise ValueError("No closest nodes found - check mesh and physical groups!")
1867
+ return closest_node
1868
+
1869
+ def _get_closest_nodes(name, reference_list, origin_list):
1870
+ self.rc.neighbouring_nodes.groups[name].extend(
1871
+ [node for node_list in _closest_node_on_reference(origin_list, reference_list) for node in node_list])
1872
+
1873
+ logger.info(
1874
+ f"Info : {self.data.general.magnet_name} - F i n d i n g C l o s e s t N e i g h b o u r s (new TSA) . . .")
1875
+ logger.info(f"Info : Finding closest reference nodes ...")
1876
+
1877
+ # two of these will always be empty, but depending on the layers it will either be a1 or a2
1878
+ self.rc.neighbouring_nodes.groups['mid2ht_1_1'] = []
1879
+ self.rc.neighbouring_nodes.groups['mid2ht_2_1'] = []
1880
+ self.rc.neighbouring_nodes.groups['mid2ht_1_2'] = []
1881
+ self.rc.neighbouring_nodes.groups['mid2ht_2_2'] = []
1882
+ # map coils to origin_list: start with outer layer of HT
1883
+ max_layer = len([k for k in self.md.geometries.coil.coils[1].poles[1].layers.keys()])
1884
+
1885
+ origin_all = [self.mesh.getNodesForPhysicalGroup(1, line)[1].tolist() for _, line in
1886
+ self.ins_type['collar'].items()]
1887
+ origin_all = [node for sublist in origin_all for node in sublist] # flatten the list
1888
+
1889
+ for blk_nr, blk in self.md.domains.physical_groups.blocks.items():
1890
+ for ht_nr, el in blk.half_turns.items():
1891
+ if str(el.group[-1]) == str(max_layer):
1892
+ group = el.group[1] + '_' + str((1 + int(el.group[-1])) % 2) # e.g. 1_2
1893
+ origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['collar'][str(ht_nr) + '_x'])[
1894
+ 1].tolist()
1895
+ ref_list = np.array(self.mesh.getNodesForPhysicalGroup(1, el.lines['o'])[1]).tolist()
1896
+ _get_closest_nodes(reference_list=ref_list, name=f'mid2ht_{group}', origin_list=origin)
1897
+ # collar mid
1898
+ self.rc.neighbouring_nodes.groups['mid2col'] = []
1899
+ ref = np.array(self.mesh.getNodesForPhysicalGroup(1, self.md.domains.physical_groups.inner_col)[1]).tolist()
1900
+ _get_closest_nodes(reference_list=ref, origin_list=origin_all, name='mid2col')
1901
+ # collar wedges
1902
+ for wdg_nr, el in self.md.domains.physical_groups.wedges.items(): ## one can conveniently add the wedges to the mid2ht groups
1903
+ if str(el.group[-1]) == str(max_layer):
1904
+ group = el.group[1] + '_' + str((1 + int(el.group[-1])) % 2)
1905
+ origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['collar']['w' + str(wdg_nr) + '_x'])[
1906
+ 1].tolist()
1907
+ ref_list = np.array(self.mesh.getNodesForPhysicalGroup(1, el.lines['o'])[1]).tolist()
1908
+ _get_closest_nodes(reference_list=ref_list, name=f'mid2ht_{group}', origin_list=origin)
1909
+
1910
+ # POLES
1911
+ self.rc.neighbouring_nodes.groups['pole_mid2ht_1_2'] = []
1912
+ self.rc.neighbouring_nodes.groups['pole_mid2ht_1_1'] = []
1913
+ self.rc.neighbouring_nodes.groups['pole_mid2ht_2_1'] = []
1914
+ self.rc.neighbouring_nodes.groups['pole_mid2ht_2_2'] = []
1915
+ self.rc.neighbouring_nodes.groups['mid2pol'] = []
1916
+ # TSA lines to poles
1917
+ for tsl_name in self.ins_type['poles'].keys():
1918
+ # first half: mid2ht (also mid2wedge)
1919
+ nr = tsl_name[1:tsl_name.index('_')]
1920
+ origin = self.mesh.getNodesForPhysicalGroup(1, self.ins_type['poles'][tsl_name])[1].tolist()
1921
+ if nr.startswith('w'): # this is a mid2wedge
1922
+ ref_list = None
1923
+ for i, gr in self.md.domains.physical_groups.wedges.items():
1924
+ # for name, tag in gr.aux_lines.items():
1925
+ tag = gr.aux_lines.get(nr)
1926
+ if tag is not None: # should only be one
1927
+ ref_list = self.mesh.getNodesForPhysicalGroup(1, tag)[1]
1928
+ ref_list = [float(x) for x in ref_list]
1929
+ break
1930
+ # alignment is always the same here, for naming later
1931
+ group = gr.group
1932
+ group = group[1] + '_' + str(1 + int(group[-1]) % 2)
1933
+ else:
1934
+ ht_nr = int(nr) # e.g. p1_a and p12_a -> 1 and 12
1935
+ alignment = tsl_name[-1]
1936
+ # reference can be the nodes from the half turn, we don't have to specify the line
1937
+ for blk in self.md.domains.physical_groups.blocks.values():
1938
+ el = blk.half_turns.get(ht_nr, None)
1939
+ if el is not None:
1940
+ ref_list = []
1941
+ [ref_list.extend(self.mesh.getNodesForPhysicalGroup(1, line)[1]) for line in el.lines.values()]
1942
+ ref_list = [float(x) for x in ref_list]
1943
+ break
1944
+ # alignment : direction of the normal vector, for naming
1945
+ if alignment == 'r':
1946
+ # group = el.group[1] + '_' + el.group[-1]
1947
+ group = el.group[1] + '_' + str(1 + int(el.group[-1]) % 2)
1948
+ elif alignment == 'a':
1949
+ # group = el.group[1] + '_' + el.group[-1]
1950
+ group = str(1 + int(el.group[1]) % 2) + '_' + el.group[-1]
1951
+ _get_closest_nodes(reference_list=ref_list, name=f'pole_mid2ht_{group}', origin_list=origin)
1952
+
1953
+ # second half: mid2pole
1954
+ origin = [float(x) for x in
1955
+ self.mesh.getNodesForPhysicalGroup(1, self.ins_type['poles'][tsl_name])[1].tolist()]
1956
+ pole_bdry = self.md.domains.physical_groups.poles.curves.get("bdry", None)
1957
+ ###ref_list = [float(x) for x in self.mesh.getNodesForPhysicalGroup(2, self.md.domains.physical_groups.poles.surfaces['SS'])[1].tolist()]
1958
+ ref_list = [float(x) for x in self.mesh.getNodesForPhysicalGroup(1, pole_bdry)[1].tolist()]
1959
+ # just use all the nodes
1960
+ # todo: this can be optimized by selecting only the boundary of the pole
1961
+ _get_closest_nodes(reference_list=ref_list, name=f'mid2pol', origin_list=origin)
1962
+
1963
+ logger.info(
1964
+ f"Info : {self.data.general.magnet_name} - E n d F i n d i n g C l o s e s t N e i g h b o u r s (new TSA)")
1965
+
1966
+ def saveHalfTurnCornerPositions(self):
1967
+ with open(f"{self.geom_files}.crns", 'r') as f: self.rc.coordinates_per_half_turn = json.load(f)
1293
1968
 
1294
1969
  def selectMeshNodes(self, elements: str):
1295
1970