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.
- fiqus/MainFiQuS.py +9 -0
- fiqus/data/DataConductor.py +112 -3
- fiqus/data/DataFiQuS.py +4 -3
- fiqus/data/DataFiQuSConductorAC_CC.py +345 -0
- fiqus/data/DataFiQuSConductorAC_Rutherford.py +569 -0
- fiqus/data/DataFiQuSConductorAC_Strand.py +3 -3
- fiqus/data/DataFiQuSHomogenizedConductor.py +478 -0
- fiqus/geom_generators/GeometryConductorAC_CC.py +1906 -0
- fiqus/geom_generators/GeometryConductorAC_Rutherford.py +706 -0
- fiqus/geom_generators/GeometryConductorAC_Strand_RutherfordCopy.py +1848 -0
- fiqus/geom_generators/GeometryHomogenizedConductor.py +183 -0
- fiqus/getdp_runners/RunGetdpConductorAC_CC.py +123 -0
- fiqus/getdp_runners/RunGetdpConductorAC_Rutherford.py +200 -0
- fiqus/getdp_runners/RunGetdpHomogenizedConductor.py +178 -0
- fiqus/mains/MainConductorAC_CC.py +148 -0
- fiqus/mains/MainConductorAC_Rutherford.py +76 -0
- fiqus/mains/MainHomogenizedConductor.py +112 -0
- fiqus/mesh_generators/MeshConductorAC_CC.py +1305 -0
- fiqus/mesh_generators/MeshConductorAC_Rutherford.py +235 -0
- fiqus/mesh_generators/MeshConductorAC_Strand_RutherfordCopy.py +718 -0
- fiqus/mesh_generators/MeshHomogenizedConductor.py +229 -0
- fiqus/post_processors/PostProcessAC_CC.py +65 -0
- fiqus/post_processors/PostProcessAC_Rutherford.py +142 -0
- fiqus/post_processors/PostProcessHomogenizedConductor.py +114 -0
- fiqus/pro_templates/combined/CAC_CC_template.pro +542 -0
- fiqus/pro_templates/combined/CAC_Rutherford_template.pro +1742 -0
- fiqus/pro_templates/combined/HomogenizedConductor_template.pro +1663 -0
- {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/METADATA +9 -12
- {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/RECORD +36 -13
- tests/test_geometry_generators.py +40 -0
- tests/test_mesh_generators.py +76 -0
- tests/test_solvers.py +137 -0
- /fiqus/pro_templates/combined/{ConductorAC_template.pro → CAC_Strand_template.pro} +0 -0
- {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/LICENSE.txt +0 -0
- {fiqus-2025.11.0.dist-info → fiqus-2026.1.0.dist-info}/WHEEL +0 -0
- {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")
|