fiqus 2025.11.0__py3-none-any.whl → 2025.12.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 +6 -0
- fiqus/data/DataConductor.py +9 -1
- fiqus/data/DataFiQuS.py +3 -3
- fiqus/data/DataFiQuSConductorAC_Rutherford.py +569 -0
- fiqus/data/DataFiQuSHomogenizedConductor.py +478 -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_Rutherford.py +200 -0
- fiqus/getdp_runners/RunGetdpHomogenizedConductor.py +178 -0
- fiqus/mains/MainConductorAC_Rutherford.py +76 -0
- fiqus/mains/MainHomogenizedConductor.py +112 -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_Rutherford.py +142 -0
- fiqus/post_processors/PostProcessHomogenizedConductor.py +114 -0
- fiqus/pro_templates/combined/ConductorACRutherford_template.pro +1742 -0
- fiqus/pro_templates/combined/HomogenizedConductor_template.pro +1663 -0
- {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/METADATA +6 -5
- {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/RECORD +27 -11
- tests/test_geometry_generators.py +20 -0
- tests/test_mesh_generators.py +38 -0
- tests/test_solvers.py +100 -0
- {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/LICENSE.txt +0 -0
- {fiqus-2025.11.0.dist-info → fiqus-2025.12.0.dist-info}/WHEEL +0 -0
- {fiqus-2025.11.0.dist-info → fiqus-2025.12.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,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")
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import os, re, subprocess, logging, timeit
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import gmsh
|
|
4
|
+
|
|
5
|
+
from fiqus.data.DataFiQuSConductor import Conductor, SolutionParameters
|
|
6
|
+
from fiqus.data.RegionsModelFiQuS import RegionsModel
|
|
7
|
+
from fiqus.utils.Utils import GmshUtils, FilesAndFolders
|
|
8
|
+
|
|
9
|
+
from fiqus.pro_assemblers.ProAssembler import ASS_PRO
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger('FiQuS')
|
|
12
|
+
|
|
13
|
+
class Solve:
|
|
14
|
+
def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
|
|
15
|
+
self.fdm = fdm
|
|
16
|
+
self.GetDP_path = GetDP_path
|
|
17
|
+
|
|
18
|
+
self.solution_folder = os.path.join(os.getcwd())
|
|
19
|
+
self.geometry_folder = geometry_folder
|
|
20
|
+
self.mesh_folder = mesh_folder
|
|
21
|
+
|
|
22
|
+
self.mesh_file = os.path.join(self.mesh_folder, f"{self.fdm.general.magnet_name}.msh")
|
|
23
|
+
self.pro_file = os.path.join(self.solution_folder, f"{self.fdm.general.magnet_name}.pro")
|
|
24
|
+
self.regions_file = os.path.join(mesh_folder, f"{self.fdm.general.magnet_name}.regions")
|
|
25
|
+
|
|
26
|
+
self.verbose = verbose
|
|
27
|
+
self.gu = GmshUtils(self.solution_folder, self.verbose)
|
|
28
|
+
self.gu.initialize(verbosity_Gmsh=fdm.run.verbosity_Gmsh)
|
|
29
|
+
|
|
30
|
+
self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.fdm.general.magnet_name))
|
|
31
|
+
self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
|
|
32
|
+
self.material_properties_model = None
|
|
33
|
+
|
|
34
|
+
self.ed = {} # excitation dictionary
|
|
35
|
+
self.rohm = {} # rohm parameter dictionary
|
|
36
|
+
self.rohf = {} # rohf parameter dictionary
|
|
37
|
+
|
|
38
|
+
gmsh.option.setNumber("General.Terminal", verbose)
|
|
39
|
+
|
|
40
|
+
def read_ro_parameters(self, inputs_folder_path):
|
|
41
|
+
"""
|
|
42
|
+
Function for reading the CSV files containing the reduced order parameters for ROHF and ROHM.
|
|
43
|
+
The function expects CSV files with an descriptive first header row (i.e. 'alphas,kappas,taus') and following rows with comma seperated parameter values for each cell starting from cell 1.
|
|
44
|
+
|
|
45
|
+
:param inputs_folder_path: The full path to the folder with input files
|
|
46
|
+
"""
|
|
47
|
+
class reduced_order_params:
|
|
48
|
+
" This structure is used to access the parameter dictionaries inside the pro template with a convenient syntax 'mp.rohf' or 'mp.rohm'."
|
|
49
|
+
def __init__(self, rohf_dict=None, rohm_dict=None):
|
|
50
|
+
self.rohf = rohf_dict
|
|
51
|
+
self.rohm = rohm_dict
|
|
52
|
+
|
|
53
|
+
if self.fdm.magnet.solve.rohm.enable and self.fdm.magnet.solve.rohm.parameter_csv_file:
|
|
54
|
+
parameter_file = os.path.join(inputs_folder_path, self.fdm.magnet.solve.rohm.parameter_csv_file)
|
|
55
|
+
df = pd.read_csv(parameter_file, delimiter=',', engine='python')
|
|
56
|
+
df.index += 1
|
|
57
|
+
logger.info(f'Using ROHM parameters from file: {parameter_file}:')
|
|
58
|
+
logger.info(df)
|
|
59
|
+
self.rohm = df.to_dict(orient='dict')
|
|
60
|
+
|
|
61
|
+
if self.fdm.magnet.solve.rohf.enable and self.fdm.magnet.solve.rohf.parameter_csv_file:
|
|
62
|
+
parameter_file = os.path.join(inputs_folder_path, self.fdm.magnet.solve.rohf.parameter_csv_file)
|
|
63
|
+
df = pd.read_csv(parameter_file, delimiter=',', engine='python')
|
|
64
|
+
df.index += 1
|
|
65
|
+
logger.info(f'Using ROHF parameters from file: {parameter_file}:')
|
|
66
|
+
logger.info(df)
|
|
67
|
+
self.rohf = df.to_dict(orient='dict')
|
|
68
|
+
|
|
69
|
+
# For the moment the reduced order parameters are treated as material properties mp structure.
|
|
70
|
+
self.material_properties_model = reduced_order_params(rohf_dict=self.rohf, rohm_dict=self.rohm)
|
|
71
|
+
|
|
72
|
+
def read_excitation(self, inputs_folder_path):
|
|
73
|
+
"""
|
|
74
|
+
Function for reading a CSV file for the 'from_file' excitation case.
|
|
75
|
+
|
|
76
|
+
:param inputs_folder_path: The full path to the folder with input files.
|
|
77
|
+
:type inputs_folder_path: str
|
|
78
|
+
"""
|
|
79
|
+
if self.fdm.magnet.solve.source_parameters.source_type == 'piecewise' and self.fdm.magnet.solve.source_parameters.piecewise.source_csv_file:
|
|
80
|
+
input_file = os.path.join(inputs_folder_path, self.fdm.magnet.solve.source_parameters.piecewise.source_csv_file)
|
|
81
|
+
logger.info(f'Using excitation from file: {input_file}')
|
|
82
|
+
df = pd.read_csv(input_file, delimiter=',', engine='python')
|
|
83
|
+
excitation_time = df['time'].to_numpy(dtype='float').tolist()
|
|
84
|
+
self.ed['time'] = excitation_time
|
|
85
|
+
excitation_value = df['value'].to_numpy(dtype='float').tolist()
|
|
86
|
+
self.ed['value'] = excitation_value
|
|
87
|
+
|
|
88
|
+
def assemble_pro(self):
|
|
89
|
+
logger.info("Assembling .pro file")
|
|
90
|
+
self.ass_pro.assemble_combined_pro(template = self.fdm.magnet.solve.pro_template, rm = self.regions_model, dm = self.fdm, ed=self.ed, mp=self.material_properties_model)
|
|
91
|
+
|
|
92
|
+
def run_getdp(self, solve = True, postOperation = True, gui = False):
|
|
93
|
+
|
|
94
|
+
command = ["-v2", "-verbose", "3", "-mat_mumps_icntl_14","100"]
|
|
95
|
+
if solve:
|
|
96
|
+
command += ["-solve", "MagDyn"]
|
|
97
|
+
command += ["-pos", "MagDyn"]
|
|
98
|
+
|
|
99
|
+
if self.fdm.magnet.solve.general_parameters.noOfMPITasks:
|
|
100
|
+
mpi_prefix = ["mpiexec", "-np", str(self.fdm.magnet.solve.general_parameters.noOfMPITasks)]
|
|
101
|
+
else:
|
|
102
|
+
mpi_prefix = []
|
|
103
|
+
|
|
104
|
+
startTime = timeit.default_timer()
|
|
105
|
+
|
|
106
|
+
getdpProcess = subprocess.Popen(mpi_prefix + [self.GetDP_path, self.pro_file, "-msh", self.mesh_file] + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
107
|
+
|
|
108
|
+
with getdpProcess.stdout:
|
|
109
|
+
for line in iter(getdpProcess.stdout.readline, b""):
|
|
110
|
+
line = line.decode("utf-8").rstrip()
|
|
111
|
+
line = line.split("\r")[-1]
|
|
112
|
+
if not "Test" in line:
|
|
113
|
+
if line.startswith("Info"):
|
|
114
|
+
parsedLine = re.sub(r"Info\s+:\s+", "", line)
|
|
115
|
+
logger.info(parsedLine)
|
|
116
|
+
elif line.startswith("Warning"):
|
|
117
|
+
parsedLine = re.sub(r"Warning\s+:\s+", "", line)
|
|
118
|
+
logger.warning(parsedLine)
|
|
119
|
+
elif line.startswith("Error"):
|
|
120
|
+
parsedLine = re.sub(r"Error\s+:\s+", "", line)
|
|
121
|
+
logger.error(parsedLine)
|
|
122
|
+
logger.error("Solving HomogenizedConductor failed.")
|
|
123
|
+
# raise Exception(parsedLine)
|
|
124
|
+
elif re.match("##", line):
|
|
125
|
+
logger.critical(line)
|
|
126
|
+
else:
|
|
127
|
+
logger.info(line)
|
|
128
|
+
|
|
129
|
+
simulation_time = timeit.default_timer()-startTime
|
|
130
|
+
# Save simulation time:
|
|
131
|
+
if solve:
|
|
132
|
+
logger.info(f"Solving HomogenizedConductor has finished in {round(simulation_time, 3)} seconds.")
|
|
133
|
+
with open(os.path.join(self.solution_folder, 'txt_files', 'simulation_time.txt'), 'w') as file:
|
|
134
|
+
file.write(str(simulation_time))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if gui and ((postOperation and not solve) or (solve and postOperation and self.fdm.magnet.postproc.generate_pos_files)):
|
|
138
|
+
# gmsh.option.setNumber("Geometry.Volumes", 1)
|
|
139
|
+
# gmsh.option.setNumber("Geometry.Surfaces", 1)
|
|
140
|
+
# gmsh.option.setNumber("Geometry.Curves", 1)
|
|
141
|
+
# gmsh.option.setNumber("Geometry.Points", 0)
|
|
142
|
+
posFiles = [
|
|
143
|
+
fileName
|
|
144
|
+
for fileName in os.listdir(self.solution_folder)
|
|
145
|
+
if fileName.endswith(".pos")
|
|
146
|
+
]
|
|
147
|
+
for posFile in reversed(posFiles):
|
|
148
|
+
gmsh.open(os.path.join(self.solution_folder, posFile))
|
|
149
|
+
self.gu.launch_interactive_GUI()
|
|
150
|
+
else:
|
|
151
|
+
if gmsh.isInitialized():
|
|
152
|
+
gmsh.clear()
|
|
153
|
+
gmsh.finalize()
|
|
154
|
+
|
|
155
|
+
def cleanup(self):
|
|
156
|
+
"""
|
|
157
|
+
This funtion is used to remove .msh, .pre and .res files from the solution folder, as they may be large and not needed.
|
|
158
|
+
"""
|
|
159
|
+
magnet_name = self.fdm.general.magnet_name
|
|
160
|
+
cleanup = self.fdm.magnet.postproc.cleanup
|
|
161
|
+
|
|
162
|
+
if cleanup.remove_res_file:
|
|
163
|
+
res_file_path = os.path.join(self.solution_folder, f"{magnet_name}.res")
|
|
164
|
+
if os.path.exists(res_file_path):
|
|
165
|
+
os.remove(res_file_path)
|
|
166
|
+
logger.info(f"Removed {magnet_name}.res")
|
|
167
|
+
|
|
168
|
+
if cleanup.remove_pre_file:
|
|
169
|
+
pre_file_path = os.path.join(self.solution_folder, f"{magnet_name}.pre")
|
|
170
|
+
if os.path.exists(pre_file_path):
|
|
171
|
+
os.remove(pre_file_path)
|
|
172
|
+
logger.info(f"Removed {magnet_name}.pre")
|
|
173
|
+
|
|
174
|
+
if cleanup.remove_msh_file:
|
|
175
|
+
msh_file_path = os.path.join(self.mesh_folder, f"{magnet_name}.msh")
|
|
176
|
+
if os.path.exists(msh_file_path):
|
|
177
|
+
os.remove(msh_file_path)
|
|
178
|
+
logger.info(f"Removed {magnet_name}.msh")
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from fiqus.geom_generators.GeometryConductorAC_Rutherford import Geometry
|
|
5
|
+
from fiqus.mesh_generators.MeshConductorAC_Rutherford import CableMesh
|
|
6
|
+
from fiqus.getdp_runners.RunGetdpConductorAC_Rutherford import Solve
|
|
7
|
+
from fiqus.post_processors.PostProcessAC_Rutherford import PostProcess
|
|
8
|
+
from fiqus.plotters.PlotPythonConductorAC import PlotPythonConductorAC
|
|
9
|
+
|
|
10
|
+
from fiqus.mains.MainConductorAC_Strand import MainConductorAC_Strand
|
|
11
|
+
|
|
12
|
+
class MainConductorAC_Rutherford(MainConductorAC_Strand):
|
|
13
|
+
|
|
14
|
+
def generate_geometry(self, gui=False):
|
|
15
|
+
"""
|
|
16
|
+
Generates the cable geometry.
|
|
17
|
+
"""
|
|
18
|
+
os.chdir(self.geom_folder)
|
|
19
|
+
g = Geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
|
|
20
|
+
g.generate_cable_geometry(gui)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def mesh(self, gui: bool = False):
|
|
25
|
+
"""
|
|
26
|
+
Generates the mesh for the cable geometry.
|
|
27
|
+
"""
|
|
28
|
+
os.chdir(self.mesh_folder)
|
|
29
|
+
|
|
30
|
+
m = CableMesh(fdm=self.fdm, verbose=self.verbose)
|
|
31
|
+
m.generate_mesh(self.geom_folder)
|
|
32
|
+
m.generate_cuts()
|
|
33
|
+
m.generate_regions_file()
|
|
34
|
+
m.save_mesh(gui)
|
|
35
|
+
|
|
36
|
+
return {"test": 0}
|
|
37
|
+
|
|
38
|
+
def solve_and_postprocess_getdp(self, gui: bool = False):
|
|
39
|
+
"""
|
|
40
|
+
Assembles the .pro-file from the template, then runs the simulation and the post-processing steps using GetDP.
|
|
41
|
+
"""
|
|
42
|
+
os.chdir(self.solution_folder)
|
|
43
|
+
|
|
44
|
+
# Checks if the strand type in the conductor data model is 'Homogenized', other types are not allowed (e.g., round) to avoid confusion as inputs in them won't be used
|
|
45
|
+
if self.fdm.conductors[self.fdm.magnet.solve.conductor_name].strand.type != 'Homogenized':
|
|
46
|
+
raise Exception(f"The strand type must be 'Homogenized' in the conductors section of the input .yaml file, any other type is not allowed.")
|
|
47
|
+
|
|
48
|
+
s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
|
|
49
|
+
s.read_excitation(inputs_folder_path=self.inputs_folder_path)
|
|
50
|
+
# s.get_solution_parameters_from_yaml(inputs_folder_path=self.inputs_folder_path)
|
|
51
|
+
s.assemble_pro()
|
|
52
|
+
s.run_getdp(solve = True, postOperation = True, gui = gui)
|
|
53
|
+
s.cleanup()
|
|
54
|
+
|
|
55
|
+
def post_process_getdp(self, gui: bool = False):
|
|
56
|
+
"""
|
|
57
|
+
Runs the post-processing steps trough GetDP.
|
|
58
|
+
"""
|
|
59
|
+
os.chdir(self.solution_folder)
|
|
60
|
+
|
|
61
|
+
s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
|
|
62
|
+
s.read_excitation(inputs_folder_path=self.inputs_folder_path)
|
|
63
|
+
# s.get_solution_parameters_from_yaml(inputs_folder_path=self.inputs_folder_path)
|
|
64
|
+
s.assemble_pro()
|
|
65
|
+
s.run_getdp(solve = False, postOperation = True, gui = gui)
|
|
66
|
+
|
|
67
|
+
def post_process_python(self, gui: bool = False):
|
|
68
|
+
"""
|
|
69
|
+
Runs the post-processing steps in the python PostProccess class.
|
|
70
|
+
"""
|
|
71
|
+
os.chdir(self.solution_folder)
|
|
72
|
+
|
|
73
|
+
p = PostProcess(self.solution_folder)
|
|
74
|
+
p.show()
|
|
75
|
+
|
|
76
|
+
return {'test': 0}
|