fiqus 2025.10.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.
Files changed (27) hide show
  1. fiqus/MainFiQuS.py +24 -17
  2. fiqus/data/DataConductor.py +9 -1
  3. fiqus/data/DataFiQuS.py +3 -3
  4. fiqus/data/DataFiQuSConductorAC_Rutherford.py +569 -0
  5. fiqus/data/DataFiQuSHomogenizedConductor.py +478 -0
  6. fiqus/geom_generators/GeometryConductorAC_Rutherford.py +706 -0
  7. fiqus/geom_generators/GeometryConductorAC_Strand_RutherfordCopy.py +1848 -0
  8. fiqus/geom_generators/GeometryHomogenizedConductor.py +183 -0
  9. fiqus/getdp_runners/RunGetdpConductorAC_Rutherford.py +200 -0
  10. fiqus/getdp_runners/RunGetdpHomogenizedConductor.py +178 -0
  11. fiqus/mains/MainConductorAC_Rutherford.py +76 -0
  12. fiqus/mains/MainHomogenizedConductor.py +112 -0
  13. fiqus/mesh_generators/MeshConductorAC_Rutherford.py +235 -0
  14. fiqus/mesh_generators/MeshConductorAC_Strand_RutherfordCopy.py +718 -0
  15. fiqus/mesh_generators/MeshHomogenizedConductor.py +229 -0
  16. fiqus/post_processors/PostProcessAC_Rutherford.py +142 -0
  17. fiqus/post_processors/PostProcessHomogenizedConductor.py +114 -0
  18. fiqus/pro_templates/combined/ConductorACRutherford_template.pro +1742 -0
  19. fiqus/pro_templates/combined/HomogenizedConductor_template.pro +1663 -0
  20. {fiqus-2025.10.0.dist-info → fiqus-2025.12.0.dist-info}/METADATA +6 -5
  21. {fiqus-2025.10.0.dist-info → fiqus-2025.12.0.dist-info}/RECORD +27 -11
  22. tests/test_geometry_generators.py +20 -0
  23. tests/test_mesh_generators.py +38 -0
  24. tests/test_solvers.py +100 -0
  25. {fiqus-2025.10.0.dist-info → fiqus-2025.12.0.dist-info}/LICENSE.txt +0 -0
  26. {fiqus-2025.10.0.dist-info → fiqus-2025.12.0.dist-info}/WHEEL +0 -0
  27. {fiqus-2025.10.0.dist-info → fiqus-2025.12.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,112 @@
1
+ import os, cProfile
2
+
3
+ from fiqus.geom_generators.GeometryHomogenizedConductor import Geometry
4
+ from fiqus.mesh_generators.MeshHomogenizedConductor import Mesh
5
+ from fiqus.getdp_runners.RunGetdpHomogenizedConductor import Solve
6
+ from fiqus.post_processors.PostProcessHomogenizedConductor import PostProcess
7
+
8
+ class MainHomogenizedConductor:
9
+ def __init__(self, fdm, inputs_folder_path='', outputs_folder_path='', verbose=True):
10
+ """
11
+ Main class for working with simulations for the homogenized conductor model.
12
+ :param fdm: FiQuS data model
13
+ :param inputs_folder_path: full path to folder with input files
14
+ :param verbose: if True, more info is printed in the console
15
+ """
16
+ self.verbose = verbose
17
+ self.fdm = fdm
18
+ self.inputs_folder_path = inputs_folder_path
19
+ self.outputs_folder_path = outputs_folder_path
20
+ self.GetDP_path = None
21
+ self.geom_folder = None
22
+ self.mesh_folder = None
23
+ self.solution_folder = None
24
+ self.model_file = None
25
+ self.model_folder = None
26
+
27
+
28
+ def generate_geometry(self, gui=False):
29
+ """
30
+ Generates the conductor geometry.
31
+ """
32
+ os.chdir(self.geom_folder)
33
+ g = Geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
34
+ g.generate_geometry()
35
+ g.generate_vi_file(gui)
36
+
37
+
38
+ def load_geometry(self, gui: bool = False):
39
+ """
40
+ Loads the previously generated geometry from the .brep file.
41
+ """
42
+ os.chdir(self.geom_folder)
43
+ g = Geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
44
+ g.load_geometry()
45
+ # self.model_file = g.model_file
46
+
47
+ def pre_process(self, gui=False):
48
+ pass
49
+
50
+ def mesh(self, gui: bool = False):
51
+ """
52
+ Generates the mesh for the geometry.
53
+ """
54
+ os.chdir(self.mesh_folder)
55
+
56
+ m = Mesh(fdm=self.fdm, verbose=self.verbose)
57
+ m.generate_mesh()
58
+ m.generate_cuts()
59
+ m.generate_regions_file()
60
+ m.save_mesh(gui)
61
+
62
+ return {"test": 0}
63
+
64
+ def load_mesh(self, gui=False):
65
+ """
66
+ Loads the previously generated mesh from the MSH file.
67
+ """
68
+ os.chdir(self.mesh_folder)
69
+ m = Mesh(fdm=self.fdm, verbose=self.verbose)
70
+ m.load_mesh(gui)
71
+
72
+ # self.model_file = m.mesh_file
73
+
74
+ def solve_and_postprocess_getdp(self, gui: bool = False):
75
+ """
76
+ Assembles the .pro-file from the template, then runs the simulation and the post-processing steps using GetDP.
77
+ """
78
+ os.chdir(self.solution_folder)
79
+
80
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
81
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
82
+ s.read_ro_parameters(inputs_folder_path=self.inputs_folder_path)
83
+ s.assemble_pro()
84
+ s.run_getdp(solve = True, postOperation = True, gui = gui)
85
+ s.cleanup()
86
+
87
+ def post_process_getdp(self, gui: bool = False):
88
+ """
89
+ Runs the post-processing steps trough GetDP.
90
+ """
91
+ os.chdir(self.solution_folder)
92
+
93
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
94
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
95
+ s.read_ro_parameters(inputs_folder_path=self.inputs_folder_path)
96
+ s.assemble_pro()
97
+ s.run_getdp(solve = False, postOperation = True, gui = gui)
98
+
99
+ def post_process_python(self, gui: bool = False):
100
+ """
101
+ Runs the post-processing steps in the python PostProcess class.
102
+ """
103
+
104
+ postProc = PostProcess(self.fdm, self.solution_folder)
105
+ postProc.show()
106
+
107
+ return {'test': 0}
108
+
109
+ def batch_post_process_python(self, gui: bool = False):
110
+ pass
111
+
112
+
@@ -0,0 +1,235 @@
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
+
11
+ import gmsh
12
+ import numpy as np
13
+
14
+ from fiqus.data import RegionsModelFiQuS
15
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
16
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
17
+
18
+ from fiqus.geom_generators.GeometryConductorAC_Rutherford import RutherfordCable
19
+ from fiqus.mesh_generators.MeshConductorAC_Strand_RutherfordCopy import Mesh
20
+ from abc import ABC, abstractmethod
21
+
22
+ occ = gmsh.model.occ
23
+
24
+ class CableMesh(Mesh):
25
+ def __init__(self, fdm, verbose=True):
26
+ super().__init__(fdm, verbose)
27
+ self.geometry_class : RutherfordCable = None # Class from geometry generation step, used to store all information about tags corresponding to everything... To be changed later (probably)
28
+
29
+ def load_geometry(self, geom_folder):
30
+ """ Generating the geometry file also saves the geometry class as a .pkl file. This geometry class can be loaded to reference the different parts of the geometry for meshing."""
31
+ geom_save_file = os.path.join(geom_folder, f'{self.magnet_name}.pkl')
32
+
33
+ with open(geom_save_file, "rb") as geom_save_file: # Unnecessary to return geom instead of setting self.geometry_class
34
+ geom = pickle.load(geom_save_file)
35
+ return geom
36
+
37
+ def generate_mesh(self, geom_folder):
38
+ self.geometry_class : RutherfordCable = self.load_geometry(geom_folder)
39
+ self.geometry_class.update_tags()
40
+ self.geometry_class.add_physical_groups()
41
+
42
+ # Mesh size field for strands:
43
+ strand_field = gmsh.model.mesh.field.add("Constant")
44
+ gmsh.model.mesh.field.setNumbers(strand_field, "SurfacesList", [strand.surface_tag for strand in self.geometry_class.strands])
45
+ gmsh.model.mesh.field.setNumber(strand_field, "VIn", self.cacdm.mesh.strand_mesh_size_ratio * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.diameter)
46
+
47
+ # Mesh size field for coating:
48
+ coating_field = gmsh.model.mesh.field.add("Constant")
49
+ gmsh.model.mesh.field.setNumbers(coating_field, "SurfacesList", [self.geometry_class.coating.surface_tag])
50
+ gmsh.model.mesh.field.setNumber(coating_field, "VIn", self.cacdm.mesh.coating_mesh_size_ratio * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.diameter)
51
+
52
+ # Mesh size field for air:
53
+ air_field = gmsh.model.mesh.field.add("Constant")
54
+ gmsh.model.mesh.field.setNumbers(air_field, "SurfacesList", [self.geometry_class.air[0].surface_tag])
55
+ gmsh.model.mesh.field.setNumber(air_field, "VIn", self.cacdm.mesh.air_boundary_mesh_size_ratio * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.diameter)
56
+
57
+ # Mesh size field for excitation coils:
58
+ coil_field = gmsh.model.mesh.field.add("Constant")
59
+ gmsh.model.mesh.field.setNumbers(coil_field, "SurfacesList", [coil.surface_tag for coil in self.geometry_class.excitation_coils])
60
+ gmsh.model.mesh.field.setNumber(coil_field, "VIn", 2*self.cacdm.mesh.coating_mesh_size_ratio * self.fdm.conductors[self.cacdm.solve.conductor_name].strand.diameter)
61
+
62
+ total_meshSize_field = gmsh.model.mesh.field.add("Min")
63
+ gmsh.model.mesh.field.setNumbers(total_meshSize_field, "FieldsList", [strand_field, coating_field, air_field, coil_field])
64
+
65
+ gmsh.model.mesh.field.setAsBackgroundMesh(total_meshSize_field)
66
+
67
+ gmsh.option.setNumber("Mesh.MeshSizeFactor", self.cacdm.mesh.scaling_global)
68
+ gmsh.model.mesh.generate(2)
69
+
70
+ def generate_cuts(self):
71
+ """
72
+ Computes the cuts for imposing global quantities (massive or stranded).
73
+ """
74
+ # We need:
75
+ # 1) one cut for the coating,
76
+ # 2) one cut per strand, which should be directly at the interface between the strand and the coating matrix,
77
+ # 3) one cut per excitation coil region.
78
+
79
+ # 1) Compute cut for the coating region and oriente it based on the surface orientation
80
+ cable_outer_boundary = self.geometry_class.coating.physical_boundary_tag
81
+ gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[cable_outer_boundary],dims=[1])
82
+ air = self.geometry_class.air[0].physical_surface_tag
83
+ coils = [coil.physical_surface_tag for coil in self.geometry_class.excitation_coils]
84
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[air]+coils,dims=[1])
85
+ cuts = gmsh.model.mesh.computeHomology()
86
+ gmsh.model.mesh.clearHomologyRequests()
87
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", str(cuts[0][1]))
88
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", str(cuts[1][1]))
89
+ gmsh.plugin.run("HomologyPostProcessing")
90
+ self.geometry_class.coating.cut_tag = cuts[1][1] + 1
91
+
92
+ # 2) Compute cuts and get the oriented surface of each strand
93
+ # Cuts are computed one by one for each strand.
94
+ # iMPORTANT: the relative cohomology is computed.
95
+ # We consider the boundaries of each strand, relative to all the other strand boundaries and inner air regions
96
+ # so that we ensure to keep the support of the co-chains in interfaces between strands and coating matrix,
97
+ # rather than between strands or adjacent with inner air regions.
98
+ # NB: cuts could also be defined one by one, possibly going through other strands and inner air regions. (But it does not seem to work correctly now.)
99
+ for strand in self.geometry_class.strands:
100
+ gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[strand.physical_surface_tag], subdomainTags=[strand.physical_boundary_tag], dims=[2])
101
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[strand.physical_boundary_tag], subdomainTags=[other_strand.physical_boundary_tag for other_strand in self.geometry_class.strands if other_strand != strand]+[air], dims=[1])
102
+ cuts = gmsh.model.mesh.computeHomology()
103
+ gmsh.model.mesh.clearHomologyRequests()
104
+
105
+ homology = cuts[1::2] # The homology results represent the surfaces of the strands
106
+ cuts = cuts[::2] # The (randomly oriented) cuts on the boundary of the strands
107
+
108
+ # Extract the tags from the homology and cuts
109
+ homology = [h[1] for h in homology]
110
+ cuts = [c[1] for c in cuts]
111
+
112
+ # Format the homology and cuts for the plugin
113
+ homology_formatted = ', '.join(map(str, homology))
114
+ cuts_formatted = ', '.join(map(str, cuts))
115
+
116
+ # Create an identity matrix for the transformation matrix
117
+ N = len(self.geometry_class.strands)
118
+ identity = '; '.join(', '.join('1' if i == j else '0' for j in range(N)) for i in range(N))
119
+
120
+ # Next we get the boundaries of the oriented surfaces, which will also be oriented correctly
121
+ # The result of this operation is the oriented boundaries of the strands
122
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", homology_formatted)
123
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", "")
124
+ gmsh.plugin.setString("HomologyPostProcessing", "TransformationMatrix", identity)
125
+ gmsh.plugin.setNumber("HomologyPostProcessing", "ApplyBoundaryOperatorToResults", 1)
126
+ gmsh.plugin.run("HomologyPostProcessing")
127
+
128
+ homology_oriented = [homology[-1] + i + 1 for i in range(N)]
129
+ homology_oriented_formatted = ', '.join(map(str, homology_oriented))
130
+
131
+ # Finally we get the cuts for each strand as some linear combination of the cuts, which allows inducing currents in each strand separately
132
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", homology_oriented_formatted)
133
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", cuts_formatted)
134
+ gmsh.plugin.setNumber("HomologyPostProcessing", "ApplyBoundaryOperatorToResults", 0)
135
+ gmsh.plugin.run("HomologyPostProcessing")
136
+
137
+ for i, strand in enumerate(self.geometry_class.strands):
138
+ # print(f"Strand {strand.physical_surface_name} cut tag: {homology_oriented[-1] + i + 1}")
139
+ strand.cut_tag = int(homology_oriented[-1]) + i + 1
140
+
141
+ # 3) Compute cuts for excitation coils (will be modelled as stranded conductors)
142
+ strands = [strand.physical_surface_tag for strand in self.geometry_class.strands]
143
+ coating = self.geometry_class.coating.physical_surface_tag
144
+ for coil in self.geometry_class.excitation_coils:
145
+ coil_outer_boundary = coil.physical_boundary_tag
146
+ gmsh.model.mesh.addHomologyRequest("Homology", domainTags=[coil_outer_boundary],dims=[1])
147
+ other_coils = [other_coil.physical_surface_tag for other_coil in self.geometry_class.excitation_coils if other_coil != coil]
148
+ gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[air]+other_coils+strands+[coating],dims=[1])
149
+ cuts = gmsh.model.mesh.computeHomology()
150
+ gmsh.model.mesh.clearHomologyRequests()
151
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains2", str(cuts[0][1]))
152
+ gmsh.plugin.setString("HomologyPostProcessing", "PhysicalGroupsOfOperatedChains", str(cuts[1][1]))
153
+ gmsh.plugin.run("HomologyPostProcessing")
154
+ coil.cut_tag = cuts[1][1] + 1
155
+
156
+ def generate_regions_file(self):
157
+ """
158
+ Generates the .regions file for the GetDP solver.
159
+ """
160
+ regions_model = RegionsModel()
161
+
162
+ """ -- Initialize data -- """
163
+ regions_model.powered["Strands"] = RegionsModelFiQuS.Powered()
164
+ regions_model.powered["Strands"].vol.numbers = []
165
+ regions_model.powered["Strands"].vol.names = []
166
+ regions_model.powered["Strands"].surf.numbers = []
167
+ regions_model.powered["Strands"].surf.names = []
168
+ regions_model.powered["Strands"].cochain.numbers = []
169
+ regions_model.powered["Strands"].cochain.names = []
170
+ regions_model.powered["Strands"].curve.names = [] # Stores physical points at filament boundary (to fix phi=0)
171
+ regions_model.powered["Strands"].curve.numbers = [] # Stores physical points at filament boundary (to fix phi=0)
172
+
173
+ regions_model.powered["Coating"] = RegionsModelFiQuS.Powered()
174
+ regions_model.powered["Coating"].vol.numbers = [self.geometry_class.coating.physical_surface_tag]
175
+ regions_model.powered["Coating"].vol.names = [self.geometry_class.coating.physical_surface_name]
176
+ regions_model.powered["Coating"].surf_out.numbers = [self.geometry_class.coating.physical_boundary_tag]
177
+ regions_model.powered["Coating"].surf_out.names = [self.geometry_class.coating.physical_boundary_name]
178
+ ## regions_model.powered["Coating"].surf_in.numbers = [ ]
179
+ ## regions_model.powered["Coating"].surf_in.names = []
180
+ regions_model.powered["Coating"].cochain.numbers = [self.geometry_class.coating.cut_tag]
181
+ regions_model.powered["Coating"].cochain.names = [f"Cut: Coating"]
182
+ regions_model.powered["Coating"].curve.names = ["EdgePoint: Coating"] # Stores physical points at filament boundary (to fix phi=0)
183
+ regions_model.powered["Coating"].curve.numbers = [self.geometry_class.coating.physical_edge_point_tag] # Stores physical points at filament boundary (to fix phi=0)
184
+
185
+ regions_model.air.point.names = []
186
+ regions_model.air.point.numbers = []
187
+
188
+ regions_model.powered["ExcitationCoils"] = RegionsModelFiQuS.Powered()
189
+ regions_model.powered["ExcitationCoils"].vol.numbers = []
190
+ regions_model.powered["ExcitationCoils"].vol.names = []
191
+ regions_model.powered["ExcitationCoils"].surf.numbers = []
192
+ regions_model.powered["ExcitationCoils"].surf.names = []
193
+ regions_model.powered["ExcitationCoils"].cochain.numbers = []
194
+ regions_model.powered["ExcitationCoils"].cochain.names = []
195
+
196
+ for i, strand in enumerate(self.geometry_class.strands):
197
+ regions_model.powered["Strands"].vol.numbers.append(strand.physical_surface_tag) # Surfaces in powered.vol
198
+ regions_model.powered["Strands"].vol.names.append(strand.physical_surface_name)
199
+
200
+ regions_model.powered["Strands"].surf.numbers.append(strand.physical_boundary_tag) # Boundaries in powered.surf
201
+ regions_model.powered["Strands"].surf.names.append(strand.physical_boundary_name)
202
+
203
+ regions_model.powered["Strands"].cochain.numbers.append(strand.cut_tag)
204
+ regions_model.powered["Strands"].cochain.names.append(f"Cut: Strand {i}")
205
+
206
+ # Add physical point at Strand boundary to fix phi=0
207
+ regions_model.powered["Strands"].curve.names.append(f"EdgePoint: Strand {i}")
208
+ regions_model.powered["Strands"].curve.numbers.append(strand.physical_edge_point_tag)
209
+
210
+
211
+ regions_model.air.vol.number = self.geometry_class.air[0].physical_surface_tag
212
+ regions_model.air.vol.name = self.geometry_class.air[0].physical_surface_name
213
+
214
+ regions_model.air.surf.number = self.geometry_class.air[0].physical_boundary_tag
215
+ regions_model.air.surf.name = self.geometry_class.air[0].physical_boundary_name
216
+
217
+ for air_region in self.geometry_class.air[1:]:
218
+ regions_model.air.point.names.append(air_region.physical_boundary_name)
219
+ regions_model.air.point.numbers.append(air_region.physical_boundary_tag)
220
+
221
+ for i, coil in enumerate(self.geometry_class.excitation_coils):
222
+ regions_model.powered["ExcitationCoils"].vol.numbers.append(coil.physical_surface_tag) # Surfaces in powered.vol
223
+ regions_model.powered["ExcitationCoils"].vol.names.append(coil.physical_surface_name)
224
+
225
+ regions_model.powered["ExcitationCoils"].surf.numbers.append(coil.physical_boundary_tag) # Boundaries in powered.surf
226
+ regions_model.powered["ExcitationCoils"].surf.names.append(coil.physical_boundary_name)
227
+
228
+ regions_model.powered["ExcitationCoils"].cochain.numbers.append(coil.cut_tag)
229
+ regions_model.powered["ExcitationCoils"].cochain.names.append(f"Cut: Coil {i}")
230
+
231
+ # Add physical point at matrix boundary to fix phi=0
232
+ ## regions_model.air.point.names = ["Point at matrix boundary"]
233
+ ## regions_model.air.point.numbers = [self.geometry_class.matrix[-1].physicalEdgePointTag]
234
+
235
+ FilesAndFolders.write_data_to_yaml(self.regions_file, regions_model.model_dump())