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