fiqus 2025.11.0__py3-none-any.whl → 2026.1.0__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 (36) hide show
  1. fiqus/MainFiQuS.py +9 -0
  2. fiqus/data/DataConductor.py +112 -3
  3. fiqus/data/DataFiQuS.py +4 -3
  4. fiqus/data/DataFiQuSConductorAC_CC.py +345 -0
  5. fiqus/data/DataFiQuSConductorAC_Rutherford.py +569 -0
  6. fiqus/data/DataFiQuSConductorAC_Strand.py +3 -3
  7. fiqus/data/DataFiQuSHomogenizedConductor.py +478 -0
  8. fiqus/geom_generators/GeometryConductorAC_CC.py +1906 -0
  9. fiqus/geom_generators/GeometryConductorAC_Rutherford.py +706 -0
  10. fiqus/geom_generators/GeometryConductorAC_Strand_RutherfordCopy.py +1848 -0
  11. fiqus/geom_generators/GeometryHomogenizedConductor.py +183 -0
  12. fiqus/getdp_runners/RunGetdpConductorAC_CC.py +123 -0
  13. fiqus/getdp_runners/RunGetdpConductorAC_Rutherford.py +200 -0
  14. fiqus/getdp_runners/RunGetdpHomogenizedConductor.py +178 -0
  15. fiqus/mains/MainConductorAC_CC.py +148 -0
  16. fiqus/mains/MainConductorAC_Rutherford.py +76 -0
  17. fiqus/mains/MainHomogenizedConductor.py +112 -0
  18. fiqus/mesh_generators/MeshConductorAC_CC.py +1305 -0
  19. fiqus/mesh_generators/MeshConductorAC_Rutherford.py +235 -0
  20. fiqus/mesh_generators/MeshConductorAC_Strand_RutherfordCopy.py +718 -0
  21. fiqus/mesh_generators/MeshHomogenizedConductor.py +229 -0
  22. fiqus/post_processors/PostProcessAC_CC.py +65 -0
  23. fiqus/post_processors/PostProcessAC_Rutherford.py +142 -0
  24. fiqus/post_processors/PostProcessHomogenizedConductor.py +114 -0
  25. fiqus/pro_templates/combined/CAC_CC_template.pro +542 -0
  26. fiqus/pro_templates/combined/CAC_Rutherford_template.pro +1742 -0
  27. fiqus/pro_templates/combined/HomogenizedConductor_template.pro +1663 -0
  28. {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/METADATA +9 -12
  29. {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/RECORD +36 -13
  30. tests/test_geometry_generators.py +40 -0
  31. tests/test_mesh_generators.py +76 -0
  32. tests/test_solvers.py +137 -0
  33. /fiqus/pro_templates/combined/{ConductorAC_template.pro → CAC_Strand_template.pro} +0 -0
  34. {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/LICENSE.txt +0 -0
  35. {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/WHEEL +0 -0
  36. {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,183 @@
1
+ import os, json, gmsh, logging
2
+
3
+ from fiqus.utils.Utils import GmshUtils
4
+ from typing import Tuple
5
+
6
+ logger = logging.getLogger('FiQuS')
7
+
8
+ class Rectangle:
9
+ """ A class to represent a rectangular surface in gmsh. """
10
+ def __init__( self, x_center, y_center, z_center, width, height, name) -> None:
11
+ self.name = name
12
+
13
+ self.x_center = x_center
14
+ self.y_center = y_center
15
+ self.z_center = z_center
16
+ self.width = width
17
+ self.height = height
18
+
19
+ self.tag = gmsh.model.occ.addRectangle(x_center-width/2, y_center-height/2, z_center, width, height)
20
+ self.dimTag: Tuple = (2, self.tag)
21
+
22
+
23
+ class Circle:
24
+ """ A class to represent a circle surface in gmsh. """
25
+ def __init__(self, x_center, y_center, z_center, radius, name) -> None:
26
+ self.name = name
27
+
28
+ self.x_center = x_center
29
+ self.y_center = y_center
30
+ self.z_center = z_center
31
+ self.radius = radius
32
+
33
+ self.tag = gmsh.model.occ.addDisk(self.x_center, self.y_center, self.z_center, radius, radius)
34
+ self.dimTag: Tuple = (2, self.tag)
35
+
36
+
37
+ class HomogenizedConductor:
38
+ """
39
+ A class representing the geometry of a 2D cross section containing multiple cables and excitation coils in an isolating medium.
40
+
41
+ :ivar cables: list of surfaces representing the cable cross sections.
42
+ :vartype cables: list[list[Surface]]
43
+ :ivar excitation_coils: list of surfaces representing the excitation coils cross sections.
44
+ :vartype excitation_coils: list[list[Surface]]
45
+ :ivar Air: Surrounding surface representing the air region.
46
+ :vartype Air: Surface
47
+ """
48
+ def __init__(self, fdm) -> None:
49
+ """
50
+ Initializes the HomogenizedConductor object.
51
+ """
52
+ self.fdm = fdm
53
+
54
+ self.cables = self.generate_cables()
55
+ self.excitation_coils = self.generate_excitation_coils()
56
+ self.air = self.generate_air()
57
+
58
+ def generate_cables(self):
59
+ """
60
+ This function generates a list of rectangular surfaces according to the specifications of the cables within the yaml.
61
+
62
+ :return: List of rectangle surfaces
63
+ """
64
+ cables = []
65
+ for idx, cable in enumerate(self.fdm.magnet.geometry.cables_definition):
66
+ cables.append(Rectangle(cable.center_position[0],cable.center_position[1],cable.center_position[2],
67
+ cable.width, cable.height, name='Cable'+str(idx+1)))
68
+ return cables
69
+
70
+ def generate_excitation_coils(self):
71
+ """
72
+ This function generates a list of rectangular surfaces according to the specifications of the excitation coils within the yaml.
73
+
74
+ :return: List of rectangle surfaces
75
+ """
76
+ excitation_coils = []
77
+ for idx, excitation_coil in enumerate(self.fdm.magnet.geometry.excitation_coils):
78
+ excitation_coils.append(Rectangle(excitation_coil.center_position[0],excitation_coil.center_position[1],excitation_coil.center_position[2],
79
+ excitation_coil.width, excitation_coil.height, name='Coil'+str(idx+1)))
80
+ return excitation_coils
81
+
82
+ def generate_air(self):
83
+ """
84
+ This function generates the surrounding air surface by fragmenting a base surface with the previously defined cable surfaces.
85
+
86
+ :raises ValueError: 'circle' is the only defined air surface form
87
+ :return: fragmented air surface
88
+ """
89
+ if self.fdm.magnet.geometry.air_form == 'circle':
90
+ air_pos = self.fdm.magnet.geometry.air.center_position
91
+ air = Circle(air_pos[0],air_pos[1],air_pos[2],
92
+ self.fdm.magnet.geometry.air.radius, name='Air')
93
+ else:
94
+ raise ValueError('Undefined air_form.')
95
+ # fragment air with cables and excitation coils
96
+ gmsh.model.occ.synchronize()
97
+ outDimTags, outDimTagsMap = gmsh.model.occ.fragment([(2, air.tag)], [cable.dimTag for cable in self.cables] + [excitation_coil.dimTag for excitation_coil in self.excitation_coils])
98
+ return air
99
+
100
+ def get_vi_dictionary(self):
101
+ """
102
+ This function dumps the dimTags of all surfaces into one dictionary, which can be used to generate the volume information file.
103
+
104
+ :return: dictionary with volume information
105
+ """
106
+ vi_dictionary = {}
107
+ # add all cables
108
+ for cable in self.cables:
109
+ vi_dictionary.update({str(cable.name):list(cable.dimTag)})
110
+ # add all excitation coils
111
+ for excitation_coil in self.excitation_coils:
112
+ vi_dictionary.update({str(excitation_coil.name):list(excitation_coil.dimTag)})
113
+ # add air
114
+ vi_dictionary.update({str(self.air.name):list(self.air.dimTag)})
115
+ return vi_dictionary
116
+
117
+
118
+ class Geometry:
119
+ def __init__(self, fdm, inputs_folder_path, verbose=True) -> None:
120
+ """
121
+ Initializes the Geometry class.
122
+
123
+ :param fdm: The fiqus data model.
124
+ :type fdm: object
125
+ :param inputs_folder_path: The full path to the folder with input files, i.e., conductor and STEP files.
126
+ :type inputs_folder_path: str
127
+ :param verbose: If True, more information is printed in the Python console. Defaults to True.
128
+ :type verbose: bool, optional
129
+ """
130
+ self.fdm = fdm
131
+ self.inputs_folder = inputs_folder_path
132
+ self.geom_folder = os.path.join(os.getcwd())
133
+ self.geom_file = os.path.join(self.geom_folder, f'{fdm.general.magnet_name}.brep')
134
+ self.vi_file = os.path.join(self.geom_folder, f'{fdm.general.magnet_name}.vi')
135
+ self.verbose = verbose
136
+ # start GMSH
137
+ self.gu = GmshUtils(self.geom_folder, self.verbose)
138
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
139
+ # To see the surfaces in a better way in GUI:
140
+ gmsh.option.setNumber("Geometry.SurfaceType", 2)
141
+
142
+ def generate_geometry(self):
143
+ """
144
+ Generates the geometry of conductors within a surrounding air region as specified in the yaml.
145
+
146
+ :param gui: If True, launches an interactive GUI after generating the geometry. Default is False.
147
+ :type gui: bool, optional
148
+ :return: None
149
+ """
150
+ logger.info("Generating geometry")
151
+
152
+ # 1) Either load the geometry from a yaml file or create the model from scratch
153
+ self.geometry = HomogenizedConductor(fdm=self.fdm)
154
+
155
+ gmsh.model.occ.synchronize()
156
+ logger.info("Writing geometry")
157
+ gmsh.write(self.geom_file) # Write the geometry to a .brep file
158
+
159
+ def load_geometry(self):
160
+ """ Loads geometry from .brep file. """
161
+ logger.info("Loading geometry")
162
+
163
+ gmsh.clear()
164
+ gmsh.model.occ.importShapes(self.geom_file, format="brep")
165
+ gmsh.model.occ.synchronize()
166
+
167
+ def generate_vi_file(self, gui=False):
168
+ """
169
+ Generates volume information file. Volume information file stores dimTags of all
170
+ the stored volumes. Since this model is 2D, those volumes are equivalent to simple surfaces.
171
+ """
172
+
173
+ dimTagsDict = self.geometry.get_vi_dictionary()
174
+ with open(self.vi_file, 'w') as f:
175
+ json.dump(dimTagsDict, f)
176
+
177
+ if gui:
178
+ #self.generate_physical_groups()
179
+ self.gu.launch_interactive_GUI()
180
+ else:
181
+ if gmsh.isInitialized():
182
+ gmsh.clear()
183
+ gmsh.finalize()
@@ -0,0 +1,123 @@
1
+ import timeit
2
+ import logging
3
+ from enum import Enum
4
+ import os
5
+ import subprocess
6
+ import re
7
+ import pandas as pd
8
+
9
+ import gmsh
10
+ import numpy as np
11
+
12
+ #from fiqus.data import RegionsModelFiQuS
13
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
14
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
15
+ # import fiqus.data.DataConductorACGeom as geom
16
+
17
+ from fiqus.pro_assemblers.ProAssembler import ASS_PRO
18
+
19
+ logger = logging.getLogger('FiQuS')
20
+
21
+ class Solve:
22
+ def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
23
+ self.fdm = fdm
24
+ self.cacdm = fdm.magnet
25
+ self.GetDP_path = GetDP_path
26
+ self.solution_folder = os.path.join(os.getcwd())
27
+ self.magnet_name = fdm.general.magnet_name
28
+ self.geometry_folder = geometry_folder
29
+ self.mesh_folder = mesh_folder
30
+ self.mesh_file = os.path.join(self.mesh_folder, f"{self.magnet_name}.msh")
31
+ self.pro_file = os.path.join(self.solution_folder, f"{self.magnet_name}.pro")
32
+ self.regions_file = os.path.join(mesh_folder, f"{self.magnet_name}.regions")
33
+
34
+ self.verbose = verbose
35
+ self.gu = GmshUtils(self.solution_folder, self.verbose)
36
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
37
+
38
+ self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.magnet_name))
39
+ self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
40
+ self.material_properties_model = None
41
+
42
+ self.ed = {} # excitation dictionary
43
+
44
+ gmsh.option.setNumber("General.Terminal", verbose)
45
+
46
+ def assemble_pro(self):
47
+ logger.info("Assembling .pro file")
48
+ self.ass_pro.assemble_combined_pro(template = self.cacdm.solve.pro_template, rm = self.regions_model, dm = self.fdm, ed=self.ed, mp=self.material_properties_model)
49
+
50
+ def read_excitation(self, inputs_folder_path):
51
+ """
52
+ Function for reading a CSV file for the 'piecewise' excitation case.
53
+
54
+ :param inputs_folder_path: The full path to the folder with input files.
55
+ :type inputs_folder_path: str
56
+ """
57
+ if self.cacdm.solve.source_parameters.source_type == 'piecewise' and self.cacdm.solve.source_parameters.piecewise.source_csv_file:
58
+ input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.piecewise.source_csv_file)
59
+ logger.info(f'Using excitation from file: {input_file}')
60
+ df = pd.read_csv(input_file, delimiter=',', engine='python')
61
+ excitation_time = df['time'].to_numpy(dtype='float').tolist()
62
+ self.ed['time'] = excitation_time
63
+ excitation_b = df['b'].to_numpy(dtype='float').tolist()
64
+ self.ed['b'] = excitation_b
65
+ excitation_I = df['I'].to_numpy(dtype='float').tolist()
66
+ self.ed['I'] = excitation_I
67
+
68
+ def run_getdp(self, solve = True, postOperation = True, gui = False):
69
+ command = ["-v2", "-verbose", "3"]
70
+ if solve:
71
+ command += ["-solve", "MagDyn", "-mat_mumps_icntl_14","100"] # icntl for mumps just by precaution
72
+ command += ["-pos", "MagDyn"]
73
+
74
+ logger.info(f"Running GetDP with command: {command}")
75
+ startTime = timeit.default_timer()
76
+
77
+ if self.cacdm.solve.general_parameters.noOfMPITasks:
78
+ mpi_prefix = ["mpiexec", "-np", str(self.cacdm.solve.general_parameters.noOfMPITasks)]
79
+ else:
80
+ mpi_prefix = []
81
+
82
+ getdpProcess = subprocess.Popen(mpi_prefix + [self.GetDP_path] + [self.pro_file] + command + ["-msh"] + [self.mesh_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
83
+
84
+ with getdpProcess.stdout:
85
+ for line in iter(getdpProcess.stdout.readline, b""):
86
+ line = line.decode("utf-8").rstrip()
87
+ line = line.split("\r")[-1]
88
+ if not "Test" in line:
89
+ if line.startswith("Info"):
90
+ parsedLine = re.sub(r"Info\s+:\s+", "", line)
91
+ logger.info(parsedLine)
92
+ elif line.startswith("Warning"):
93
+ parsedLine = re.sub(r"Warning\s+:\s+", "", line)
94
+ logger.warning(parsedLine)
95
+ elif line.startswith("Error"):
96
+ parsedLine = re.sub(r"Error\s+:\s+", "", line)
97
+ logger.error(parsedLine)
98
+ logger.error("Solving CAC failed.")
99
+ # raise Exception(parsedLine)
100
+ elif re.match("##", line):
101
+ logger.critical(line)
102
+ else:
103
+ logger.info(line)
104
+
105
+ simulation_time = timeit.default_timer()-startTime
106
+
107
+ if gui and ((postOperation and not solve) or (solve and postOperation)): # and self.cacdm.postproc.generate_pos_files
108
+ # gmsh.option.setNumber("Geometry.Volumes", 1)
109
+ # gmsh.option.setNumber("Geometry.Surfaces", 1)
110
+ # gmsh.option.setNumber("Geometry.Curves", 1)
111
+ # gmsh.option.setNumber("Geometry.Points", 0)
112
+ posFiles = [
113
+ fileName
114
+ for fileName in os.listdir(self.solution_folder)
115
+ if fileName.endswith(".pos")
116
+ ]
117
+ for posFile in posFiles:
118
+ gmsh.open(os.path.join(self.solution_folder, posFile))
119
+ self.gu.launch_interactive_GUI()
120
+ else:
121
+ if gmsh.isInitialized():
122
+ gmsh.clear()
123
+ gmsh.finalize()
@@ -0,0 +1,200 @@
1
+ import timeit
2
+ import json
3
+ import logging
4
+ import math
5
+ from enum import Enum
6
+ import operator
7
+ import itertools
8
+ import os
9
+ import pickle
10
+ import subprocess
11
+ import re
12
+ import pandas as pd
13
+
14
+ import gmsh
15
+ import numpy as np
16
+
17
+ from fiqus.data import RegionsModelFiQuS
18
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
19
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
20
+ # import fiqus.data.DataConductorACGeom as geom
21
+
22
+ from fiqus.pro_assemblers.ProAssembler import ASS_PRO
23
+
24
+ logger = logging.getLogger('FiQuS')
25
+
26
+ class Solve:
27
+ def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
28
+ self.fdm = fdm
29
+ self.cacdm = fdm.magnet
30
+ self.GetDP_path = GetDP_path
31
+ self.solution_folder = os.path.join(os.getcwd())
32
+ self.magnet_name = fdm.general.magnet_name
33
+ self.geometry_folder = geometry_folder
34
+ self.mesh_folder = mesh_folder
35
+ self.mesh_file = os.path.join(self.mesh_folder, f"{self.magnet_name}.msh")
36
+ self.pro_file = os.path.join(self.solution_folder, f"{self.magnet_name}.pro")
37
+ self.regions_file = os.path.join(mesh_folder, f"{self.magnet_name}.regions")
38
+
39
+ self.verbose = verbose
40
+ self.gu = GmshUtils(self.solution_folder, self.verbose)
41
+ self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
42
+
43
+ self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.magnet_name))
44
+ self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
45
+ self.material_properties_model = None
46
+
47
+ self.ed = {} # excitation dictionary
48
+
49
+ gmsh.option.setNumber("General.Terminal", verbose)
50
+
51
+ def read_excitation(self, inputs_folder_path):
52
+ """
53
+ Function for reading csv for the 'from_file' excitation case
54
+ :type inputs_folder_path: str
55
+ """
56
+ # if self.cacdm.solve.source_parameters.source_type == 'from_file':
57
+ # input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.source_csv_file)
58
+ # logger.info(f'Getting applied field and transport current from file: {input_file}')
59
+ # df = pd.read_csv(input_file, delimiter=',', engine='python')
60
+ # excitation_time = df['time'].to_numpy(dtype='float').tolist()
61
+ # self.ed['time'] = excitation_time
62
+ # excitation_b = df['b'].to_numpy(dtype='float').tolist()
63
+ # self.ed['b'] = excitation_b
64
+ # excitation_I = df['I'].to_numpy(dtype='float').tolist()
65
+ # self.ed['I'] = excitation_I
66
+
67
+ if self.cacdm.solve.source_parameters.excitation_coils.enable and self.cacdm.solve.source_parameters.excitation_coils.source_csv_file:
68
+ input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.excitation_coils.source_csv_file)
69
+ logger.info(f'Getting excitation coils currents from file: {input_file}')
70
+ df = pd.read_csv(input_file, delimiter=',', engine='python')
71
+
72
+ if( len(df.columns) != len(self.cacdm.geometry.excitation_coils.centers)+1):
73
+ logger.warning('Number of excitation coils in geometry ('+ str(len(self.cacdm.geometry.excitation_coils.centers))+') and input source file ('+str(len(df.columns))+') not compatible')
74
+
75
+ excitation_time = df['time'].to_numpy(dtype='float').tolist()
76
+ self.ed['time'] = excitation_time
77
+ for i in range(1, len(df.columns)):
78
+ Istr = 'I'+str(i)
79
+ excitation_value = df[Istr].to_numpy(dtype='float').tolist()
80
+ self.ed[Istr] = excitation_value
81
+
82
+ # def get_material_properties(self, inputs_folder_path):
83
+ # """
84
+ # Function for reading material properties from the geometry YAML file.
85
+ # This reads the 'solution' section of the YAML file and stores it in the solution folder.
86
+ # This could also be a place to change the material properties in the future.
87
+ # """
88
+ # if self.cacdm.geometry.io_settings.load.load_from_yaml:
89
+ # input_yaml_file = os.path.join(inputs_folder_path, self.cacdm.geometry.io_settings.load.filename)
90
+ # Conductor_dm = FilesAndFolders.read_data_from_yaml(input_yaml_file, geom.Conductor)
91
+ # solution_parameters = Conductor_dm.Solution
92
+ # FilesAndFolders.write_data_to_yaml(os.path.join(self.solution_folder, "MaterialProperties.yaml"), solution_parameters.dict())
93
+ # self.material_properties_model = solution_parameters
94
+
95
+
96
+
97
+
98
+ def assemble_pro(self):
99
+ logger.info("Assembling .pro file")
100
+ self.ass_pro.assemble_combined_pro(template = self.cacdm.solve.pro_template, rm = self.regions_model, dm = self.fdm, ed=self.ed, mp=self.material_properties_model)
101
+
102
+ def run_getdp(self, solve = True, postOperation = True, gui = False):
103
+
104
+ # f = self.cacdm.solve.source_parameters.frequency
105
+ # bmax = self.cacdm.solve.source_parameters.applied_field_amplitude
106
+ # Imax_ratio = self.cacdm.solve.source_parameters.ratio_of_max_imposed_current_amplitude
107
+
108
+ command = ["-v2", "-verbose", "3"]
109
+ if solve:
110
+ # command += ["-solve", "js_to_hs_2"] if not self.cacdm.solve.frequency_domain_solver.enable else ["-solve", "MagDyn_freq"] # for debugging purposes
111
+ command += ["-solve", "MagDyn"] if not self.cacdm.solve.frequency_domain_solver.enable else ["-solve", "MagDyn_freq"]
112
+ # Solve_only seems to always call postproc, here we only save .pos-files if specified in input file
113
+ # if (postOperation and not solve) or (solve and postOperation and self.cacdm.postproc.generate_pos_files):
114
+ # if self.cacdm.solve.formulation_parameters.dynamic_correction:
115
+ # command += " -pos MagDyn MagDyn_dynCorr"
116
+ # else:
117
+ command += ["-pos", "MagDyn"]
118
+
119
+ logger.info(f"Running GetDP with command: {command}")
120
+ startTime = timeit.default_timer()
121
+ # subprocess.run(f"{self.GetDP_path} {self.pro_file} {command} -msh {self.mesh_file}")
122
+
123
+ if self.cacdm.solve.general_parameters.noOfMPITasks:
124
+ mpi_prefix = ["mpiexec", "-np", str(self.cacdm.solve.general_parameters.noOfMPITasks)]
125
+ else:
126
+ mpi_prefix = []
127
+
128
+ getdpProcess = subprocess.Popen(mpi_prefix + [self.GetDP_path] + [self.pro_file] + command + ["-msh"] + [self.mesh_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
129
+
130
+ with getdpProcess.stdout:
131
+ for line in iter(getdpProcess.stdout.readline, b""):
132
+ line = line.decode("utf-8").rstrip()
133
+ line = line.split("\r")[-1]
134
+ if not "Test" in line:
135
+ if line.startswith("Info"):
136
+ parsedLine = re.sub(r"Info\s+:\s+", "", line)
137
+ logger.info(parsedLine)
138
+ elif line.startswith("Warning"):
139
+ parsedLine = re.sub(r"Warning\s+:\s+", "", line)
140
+ logger.warning(parsedLine)
141
+ elif line.startswith("Error"):
142
+ parsedLine = re.sub(r"Error\s+:\s+", "", line)
143
+ logger.error(parsedLine)
144
+ logger.error("Solving CAC failed.")
145
+ # raise Exception(parsedLine)
146
+ elif re.match("##", line):
147
+ logger.critical(line)
148
+ else:
149
+ logger.info(line)
150
+
151
+ simulation_time = timeit.default_timer()-startTime
152
+ # Save simulation time:
153
+ if solve:
154
+ logger.info(f"Solving Rutherford has finished in {round(simulation_time, 3)} seconds.")
155
+ with open(self.solution_folder+f'\\txt_files\\simulation_time.txt', 'w') as file:
156
+ file.write(str(simulation_time))
157
+
158
+
159
+ if gui and ((postOperation and not solve) or (solve and postOperation and self.cacdm.postproc.generate_pos_files)):
160
+ # gmsh.option.setNumber("Geometry.Volumes", 1)
161
+ # gmsh.option.setNumber("Geometry.Surfaces", 1)
162
+ # gmsh.option.setNumber("Geometry.Curves", 1)
163
+ # gmsh.option.setNumber("Geometry.Points", 0)
164
+ posFiles = [
165
+ fileName
166
+ for fileName in os.listdir(self.solution_folder)
167
+ if fileName.endswith(".pos")
168
+ ]
169
+ for posFile in posFiles:
170
+ gmsh.open(os.path.join(self.solution_folder, posFile))
171
+ self.gu.launch_interactive_GUI()
172
+ else:
173
+ if gmsh.isInitialized():
174
+ gmsh.clear()
175
+ gmsh.finalize()
176
+
177
+ def cleanup(self):
178
+ """
179
+ This funtion is used to remove .pre and .res files from the solution folder, as they may be large and not needed.
180
+ """
181
+ magnet_name = self.fdm.general.magnet_name
182
+ cleanup = self.cacdm.postproc.cleanup
183
+
184
+ if cleanup.remove_res_file:
185
+ res_file_path = os.path.join(self.solution_folder, f"{magnet_name}.res")
186
+ if os.path.exists(res_file_path):
187
+ os.remove(res_file_path)
188
+ logger.info(f"Removed {magnet_name}.res")
189
+
190
+ if cleanup.remove_pre_file:
191
+ pre_file_path = os.path.join(self.solution_folder, f"{magnet_name}.pre")
192
+ if os.path.exists(pre_file_path):
193
+ os.remove(pre_file_path)
194
+ logger.info(f"Removed {magnet_name}.pre")
195
+
196
+ if cleanup.remove_msh_file:
197
+ msh_file_path = os.path.join(self.mesh_folder, f"{magnet_name}.msh")
198
+ if os.path.exists(msh_file_path):
199
+ os.remove(msh_file_path)
200
+ logger.info(f"Removed {magnet_name}.msh")