fiqus 2026.1.0__py3-none-any.whl → 2026.1.2__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 (42) hide show
  1. fiqus/MainFiQuS.py +1 -8
  2. fiqus/data/DataConductor.py +4 -8
  3. fiqus/data/DataFiQuSMultipole.py +358 -167
  4. fiqus/data/DataModelCommon.py +30 -15
  5. fiqus/data/DataMultipole.py +33 -10
  6. fiqus/data/DataWindingsCCT.py +37 -37
  7. fiqus/data/RegionsModelFiQuS.py +1 -1
  8. fiqus/geom_generators/GeometryMultipole.py +751 -54
  9. fiqus/getdp_runners/RunGetdpMultipole.py +181 -31
  10. fiqus/mains/MainMultipole.py +109 -17
  11. fiqus/mesh_generators/MeshCCT.py +209 -209
  12. fiqus/mesh_generators/MeshMultipole.py +938 -263
  13. fiqus/parsers/ParserCOND.py +2 -1
  14. fiqus/parsers/ParserDAT.py +16 -16
  15. fiqus/parsers/ParserGetDPOnSection.py +212 -212
  16. fiqus/parsers/ParserGetDPTimeTable.py +134 -134
  17. fiqus/parsers/ParserMSH.py +53 -53
  18. fiqus/parsers/ParserRES.py +142 -142
  19. fiqus/plotters/PlotPythonCCT.py +133 -133
  20. fiqus/plotters/PlotPythonMultipole.py +18 -18
  21. fiqus/post_processors/PostProcessMultipole.py +16 -6
  22. fiqus/pre_processors/PreProcessCCT.py +175 -175
  23. fiqus/pro_assemblers/ProAssembler.py +3 -3
  24. fiqus/pro_material_functions/ironBHcurves.pro +246 -246
  25. fiqus/pro_templates/combined/CC_Module.pro +1213 -0
  26. fiqus/pro_templates/combined/ConductorAC_template.pro +1025 -0
  27. fiqus/pro_templates/combined/Multipole_template.pro +2738 -1338
  28. fiqus/pro_templates/combined/TSA_materials.pro +102 -2
  29. fiqus/pro_templates/combined/materials.pro +54 -3
  30. fiqus/utils/Utils.py +18 -25
  31. fiqus/utils/update_data_settings.py +1 -1
  32. {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/METADATA +64 -68
  33. {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/RECORD +42 -40
  34. {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/WHEEL +1 -1
  35. tests/test_geometry_generators.py +29 -32
  36. tests/test_mesh_generators.py +35 -34
  37. tests/test_solvers.py +32 -31
  38. tests/utils/fiqus_test_classes.py +396 -147
  39. tests/utils/generate_reference_files_ConductorAC.py +57 -57
  40. tests/utils/helpers.py +76 -1
  41. {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/LICENSE.txt +0 -0
  42. {fiqus-2026.1.0.dist-info → fiqus-2026.1.2.dist-info}/top_level.txt +0 -0
@@ -639,7 +639,8 @@ class ParserCOND:
639
639
  :rtype: dict
640
640
  """
641
641
  return {int(k): v for k, v in x.items()} # change dict keys from strings to integers
642
- return jsonKeys2int(json.load(open(json_file_path)))
642
+ with open(json_file_path) as f:
643
+ return jsonKeys2int(json.load(f))
643
644
 
644
645
  @staticmethod
645
646
  def merge_conductor_dicts(cond_dict_list):
@@ -1,16 +1,16 @@
1
- import pandas as pd
2
-
3
-
4
- class ParserDAT:
5
-
6
- def __init__(self, dat_file_path):
7
- """
8
- Read dat file and returns its content as object attribute .pqv (postprocessed quantity value) that is a float
9
- :param dat_file_path: Full path to .pos file, including file name and extension.
10
- :return: nothing, keeps attribute pqv (postprocessed quantity value)
11
- """
12
- pqn = 'pqn' # postprocessed quantity name
13
- delimiter = ' '
14
- columns = ['NaN', pqn]
15
- df = pd.read_csv(dat_file_path, delimiter=delimiter, header=None, engine='python', names=columns, skipinitialspace=True)
16
- self.pqv = float(df[pqn][0])
1
+ import pandas as pd
2
+
3
+
4
+ class ParserDAT:
5
+
6
+ def __init__(self, dat_file_path):
7
+ """
8
+ Read dat file and returns its content as object attribute .pqv (postprocessed quantity value) that is a float
9
+ :param dat_file_path: Full path to .pos file, including file name and extension.
10
+ :return: nothing, keeps attribute pqv (postprocessed quantity value)
11
+ """
12
+ pqn = 'pqn' # postprocessed quantity name
13
+ delimiter = ' '
14
+ columns = ['NaN', pqn]
15
+ df = pd.read_csv(dat_file_path, delimiter=delimiter, header=None, engine='python', names=columns, skipinitialspace=True)
16
+ self.pqv = float(df[pqn][0])
@@ -1,212 +1,212 @@
1
- import numpy as np
2
-
3
- import re
4
- import math
5
- from typing import Literal
6
-
7
-
8
- class ParserGetDPOnSection:
9
- """
10
- This class parses GetDP's TimeTable format output files.
11
- """
12
-
13
- def __init__(self, filePath, data_type: Literal["scalar", "vector"], depth):
14
- self.time_values = []
15
- self.data_type = data_type
16
- self.depth = depth
17
-
18
- if self.depth not in [0, 1]:
19
- raise NotImplementedError("Only depth = 0 and depth = 1 is implemented.")
20
-
21
- # Check GMSH documentation for object types:
22
- if self.depth == 0:
23
- if self.data_type == "scalar":
24
- lineName = "SP" # scalar point
25
- elif self.data_type == "vector":
26
- lineName = "VP" # vector point
27
- elif self.depth == 1:
28
- if self.data_type == "scalar":
29
- lineName = "ST"
30
- elif self.data_type == "vector":
31
- lineName = "VT"
32
-
33
- # Parse data:
34
- with open(filePath) as file:
35
- data = file.read()
36
-
37
- time_values_line = re.search(r"TIME\{(.*)\}", data)[0]
38
- time_values = re.findall(r"TIME\{(.*)\}", time_values_line)
39
- self.time_values = [
40
- float(time_value) for time_value in time_values[0].split(",")
41
- ]
42
- data = data.replace(time_values_line, "")
43
-
44
- points = re.findall(lineName + r"\((.*)\){.*\..*}", data)
45
- points = [point.split(",") for point in points]
46
- self.points = np.array(points, dtype=float)
47
-
48
- if self.depth == 1:
49
- length = np.shape(self.points)[1]
50
- step = int(length / 3)
51
- self.points = (
52
- self.points[:, 0:step]
53
- + self.points[:, step : 2 * step]
54
- + self.points[:, 2 * step : 3 * step]
55
- ) / 3
56
-
57
- values = re.findall(lineName + r"\(.*\){(.*\..*)}", data)
58
- values = [value.split(",") for value in values]
59
- self.values = np.array(values, dtype=float)
60
-
61
- if self.depth == 1:
62
- length = np.shape(self.values)[1]
63
- step = int(length / 3)
64
- self.values = (
65
- self.values[:, 0:step]
66
- + self.values[:, step : 2 * step]
67
- + self.values[:, 2 * step : 3 * step]
68
- ) / 3
69
-
70
- # Somehow, even with depth 0, we get duplicate magnitudes for different points.
71
- # We need to remove them for better plotting:
72
- _, unique_indices = np.unique(
73
- self.values[:, np.shape(self.values)[1] // 2], return_index=True
74
- )
75
- self.values = self.values[unique_indices, :]
76
- self.points = self.points[unique_indices, :]
77
-
78
- def get_values_at_time(self, time):
79
- """
80
- Returns the values at the specified time.
81
- """
82
- if self.points.shape[1] != 2:
83
- raise ValueError(
84
- "Use project_values_on_a_plane() before calling get_values_at_time()!"
85
- )
86
-
87
- for index, time_value in enumerate(self.time_values):
88
- if math.isclose(float(time_value), float(time), abs_tol=1e-10):
89
- return self.values[:, index]
90
-
91
- def get_values_at_time_step(self, time_step):
92
- """
93
- Returns the values at the specified time step.
94
- """
95
- if self.points.shape[1] != 2:
96
- raise ValueError(
97
- "Use project_values_on_a_plane() before calling get_values_at_time()!"
98
- )
99
-
100
- if self.data_type == "scalar":
101
- return self.values[:, time_step]
102
- elif self.data_type == "vector":
103
- return self.values[:, time_step * 2 : time_step * 2 + 2]
104
-
105
- def project_values_on_a_plane(self, plane_normal, plane_x_axis_unit_vector):
106
- """ """
107
-
108
- class unitVector:
109
- def __init__(self, u, v, w) -> None:
110
- length = math.sqrt(u**2 + v**2 + w**2)
111
- self.u = u / length
112
- self.v = v / length
113
- self.w = w / length
114
-
115
- def rotate(self, theta, withRespectTo):
116
- # Rotate with respect to the withRespectTo vector by theta degrees:
117
- # https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
118
- a = withRespectTo.u
119
- b = withRespectTo.v
120
- c = withRespectTo.w
121
-
122
- rotationMatrix = np.array(
123
- [
124
- [
125
- math.cos(theta) + a**2 * (1 - math.cos(theta)),
126
- a * b * (1 - math.cos(theta)) - c * math.sin(theta),
127
- a * c * (1 - math.cos(theta)) + b * math.sin(theta),
128
- ],
129
- [
130
- b * a * (1 - math.cos(theta)) + c * math.sin(theta),
131
- math.cos(theta) + b**2 * (1 - math.cos(theta)),
132
- b * c * (1 - math.cos(theta)) - a * math.sin(theta),
133
- ],
134
- [
135
- c * a * (1 - math.cos(theta)) - b * math.sin(theta),
136
- c * b * (1 - math.cos(theta)) + a * math.sin(theta),
137
- math.cos(theta) + c**2 * (1 - math.cos(theta)),
138
- ],
139
- ]
140
- )
141
- vector = np.array([[self.u], [self.v], [self.w]])
142
- rotatedVector = rotationMatrix @ vector
143
- return unitVector(
144
- rotatedVector[0][0],
145
- rotatedVector[1][0],
146
- rotatedVector[2][0],
147
- )
148
-
149
- def __pow__(self, otherUnitVector):
150
- # Cross product:
151
- u = self.v * otherUnitVector.w - self.w * otherUnitVector.v
152
- v = self.w * otherUnitVector.u - self.u * otherUnitVector.w
153
- w = self.u * otherUnitVector.v - self.v * otherUnitVector.u
154
- return unitVector(u, v, w)
155
-
156
- def __mul__(self, otherUnitVector) -> float:
157
- # Dot product:
158
- return (
159
- self.u * otherUnitVector.u
160
- + self.v * otherUnitVector.v
161
- + self.w * otherUnitVector.w
162
- )
163
-
164
- if len(plane_normal) != 3:
165
- raise ValueError(
166
- "planeNormal for magneticFieldOnCutPlane must be a list of"
167
- " three numbers!"
168
- )
169
-
170
- if len(plane_x_axis_unit_vector) != 3:
171
- raise ValueError(
172
- "planeXAxis for magneticFieldOnCutPlane must be a list of"
173
- " three numbers!"
174
- )
175
-
176
- plane_normal = unitVector(plane_normal[0], plane_normal[1], plane_normal[2])
177
- plane_x_axis = unitVector(
178
- plane_x_axis_unit_vector[0],
179
- plane_x_axis_unit_vector[1],
180
- plane_x_axis_unit_vector[2],
181
- )
182
-
183
- # Rotate perperndicular vector with respect to the plane's normal vector
184
- # by 90 degrees to find the second perpendicular vector:
185
- plane_y_axis = plane_x_axis.rotate(math.pi / 2, plane_normal)
186
-
187
- # Build the transformation matrix to change from the global coordinate
188
- # system to the plane's coordinate system:
189
- transformationMatrix = np.array(
190
- [
191
- [plane_x_axis.u, plane_x_axis.v, plane_x_axis.w],
192
- [plane_y_axis.u, plane_y_axis.v, plane_y_axis.w],
193
- [plane_normal.u, plane_normal.v, plane_normal.w],
194
- ]
195
- )
196
- points = self.points.transpose()
197
- new_points = transformationMatrix @ points
198
- new_points = new_points.transpose()
199
- self.points = new_points[:, 0:2]
200
-
201
- if self.data_type == "vector":
202
- reshaped_values = self.values
203
- reshaped_values = reshaped_values.reshape(
204
- (len(self.values) * len(self.time_values), 3)
205
- )
206
- reshaped_values = reshaped_values.transpose()
207
- new_values = transformationMatrix @ reshaped_values
208
- new_values = new_values.transpose()
209
- new_values = new_values[:, 0:2]
210
- self.values = new_values.reshape(
211
- np.shape(self.values)[0], int(np.shape(self.values)[1] / 3 * 2)
212
- )
1
+ import numpy as np
2
+
3
+ import re
4
+ import math
5
+ from typing import Literal
6
+
7
+
8
+ class ParserGetDPOnSection:
9
+ """
10
+ This class parses GetDP's TimeTable format output files.
11
+ """
12
+
13
+ def __init__(self, filePath, data_type: Literal["scalar", "vector"], depth):
14
+ self.time_values = []
15
+ self.data_type = data_type
16
+ self.depth = depth
17
+
18
+ if self.depth not in [0, 1]:
19
+ raise NotImplementedError("Only depth = 0 and depth = 1 is implemented.")
20
+
21
+ # Check GMSH documentation for object types:
22
+ if self.depth == 0:
23
+ if self.data_type == "scalar":
24
+ lineName = "SP" # scalar point
25
+ elif self.data_type == "vector":
26
+ lineName = "VP" # vector point
27
+ elif self.depth == 1:
28
+ if self.data_type == "scalar":
29
+ lineName = "ST"
30
+ elif self.data_type == "vector":
31
+ lineName = "VT"
32
+
33
+ # Parse data:
34
+ with open(filePath) as file:
35
+ data = file.read()
36
+
37
+ time_values_line = re.search(r"TIME\{(.*)\}", data)[0]
38
+ time_values = re.findall(r"TIME\{(.*)\}", time_values_line)
39
+ self.time_values = [
40
+ float(time_value) for time_value in time_values[0].split(",")
41
+ ]
42
+ data = data.replace(time_values_line, "")
43
+
44
+ points = re.findall(lineName + r"\((.*)\){.*\..*}", data)
45
+ points = [point.split(",") for point in points]
46
+ self.points = np.array(points, dtype=float)
47
+
48
+ if self.depth == 1:
49
+ length = np.shape(self.points)[1]
50
+ step = int(length / 3)
51
+ self.points = (
52
+ self.points[:, 0:step]
53
+ + self.points[:, step : 2 * step]
54
+ + self.points[:, 2 * step : 3 * step]
55
+ ) / 3
56
+
57
+ values = re.findall(lineName + r"\(.*\){(.*\..*)}", data)
58
+ values = [value.split(",") for value in values]
59
+ self.values = np.array(values, dtype=float)
60
+
61
+ if self.depth == 1:
62
+ length = np.shape(self.values)[1]
63
+ step = int(length / 3)
64
+ self.values = (
65
+ self.values[:, 0:step]
66
+ + self.values[:, step : 2 * step]
67
+ + self.values[:, 2 * step : 3 * step]
68
+ ) / 3
69
+
70
+ # Somehow, even with depth 0, we get duplicate magnitudes for different points.
71
+ # We need to remove them for better plotting:
72
+ _, unique_indices = np.unique(
73
+ self.values[:, np.shape(self.values)[1] // 2], return_index=True
74
+ )
75
+ self.values = self.values[unique_indices, :]
76
+ self.points = self.points[unique_indices, :]
77
+
78
+ def get_values_at_time(self, time):
79
+ """
80
+ Returns the values at the specified time.
81
+ """
82
+ if self.points.shape[1] != 2:
83
+ raise ValueError(
84
+ "Use project_values_on_a_plane() before calling get_values_at_time()!"
85
+ )
86
+
87
+ for index, time_value in enumerate(self.time_values):
88
+ if math.isclose(float(time_value), float(time), abs_tol=1e-10):
89
+ return self.values[:, index]
90
+
91
+ def get_values_at_time_step(self, time_step):
92
+ """
93
+ Returns the values at the specified time step.
94
+ """
95
+ if self.points.shape[1] != 2:
96
+ raise ValueError(
97
+ "Use project_values_on_a_plane() before calling get_values_at_time()!"
98
+ )
99
+
100
+ if self.data_type == "scalar":
101
+ return self.values[:, time_step]
102
+ elif self.data_type == "vector":
103
+ return self.values[:, time_step * 2 : time_step * 2 + 2]
104
+
105
+ def project_values_on_a_plane(self, plane_normal, plane_x_axis_unit_vector):
106
+ """ """
107
+
108
+ class unitVector:
109
+ def __init__(self, u, v, w) -> None:
110
+ length = math.sqrt(u**2 + v**2 + w**2)
111
+ self.u = u / length
112
+ self.v = v / length
113
+ self.w = w / length
114
+
115
+ def rotate(self, theta, withRespectTo):
116
+ # Rotate with respect to the withRespectTo vector by theta degrees:
117
+ # https://en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
118
+ a = withRespectTo.u
119
+ b = withRespectTo.v
120
+ c = withRespectTo.w
121
+
122
+ rotationMatrix = np.array(
123
+ [
124
+ [
125
+ math.cos(theta) + a**2 * (1 - math.cos(theta)),
126
+ a * b * (1 - math.cos(theta)) - c * math.sin(theta),
127
+ a * c * (1 - math.cos(theta)) + b * math.sin(theta),
128
+ ],
129
+ [
130
+ b * a * (1 - math.cos(theta)) + c * math.sin(theta),
131
+ math.cos(theta) + b**2 * (1 - math.cos(theta)),
132
+ b * c * (1 - math.cos(theta)) - a * math.sin(theta),
133
+ ],
134
+ [
135
+ c * a * (1 - math.cos(theta)) - b * math.sin(theta),
136
+ c * b * (1 - math.cos(theta)) + a * math.sin(theta),
137
+ math.cos(theta) + c**2 * (1 - math.cos(theta)),
138
+ ],
139
+ ]
140
+ )
141
+ vector = np.array([[self.u], [self.v], [self.w]])
142
+ rotatedVector = rotationMatrix @ vector
143
+ return unitVector(
144
+ rotatedVector[0][0],
145
+ rotatedVector[1][0],
146
+ rotatedVector[2][0],
147
+ )
148
+
149
+ def __pow__(self, otherUnitVector):
150
+ # Cross product:
151
+ u = self.v * otherUnitVector.w - self.w * otherUnitVector.v
152
+ v = self.w * otherUnitVector.u - self.u * otherUnitVector.w
153
+ w = self.u * otherUnitVector.v - self.v * otherUnitVector.u
154
+ return unitVector(u, v, w)
155
+
156
+ def __mul__(self, otherUnitVector) -> float:
157
+ # Dot product:
158
+ return (
159
+ self.u * otherUnitVector.u
160
+ + self.v * otherUnitVector.v
161
+ + self.w * otherUnitVector.w
162
+ )
163
+
164
+ if len(plane_normal) != 3:
165
+ raise ValueError(
166
+ "planeNormal for magneticFieldOnCutPlane must be a list of"
167
+ " three numbers!"
168
+ )
169
+
170
+ if len(plane_x_axis_unit_vector) != 3:
171
+ raise ValueError(
172
+ "planeXAxis for magneticFieldOnCutPlane must be a list of"
173
+ " three numbers!"
174
+ )
175
+
176
+ plane_normal = unitVector(plane_normal[0], plane_normal[1], plane_normal[2])
177
+ plane_x_axis = unitVector(
178
+ plane_x_axis_unit_vector[0],
179
+ plane_x_axis_unit_vector[1],
180
+ plane_x_axis_unit_vector[2],
181
+ )
182
+
183
+ # Rotate perperndicular vector with respect to the plane's normal vector
184
+ # by 90 degrees to find the second perpendicular vector:
185
+ plane_y_axis = plane_x_axis.rotate(math.pi / 2, plane_normal)
186
+
187
+ # Build the transformation matrix to change from the global coordinate
188
+ # system to the plane's coordinate system:
189
+ transformationMatrix = np.array(
190
+ [
191
+ [plane_x_axis.u, plane_x_axis.v, plane_x_axis.w],
192
+ [plane_y_axis.u, plane_y_axis.v, plane_y_axis.w],
193
+ [plane_normal.u, plane_normal.v, plane_normal.w],
194
+ ]
195
+ )
196
+ points = self.points.transpose()
197
+ new_points = transformationMatrix @ points
198
+ new_points = new_points.transpose()
199
+ self.points = new_points[:, 0:2]
200
+
201
+ if self.data_type == "vector":
202
+ reshaped_values = self.values
203
+ reshaped_values = reshaped_values.reshape(
204
+ (len(self.values) * len(self.time_values), 3)
205
+ )
206
+ reshaped_values = reshaped_values.transpose()
207
+ new_values = transformationMatrix @ reshaped_values
208
+ new_values = new_values.transpose()
209
+ new_values = new_values[:, 0:2]
210
+ self.values = new_values.reshape(
211
+ np.shape(self.values)[0], int(np.shape(self.values)[1] / 3 * 2)
212
+ )