Coverage for fiqus/post_processors/PostProcessMultipole.py: 11%
275 statements
« prev ^ index » next coverage.py v7.4.4, created at 2026-01-24 01:38 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2026-01-24 01:38 +0000
1import os
2from pathlib import Path
3import gmsh
4import json
5import numpy as np
6import pandas as pd
7import matplotlib.pyplot as plt
8import matplotlib.lines as lines
9import matplotlib.patches as patches
10from matplotlib.collections import PatchCollection
12from fiqus.utils.Utils import GmshUtils
13from fiqus.utils.Utils import GeometricFunctions as Func
14from fiqus.utils.Utils import RoxieParsers
15from fiqus.utils.Utils import FilesAndFolders as Util
16from fiqus.data import DataFiQuS as dF
17from fiqus.data import DataMultipole as dM
18from fiqus.data import RegionsModelFiQuS as rM
21class PostProcess:
22 def __init__(self, data: dF.FDM() = None, solution_folder: str = None, verbose: bool = False):
23 """
24 Class to post process results
25 :param data: FiQuS data model
26 :param verbose: If True more information is printed in python console.
27 """
28 self.data: dF.FDM() = data
29 self.solution_folder = solution_folder
30 self.verbose: bool = verbose
31 self.md = dM.MultipoleData()
32 self.rm = rM.RegionsModel()
34 self.gu = GmshUtils(self.solution_folder, self.verbose)
35 self.gu.initialize(verbosity_Gmsh=self.data.run.verbosity_Gmsh)
37 self.brep_curves = {}
38 for name in self.data.magnet.geometry.electromagnetics.areas:
39 self.brep_curves[name] = {1: set(), 2: set(), 3: set(), 4: set()}
40 self.strands = None
41 self.crns = None
42 self.avg_temperatures = pd.DataFrame()
43 self.postprocess_parameters = dict.fromkeys(['overall_error', 'minimum_diff', 'maximum_diff'])
44 self.mesh_folder = os.path.dirname(self.solution_folder)
45 self.geom_files = os.path.join(os.path.dirname(self.mesh_folder), self.data.general.magnet_name)
46 self.mesh_files = os.path.join(self.mesh_folder, self.data.general.magnet_name)
47 self.model_file = os.path.join(self.solution_folder, self.data.general.magnet_name)
48 self.postproc_settings = pd.DataFrame()
50 self.supported_variables = {'magnetic_flux_density': 'b',
51 'temperature': 'T'}
52 if any([var not in self.supported_variables.values()
53 for var in self.data.magnet.postproc.electromagnetics.variables + self.data.magnet.postproc.thermal.variables]):
54 pass
55 # raise Exception(f"The interpolation of the field at the strands locations can not be executed: "
56 # f"a variable listed in 'post_processors' -> 'variables' is not supported. "
57 # f"Supported variables are: {self.supported_variables.values()}")
58 self.physical_quantities_abbreviations = \
59 {'magnetic_flux_density': ('BX/T', 'BY/T'),
60 'temperature': ('T/K', '-')}
61 self.physical_quantity = None
62 self.formatted_headline = "{0:>5}{1:>8}{2:>7}{3:>12}{4:>13}{5:>8}{6:>11}{7:>16}{8:>8}{9:>10}\n\n"
63 self.formatted_content = "{0:>6}{1:>6}{2:>7}{3:>13}{4:>13}{5:>11}{6:>11}{7:>11}{8:>9}{9:>8}\n"
64 self.map2d_headline_names = []
66 def prepare_settings(self, settings):
67 self.postproc_settings = pd.DataFrame({
68 'variables': settings.variables,
69 'volumes': settings.volumes})
70 if 'compare_to_ROXIE' in settings.model_dump():
71 self.physical_quantity = 'magnetic_flux_density'
72 else:
73 self.physical_quantity = 'temperature'
74 self.map2d_headline_names = ['BL.', 'COND.', 'NO.', 'X-POS/MM', 'Y-POS/MM'] + \
75 [abbr for abbr in self.physical_quantities_abbreviations[self.physical_quantity]] + \
76 ['AREA/MM**2', 'CURRENT', 'FILL FAC.']
78 if settings.plot_all != 'false':
79 self.fiqus = None
80 self.roxie = None
81 fig1 = plt.figure(1)
82 self.ax = fig1.add_subplot()
83 self.ax.set_xlabel('x [cm]') # adjust other plots to cm
84 self.ax.set_ylabel('y [cm]')
85 # self.ax.set_xlim(0, 0.09)
86 # self.ax.set_ylim(0, 0.09)
88 if not settings.model_dump().get('take_average_conductor_temperature', False):
89 if settings.model_dump().get('compare_to_ROXIE', False):
90 fig2 = plt.figure(2)
91 self.ax2 = fig2.add_subplot(projection='3d')
92 self.ax2.set_xlabel('x [m]')
93 self.ax2.set_ylabel('y [m]')
94 self.ax2.set_zlabel('Absolute Error [T]')
95 self.fig4 = plt.figure(4)
96 self.ax4 = plt.axes()
97 self.ax4.set_xlabel('x [cm]')
98 self.ax4.set_ylabel('y [cm]')
99 self.ax4.set_aspect('equal', 'box')
100 fig3 = plt.figure(3)
101 self.ax3 = fig3.add_subplot(projection='3d')
102 self.ax3.set_xlabel('x [m]')
103 self.ax3.set_ylabel('y [m]')
104 self.ax3.set_zlabel('norm(B) [T]' if 'compare_to_ROXIE' in settings.model_dump() else '')
106 if 'compare_to_ROXIE' in settings.model_dump() and 'b' in settings.variables:
107 b_index = settings.variables.index('b')
108 file_to_open = os.path.join(self.solution_folder, f"b_{settings.volumes[b_index]}.pos")
109 gmsh.open(file_to_open)
110 elif 'T' in settings.variables:
111 T_index = settings.variables.index('T')
112 file_to_open = os.path.join(self.solution_folder, f"T_{settings.volumes[T_index]}.pos")
113 gmsh.open(file_to_open)
118 def clear(self):
119 self.md = dM.MultipoleData()
120 self.rm = rM.RegionsModel()
121 plt.close('all')
122 gmsh.clear()
124 def ending_step(self, gui: bool = False):
125 if gui:
126 self.gu.launch_interactive_GUI()
127 else:
128 if gmsh.isInitialized():
129 gmsh.clear()
130 gmsh.finalize()
132 def loadAuxiliaryFile(self, run_type):
133 self.md = Util.read_data_from_yaml(f"{self.mesh_files}_{run_type}.aux", dM.MultipoleData)
135 def loadRegionFile(self):
136 self.rm = Util.read_data_from_yaml(f"{self.mesh_files}_TH.reg", rM.RegionsModel)
138 def loadStrandPositions(self, run_type):
139 self.strands = json.load(open(f"{self.geom_files}_{run_type}.strs"))
141 def loadHalfTurnCornerPositions(self):
142 self.crns = json.load(open(f"{self.geom_files}.crns"))
144 def plotHalfTurnGeometry(self, compare_to_ROXIE):
145 for i in range(len(self.crns['iH'])):
146 self.ax.add_line(lines.Line2D([self.crns['iH'][i][0], self.crns['iL'][i][0]],
147 [self.crns['iH'][i][1], self.crns['iL'][i][1]], color='green'))
148 self.ax.add_line(lines.Line2D([self.crns['oH'][i][0], self.crns['oL'][i][0]],
149 [self.crns['oH'][i][1], self.crns['oL'][i][1]], color='green'))
150 self.ax.add_line(lines.Line2D([self.crns['oL'][i][0], self.crns['iL'][i][0]],
151 [self.crns['oL'][i][1], self.crns['iL'][i][1]], color='green'))
152 self.ax.add_line(lines.Line2D([self.crns['iH'][i][0], self.crns['oH'][i][0]],
153 [self.crns['iH'][i][1], self.crns['oH'][i][1]], color='green'))
154 cc_fiqus = Func.centroid([self.crns['iH'][i][0], self.crns['iL'][i][0],
155 self.crns['oL'][i][0], self.crns['oH'][i][0]],
156 [self.crns['iH'][i][1], self.crns['iL'][i][1],
157 self.crns['oL'][i][1], self.crns['oH'][i][1]])
159 if compare_to_ROXIE:
160 self.ax.add_line(lines.Line2D([self.crns['iHr'][i][0], self.crns['iLr'][i][0]],
161 [self.crns['iHr'][i][1], self.crns['iLr'][i][1]],
162 color='red', linestyle='dashed'))
163 self.ax.add_line(lines.Line2D([self.crns['oHr'][i][0], self.crns['oLr'][i][0]],
164 [self.crns['oHr'][i][1], self.crns['oLr'][i][1]],
165 color='red', linestyle='dashed'))
166 self.ax.add_line(lines.Line2D([self.crns['oLr'][i][0], self.crns['iLr'][i][0]],
167 [self.crns['oLr'][i][1], self.crns['iLr'][i][1]],
168 color='red', linestyle='dashed'))
169 self.ax.add_line(lines.Line2D([self.crns['iHr'][i][0], self.crns['oHr'][i][0]],
170 [self.crns['iHr'][i][1], self.crns['oHr'][i][1]],
171 color='red', linestyle='dashed'))
172 self.ax.text((self.crns['oLr'][i][0] + self.crns['iLr'][i][0]) / 2,
173 (self.crns['oLr'][i][1] + self.crns['iLr'][i][1]) / 2,
174 'R' + str(i + 1), style='italic', bbox={'facecolor': 'red', 'pad': 2})
175 self.ax.text((self.crns['iHr'][i][0] + self.crns['oHr'][i][0]) / 2,
176 (self.crns['iHr'][i][1] + self.crns['oHr'][i][1]) / 2,
177 'L' + str(i + 1), style='italic', bbox={'facecolor': 'red', 'pad': 2})
178 cc_roxie = Func.centroid(
179 [self.crns['iHr'][i][0], self.crns['iLr'][i][0], self.crns['oLr'][i][0], self.crns['oHr'][i][0]],
180 [self.crns['iHr'][i][1], self.crns['iLr'][i][1], self.crns['oLr'][i][1], self.crns['oHr'][i][1]])
181 self.roxie = self.ax.scatter(cc_roxie[0], cc_roxie[1], edgecolor='r', facecolor='none')
183 self.fiqus = self.ax.scatter(cc_fiqus[0], cc_fiqus[1], c="green")
185 def postProcess(self, postproc):
186 df_ref = pd.DataFrame()
187 model_file_extension = 'EM' if 'compare_to_ROXIE' in postproc.model_dump() else 'TH'
189 if postproc.model_dump().get('compare_to_ROXIE', False):
190 # flag_self_field = False
191 # path_map2d = Path(postproc.compare_to_ROXIE, "MQXA_All_" +
192 # f"{'WithIron_' if self.data.magnet.geometry.with_iron_yoke else 'NoIron_'}" +
193 # f"{'WithSelfField' if flag_self_field else 'NoSelfField'}" +
194 # f"{'' if flag_contraction else '_no_contraction'}" + ".map2d")
195 df_ref = RoxieParsers.parseMap2d(map2dFile=Path(postproc.compare_to_ROXIE), physical_quantity='magnetic_flux_density')
196 BB_roxie = np.linalg.norm(df_ref[['BX/T', 'BY/T']].values, axis=1)
197 if postproc.plot_all != 'false':
198 path_cond2d = Path(os.path.join(os.path.dirname(postproc.compare_to_ROXIE), self.data.general.magnet_name + ".cond2d"))
199 # path_cond2d = Path(os.path.dirname(postproc.compare_to_ROXIE), "MQXA_All_NoIron_NoSelfField" +
200 # f"{'' if flag_contraction else '_no_contraction'}" + ".cond2d")
201 if os.path.isfile(path_cond2d):
202 conductorPositionsList = RoxieParsers.parseCond2d(path_cond2d)
204 # Collect strands coordinates
205 strands_x = df_ref['X-POS/MM'] / 1e3 if postproc.model_dump().get('compare_to_ROXIE', False) else self.strands['x']
206 strands_y = df_ref['Y-POS/MM'] / 1e3 if postproc.model_dump().get('compare_to_ROXIE', False) else self.strands['y']
208 # Probe physical quantity values from view and region areas
209 physical_quantity_values = {'x': [], 'y': []}
210 cond_areas, current_signs = [], []
211 if postproc.model_dump().get('take_average_conductor_temperature', False):
212 half_turns = {name[:-3]: str(values.vol.numbers[i])
213 for group, values in self.rm.powered.items() for i, name in enumerate(values.vol.names)}
214 self.avg_temperatures = pd.concat([pd.read_csv(os.path.join(self.solution_folder, 'T_avg', 'T_avg_0.txt'),
215 delimiter=r'\s+', header=None, usecols=[0], names=['Time'])] +
216 [pd.read_csv(os.path.join(self.solution_folder, 'T_avg', f'T_avg_{i}.txt'),
217 delimiter=r'\s+', header=None, usecols=[1], names=[ht.upper()]) for i, ht in enumerate(half_turns)], axis=1)
218 self.avg_temperatures['Time'] = pd.read_csv(os.path.join(self.solution_folder, 'T_avg', 'T_avg_0.txt'),
219 delimiter=r'\s+', header=None, usecols=[0], names=['Time'])['Time']
220 self.avg_temperatures = self.avg_temperatures[['Time'] + ['HT' + str(i) for i in range(1, self.strands['ht'][-1] + 1)]]
221 columns_to_format = self.avg_temperatures.columns[1:]
222 self.avg_temperatures[columns_to_format] = self.avg_temperatures[columns_to_format].round(4)
223 self.avg_temperatures.to_csv(os.path.join(self.solution_folder, 'half_turn_temperatures_over_time.csv'), index=False)
224 else:
225 print(f"Info : {self.data.general.magnet_name} - I n t e r p o l a t i n g . . .")
226 print(f"Info : Interpolating {'magnetic flux density' if 'compare_to_ROXIE' in postproc.model_dump() else 'temperature'} ...")
228 # view = gmsh.view.getTags()[0] if len(postproc.variables) == 1 else self.postproc_settings[
229 # (self.postproc_settings['variables'] == self.supported_variables[self.physical_quantity]) &
230 # (self.postproc_settings['volumes'] == 'Omega_p')].index[0]
232 try: view = gmsh.view.getTags()[0]
233 except IndexError:
234 print("Error with post processing")
235 return
237 for i in range(len(strands_x)):
238 is_new_conductor = i == 0 or self.strands['ht'][i] != self.strands['ht'][i - 1]
239 is_new_block = i == 0 or self.strands['block'][i] != self.strands['block'][i - 1]
241 # Print update
242 if is_new_block:
243 perc = round(self.strands['block'][i] / self.strands['block'][-1] * 100)
244 print(f"Info : [{' ' if perc < 10 else ''}{' ' if perc < 100 else ''}{perc}%] Interpolating within block {self.strands['block'][i]}")
246 # Probe
247 probe_data = gmsh.view.probe(view, strands_x[i], strands_y[i], 0)[0]
248 if 'compare_to_ROXIE' in postproc.model_dump():
249 physical_quantity_values['x'].append(probe_data[0])
250 physical_quantity_values['y'].append(probe_data[1])
251 else:
252 physical_quantity_values['x'].append(probe_data[0])
253 physical_quantity_values['y'].append(0)
255 # Plot conductor and block identifiers
256 if postproc.model_dump().get('compare_to_ROXIE', False) and postproc.plot_all != 'false':
257 if is_new_conductor:
258 self.ax.text(df_ref['X-POS/MM'][i] / 1e3, df_ref['Y-POS/MM'][i] / 1e3, str(self.strands['ht'][i]),
259 style='italic', bbox={'facecolor': 'blue', 'pad': 3})
260 if is_new_block:
261 mid_strand_index = round(self.strands['ht'].count(self.strands['ht'][i]) / 2)
262 self.ax.text(df_ref['X-POS/MM'][i + mid_strand_index] / 1e3, df_ref['Y-POS/MM'][i + mid_strand_index] / 1e3,
263 str(self.strands['block'][i]), style='italic', bbox={'facecolor': 'green', 'pad': 3})
265 # Get current sign
266 current_signs.append(self.md.domains.physical_groups.blocks[self.strands['block'][i]].current_sign)
268 # Get region area
269 if is_new_conductor:
270 gmsh.plugin.setNumber("MeshVolume", "Dimension", 2)
271 gmsh.plugin.setNumber("MeshVolume", "PhysicalGroup",
272 self.md.domains.physical_groups.blocks[self.strands['block'][i]].half_turns[self.strands['ht'][i]].tag)
273 gmsh.plugin.run("MeshVolume")
274 cond_areas.append(gmsh.view.getListData(gmsh.view.getTags()[-1])[2][-1][-1])
275 else:
276 cond_areas.append(cond_areas[-1])
278 print(f"Info : {self.data.general.magnet_name} - E n d I n t e r p o l a t i n g")
280 # Assemble map2d content
281 strands_nr = 0
282 content = []
283 for i, ht in enumerate(self.strands['ht']):
284 if i == 0 or ht != self.strands['ht'][i - 1]:
285 strands_nr = self.strands['ht'].count(ht)
286 content.append(self.formatted_content.format(
287 int(self.strands['block'][i]), # bl
288 int(ht), # cond
289 int(i + 1), # no
290 f"{strands_x[i] * 1e3:.4f}", # x
291 f"{strands_y[i] * 1e3:.4f}", # y
292 f"{physical_quantity_values['x'][i]:.4f}", # pq_x
293 f"{physical_quantity_values['y'][i]:.4f}", # pq_y
294 f"{cond_areas[i] / strands_nr * 1e6:.4f}", # area
295 f"{current_signs[i] * self.data.power_supply.I_initial / strands_nr:.2f}", # curr
296 f"{df_ref['FILL FAC.'][i] if postproc.model_dump().get('compare_to_ROXIE', False) else 0:.4f}")) # fill_fac
298 # Save map2d file
299 with open(f"{self.model_file}_{model_file_extension}.map2d", 'w') as file:
300 file.write(self.formatted_headline.format(*self.map2d_headline_names))
301 file.writelines(content)
302 print(f"Info : Map2D file saved.")
303 print(f"WARNING : [Map2D] All strand surface areas are equal within a conductor. Refer to the ROXIE map2d file for actual values")
304 if not postproc.model_dump().get('compare_to_ROXIE', True):
305 print(f"WARNING : [Map2D] No data is available for Filling Factor. Refer to the ROXIE map2d file for correct values")
307 # Compute errors
308 pq = np.linalg.norm(np.column_stack((np.array(physical_quantity_values['x']), np.array(physical_quantity_values['y']))), axis=1)
309 if postproc.model_dump().get('compare_to_ROXIE', False):
310 BB_err = pq - BB_roxie
311 self.postprocess_parameters['overall_error'] = np.mean(abs(BB_err))
312 self.postprocess_parameters['minimum_diff'] = np.min(BB_err)
313 self.postprocess_parameters['maximum_diff'] = np.max(BB_err)
315 if postproc.plot_all != 'false':
316 if postproc.model_dump().get('take_average_conductor_temperature', False):
317 min_value = self.avg_temperatures.iloc[:, 1:].min().min()
318 max_value = self.avg_temperatures.iloc[:, 1:].max().max()
319 ht_polygons = [patches.Polygon(np.array([(self.crns['iHr'][i][0], self.crns['iHr'][i][1]),
320 (self.crns['iLr'][i][0], self.crns['iLr'][i][1]),
321 (self.crns['oLr'][i][0], self.crns['oLr'][i][1]),
322 (self.crns['oHr'][i][0], self.crns['oHr'][i][1])]) * 1e2,
323 closed=True) for i in range(len(self.crns['iHr']))]
324 collection = PatchCollection(ht_polygons)
325 self.ax.add_collection(collection)
326 cmap = plt.get_cmap('plasma')
327 norm = plt.Normalize(vmin=min_value, vmax=max_value)
328 cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=cmap, norm=norm), ax=self.ax)
329 cbar.set_label('Temperature [K]')
330 self.ax.autoscale_view()
331 for i in range(self.avg_temperatures['Time'].size):
332 collection.set_facecolor(cmap(norm(self.avg_temperatures.iloc[i, 1:])))
333 if postproc.plot_all == 'true':
334 plt.pause(0.05)
336 else:
337 self.plotHalfTurnGeometry(postproc.model_dump().get('compare_to_ROXIE', False))
338 map2d_strands = self.ax.scatter(strands_x, strands_y, edgecolor='black', facecolor='black', s=10)
340 scatter3D_pos = self.ax3.scatter3D(strands_x, strands_y, pq, c=pq, cmap='Greens', vmin=0, vmax=10)
342 if postproc.model_dump().get('compare_to_ROXIE', False):
343 if os.path.isfile(path_cond2d):
344 conductors_corners = [condPos.xyCorner for condPos in conductorPositionsList]
345 for corners in conductors_corners:
346 for corner in range(len(corners)):
347 self.ax.scatter(corners[corner][0] / 1e3, corners[corner][1] / 1e3, edgecolor='black', facecolor='black', s=10)
349 self.ax2.scatter3D(strands_x, strands_y, BB_err, c=BB_err, cmap='viridis') # , vmin=-0.2, vmax=0.2)
350 scatter4 = self.ax4.scatter(np.array(strands_x) * 1e2, np.array(strands_y) * 1e2, s=1, c=np.array(BB_err) * 1e3, cmap='viridis')
351 scatter3D_pos_roxie = self.ax3.scatter3D(strands_x, strands_y, BB_roxie, c=BB_roxie, cmap='Reds', vmin=0, vmax=10)
353 cax4 = self.fig4.add_axes((self.ax4.get_position().x1 + 0.02, self.ax4.get_position().y0,
354 0.02, self.ax4.get_position().height))
355 cbar = plt.colorbar(scatter4, cax=cax4)
356 cbar.ax.set_ylabel('Absolute error [mT]', rotation=270)
357 self.ax3.legend([scatter3D_pos, scatter3D_pos_roxie], ['FiQuS', 'ROXIE'], numpoints=1)
358 self.ax.legend([self.fiqus, self.roxie, map2d_strands], ['FiQuS', 'ROXIE'], numpoints=1)
359 self.fig4.savefig(f"{os.path.join(self.solution_folder, self.data.general.magnet_name)}.svg", bbox_inches='tight')
361 if postproc.plot_all == 'true':
362 plt.show()
364 # os.remove(os.path.join(self.solution_folder, 'b_Omega_p.pos'))
365 # os.remove(f"{os.path.join(self.solution_folder, self.data.general.magnet_name)}.pre")
366 # os.remove(f"{os.path.join(os.path.dirname(self.solution_folder), self.data.general.magnet_name)}.msh")
368 def completeMap2d(self):
369 def _quadrant(x, y):
370 if x < 0 and y < 0: return 3
371 elif x < 0: return 2
372 elif y < 0: return 4
373 else: return 1
375 if self.data.magnet.geometry.electromagnetics.symmetry == 'xy':
376 if self.strands['poles'] == 2:
377 mirror_components = {2: [-1, 1], 3: [1, 1], 4: [-1, 1]}
378 elif self.strands['poles'] == 4:
379 mirror_components = {2: [1, -1], 3: [-1, -1], 4: [-1, 1]}
380 elif self.data.magnet.geometry.electromagnetics.symmetry == 'x':
381 if self.strands['poles'] == 2:
382 mirror_components = {3: [-1, 1], 4: [-1, 1]}
383 elif self.strands['poles'] == 4:
384 mirror_components = {3: [-1, 1], 4: [-1, 1]}
385 elif self.data.magnet.geometry.electromagnetics.symmetry == 'y':
386 if self.strands['poles'] == 2:
387 mirror_components = {2: [-1, 1], 3: [-1, 1]}
388 elif self.strands['poles'] == 4:
389 mirror_components = {2: [1, -1], 3: [1, -1]}
390 else:
391 mirror_components = {}
393 print(f"Info : {self.data.general.magnet_name} - M i r r o r i n g . . .")
394 print(f"Info : Mirroring by symmetry ...")
395 blocks_nr = self.strands['block'][-1]
397 with open(f"{self.model_file}_EM.map2d", 'r') as file:
398 file_content = file.read()
399 content_by_row = file_content.split('\n')
400 new_content = [content_by_row[0] + '\n' + content_by_row[1] + '\n']
401 prev_block: int = 0
402 for row in content_by_row[2:-1]:
403 entries = row.split()
404 str_nr, x_coord, y_coord = entries[2], float(entries[3]), float(entries[4])
405 qdr = _quadrant(x_coord, y_coord)
406 if qdr in mirror_components:
407 #found = re.search(f" {str(abs(x_coord))} +{str(abs(y_coord))}", file_content)
408 #BB = [row_ref for row_ref in content_by_row if f" {abs(x_coord):.4f} {abs(y_coord):.4f}" in row_ref][0].split()[5:7]
409 BB = content_by_row[self.strands['mirrored'][str_nr] + 1].split()[5:7]
410 row = row.replace(entries[5], f'{mirror_components[qdr][0] * float(BB[0]):.4f}')
411 row = row.replace(entries[6], f'{mirror_components[qdr][1] * float(BB[1]):.4f}')
412 if int(entries[0]) > prev_block:
413 perc = round(int(entries[0]) / blocks_nr * 100)
414 print("Info : [" + f"{' ' if perc < 10 else ''}" + f"{' ' if perc < 100 else ''}" + f"{perc}" +
415 "%] Mirroring within block" + f"{entries[0]}")
416 prev_block = int(entries[0])
417 new_content.append(row + '\n')
418 with open(f"{self.model_file}_EM.map2d", 'w') as file:
419 file.writelines(new_content)
420 print(f"Info : {self.data.general.magnet_name} - E n d M i r r o r i n g")