Skip to content

Commit 86017a9

Browse files
authored
Merge pull request #166 from upb-lea/MagNet_Model_Implementation
Implementation of MagNet model in FEMMT
2 parents 9cf423e + 2fc8ee4 commit 86017a9

4 files changed

Lines changed: 162 additions & 1 deletion

File tree

docs/wordlist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ sublist
448448
timemax
449449
NbSetpsPerPeriod
450450
NbSteps
451+
WaveformType
452+
powerloss
451453

452454
# electrostatic
453455
capacitive

femmt/component.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,121 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain,
219219
self.onelab_client = onelab.client(__file__)
220220
self.simulation_name = simulation_name
221221

222+
def calc_hystersis_losses_with_MagNet_model_PB_based_on_reluctance(self, peak_magnetizing_current: float = 1, b_wave: WaveformType = WaveformType.Sine,
223+
custom_b_wave: np.ndarray = None) -> float:
224+
"""
225+
Calculate the hysteresis losses with the MagNet model of Paderborn University based on a reluctance model.
226+
227+
:param peak_magnetizing_current: peak value of the magnetizing current
228+
:type peak_magnetizing_current: peak value of the magnetizing current
229+
:param b_wave: type of the waveform of the magnetic flux density/magnetizing current
230+
:type b_wave: WaveformType
231+
:param custom_b_wave: normalized signal of the magnetic flux density/magnetizing current (normalized -> amplitude of 1)
232+
:type custom_b_wave: np.ndarray
233+
:return: hysteresis losses
234+
:rtype: float
235+
"""
236+
total_reluctance = self.calculate_core_reluctance()[0] + self.air_gaps_reluctance()[0]
237+
center_leg_area = np.pi*(self.core.geometry.core_inner_diameter/2)**2
238+
239+
turns = ff.get_number_of_turns_of_winding(winding_windows=self.winding_windows, windings=self.windings, winding_number=0)
240+
inductance = (turns**2)/total_reluctance
241+
b_field_peak = inductance*peak_magnetizing_current/center_leg_area/turns
242+
243+
# multiply the normalized signal of the magnetic flux density with the peak value of the magnetic flux density
244+
if b_wave is WaveformType.Sine:
245+
b_wave = np.sin(np.linspace(0, 2*np.pi, 1024)) * b_field_peak
246+
elif b_wave is WaveformType.Custom:
247+
b_wave = custom_b_wave * b_field_peak
248+
249+
power_loss = ff.calc_power_loss_from_MagNet_model_PB(material_name=self.core.material.material, b_wave=b_wave, frequency=self.frequency,
250+
temperature=self.core.material.temperature) * self.calculate_core_volume()
251+
return power_loss
252+
253+
def calc_hystersis_losses_with_MagNet_model_PB_based_on_mesh_results(self, b_wave: WaveformType = WaveformType.Sine,
254+
custom_b_wave: np.ndarray = None) -> float:
255+
"""
256+
Calculate the hysteresis losses with the MagNet model of Paderborn University based on FEM-simulation results.
257+
258+
- uses the local magnetic flux density in each mesh cell
259+
- arbitrary waveforms are possible. But FEM simulation assumes a sinusoidal waveform and the peak of the magnetic flux density result of each mesh cell
260+
is used to linearly scale the arbitrary waveform to its max. value
261+
- for each mesh cell, the magnet model is applied
262+
263+
:param b_wave: type of the waveform of the magnetic flux density/magnetizing current
264+
:type b_wave: WaveformType
265+
:param custom_b_wave: normalized signal of the magnetic flux density/magnetizing current (normalized -> amplitude of 1)
266+
:type custom_b_wave: np.ndarray
267+
:return: dict containing the hysteresis losses
268+
"""
269+
flux, volume = self.calc_magnetic_flux_density_based_on_simulation_results()
270+
271+
# multiply the normalized signal of the magnetic flux density with the peak value of the magnetic flux density
272+
if b_wave is WaveformType.Sine:
273+
b_wave = np.array([np.sin(np.linspace(0, 2*np.pi, 1024)) * b for b in flux])
274+
elif b_wave is WaveformType.Custom:
275+
b_wave = np.array([custom_b_wave * b for b in flux])
276+
277+
hyst_losses_density = ff.calc_power_loss_from_MagNet_model_PB(material_name=self.core.material.material, b_wave=b_wave,
278+
frequency=np.array([self.frequency] * b_wave.shape[0]),
279+
temperature=np.array([self.core.material.temperature] * b_wave.shape[0]))
280+
power_loss = float(np.dot(hyst_losses_density, volume))
281+
return power_loss
282+
283+
def calc_magnetic_flux_density_based_on_simulation_results(self) -> tuple[np.ndarray, np.ndarray]:
284+
"""Calculate the magnetic flux density and volume for every mesh cell of the core."""
285+
with open(os.path.join(self.file_data.e_m_fields_folder_path, "Magb.pos"), 'r') as file:
286+
content = file.read() # type of content: str
287+
288+
# find both occurrence of "Magnitude B-Field / T", Elements and Nodes in Magb.pos
289+
indexes_B_Field = np.array([m.start() for m in re.finditer('"Magnitude B-Field / T"', content)])
290+
indexes_Elements = np.array([m.start() for m in re.finditer('Elements', content)])
291+
indexes_Coords = np.array([m.start() for m in re.finditer('Nodes', content)])
292+
# filter magnetic flux density out of Magb.pos(data is between the both occurrences of "Magnitude B-Field / T") and put data into list
293+
data_B_Field = np.array([float(x) for x in content[indexes_B_Field[0]:indexes_B_Field[1]].split()[11:-3]])
294+
# filter elements(ID of triangles and nodes) out of Magb.pos(data is between the both occurrences of "Elements") and put data into list
295+
data_Elements = np.array([int(x) for x in content[indexes_Elements[0]:indexes_Elements[1]].split()[2:-1]])
296+
# filter coordinates of nodes out of Magb.pos(data is between the both occurrences of "Elements") and put data into list
297+
data_Coords = np.array([float(x) for x in content[indexes_Coords[0]:indexes_Coords[1]].split()[2:-1]])
298+
# divide long list into lists for every triangle/mesh-cell
299+
chunks_B_Field = [data_B_Field[x:x + 5] for x in range(0, data_B_Field.shape[0], 5)]
300+
chunks_Elements = np.array([data_Elements[x:x + 8] for x in range(0, data_Elements.shape[0], 8)])
301+
chunks_Coords = np.array([data_Coords[x:x + 4] for x in range(0, data_Coords.shape[0], 4)])
302+
303+
data_triangle = np.concatenate((chunks_Elements, np.array(chunks_B_Field)), axis=1)
304+
# rearrange arrays for data that is needed for calculation of mesh cell volume
305+
Triangle_Core = [[x[0], x[5], x[6], x[7], np.mean(x[10:])] for x in data_triangle if x[3] // 10000 == 12]
306+
# x[0] = ID of mesh cell(triangle), x[5] = ID of first node of triangle, x[5] = ID of second node of triangle, x[5] = ID of third node of triangle,
307+
# np.mean(x[10:]) = avr. of magnetic flux density of all three nodes
308+
309+
xyz = {}
310+
for x in chunks_Coords: # rearrange coordinates of nodes. These are the coordinates from the nodes of the whole mesh.
311+
xyz[str(x[0])] = np.array([x[1], x[2], x[3]]) # key of dict is ID of node
312+
313+
for index, TriangleNode in enumerate(Triangle_Core):
314+
xi = xyz[str(TriangleNode[1:4][0])] # first node of triangle (x1, y1, z1)
315+
xj = xyz[str(TriangleNode[1:4][1])] # second node of triangle (x2, y2, z2)
316+
xk = xyz[str(TriangleNode[1:4][2])] # third node of triangle (x3, y3, z3)
317+
318+
radius = np.mean([xi[0], xj[0], xk[0]]) # radius -> average of x-values np.mean(x1, x2, x3)
319+
320+
a = np.sqrt(np.dot(xi - xj, xi - xj)) # distance between first and second node
321+
b = np.sqrt(np.dot(xi - xk, xi - xk)) # distance between first and third node
322+
c = np.sqrt(np.dot(xj - xk, xj - xk)) # distance between second and third node
323+
324+
# Heron's formula
325+
s = (a + b + c) / 2 # semiperimeter of mesh cell (triangle)
326+
dA = np.sqrt(s * (s - a) * (s - b) * (s - c)) # area of mesh cell
327+
328+
volume = 2 * np.pi * radius * dA
329+
330+
Triangle_Core[index].append(volume)
331+
332+
flux = np.array([x[-2] for x in Triangle_Core])
333+
volume = np.array([x[-1] for x in Triangle_Core])
334+
335+
return flux, volume
336+
222337
def update_mesh_accuracies(self, mesh_accuracy_core: float, mesh_accuracy_window: float,
223338
mesh_accuracy_conductor, mesh_accuracy_air_gaps: float):
224339
"""

femmt/enumerations.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Enumeration for FEMMT."""
22
from enum import IntEnum, Enum
33

4+
45
class Verbosity(IntEnum):
56
"""State of verbosity."""
67

@@ -10,13 +11,15 @@ class Verbosity(IntEnum):
1011
ToConsole = 2 # Outputs to console
1112
ToFile = 3 # Outputs to file
1213

14+
1315
class VisualizationMode(IntEnum):
1416
"""How simulation results are shown in Gmsh."""
1517

1618
Final = 1 # Only final static result
1719
Post = 2 # Animate after simulation
1820
Stream = 3 # Live results during simulation
1921

22+
2023
class WindingTag(IntEnum):
2124
"""Names of windings."""
2225

@@ -62,19 +65,22 @@ class ComponentType(IntEnum):
6265
Transformer = 2
6366
IntegratedTransformer = 3
6467

68+
6569
class SimulationType(IntEnum):
6670
"""Sets the simulation type. The static is just to show the fields."""
6771

6872
FreqDomain = 1
6973
TimeDomain = 2
7074
ElectroStatic = 3
7175

76+
7277
class CoreType(IntEnum):
7378
"""Sets the core type for the whole simulation. Needs to be given to the MagneticComponent on creation."""
7479

7580
Single = 1 # one axisymmetric core
7681
Stacked = 2 # one and a half cores
7782

83+
7884
class AirGapMethod(IntEnum):
7985
"""Sets the method how the air gap position (vertical) is set.
8086
@@ -190,6 +196,7 @@ class ConductorArrangement(IntEnum):
190196
turns of the previous line. First drawn in y-direction then x-direction.
191197
"""
192198

199+
193200
class Align(IntEnum):
194201
"""Specifies the distribution direction for conductors when starting from the center. This can be done for having single windings in vww."""
195202

@@ -202,6 +209,7 @@ class Align(IntEnum):
202209
CenterOnHorizontalAxis = 3
203210
"""Conductors are centered across the middle line of the horizontal axis."""
204211

212+
205213
class ConductorDistribution(IntEnum):
206214
"""Defines specific strategies for placing conductors starting from the peripheral (edges) of the virtual winding window."""
207215

@@ -229,6 +237,7 @@ class ConductorDistribution(IntEnum):
229237
HorizontalLeftward_VerticalDownward = 8
230238
"""Places conductors horizontally leftward first, then moves vertically downward for the next set with consistent direction."""
231239

240+
232241
class FoilVerticalDistribution(IntEnum):
233242
"""Defines specific strategies for placing rectangular foil vertical conductors starting from the peripheral (edges) of the virtual winding window."""
234243

@@ -238,6 +247,7 @@ class FoilVerticalDistribution(IntEnum):
238247
HorizontalLeftward = 2
239248
"""Moves horizontally leftward for the next set with consistent direction."""
240249

250+
241251
class FoilHorizontalDistribution(IntEnum):
242252
"""Defines specific strategies for placing rectangular foil horizontal conductors starting from the peripheral (edges) of the virtual winding window."""
243253

@@ -247,6 +257,7 @@ class FoilHorizontalDistribution(IntEnum):
247257
VerticalDownward = 2
248258
"""Moves vertically downward for the next set with consistent direction."""
249259

260+
250261
class CenterTappedInterleavingType(IntEnum):
251262
"""Contains different interleaving types for the center tapped transformer."""
252263

@@ -310,6 +321,7 @@ class WrapParaType(IntEnum):
310321
The foils will have a given width and thickness. The virtual winding window may not be fully occupied..
311322
"""
312323

324+
313325
class ConductorMaterial(IntEnum):
314326
"""Sets the conductivity of the conductor."""
315327

@@ -346,3 +358,10 @@ class CoreMaterialType(str, Enum):
346358

347359
Linear = "linear"
348360
Imported = "imported"
361+
362+
363+
class WaveformType(str, Enum):
364+
"""Types of waveforms."""
365+
366+
Sine = "sine"
367+
Custom = "custom"

femmt/functions.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@
88
import warnings
99
from scipy.integrate import quad
1010
import logging
11-
1211
# Third parry libraries
1312
import gmsh
1413
from matplotlib import pyplot as plt
1514
import numpy.typing as npt
1615
import numpy as np
16+
from materialdatabase.meta.data_enums import Material
17+
import magnethub as mh
1718
import schemdraw
1819
import schemdraw.elements as elm
1920

21+
2022
# Local libraries
2123
from femmt.constants import *
2224
from femmt.enumerations import ConductorType
@@ -2765,5 +2767,28 @@ def compare_excel_files(femmt_excel_path: str, femm_excel_path: str, comparison_
27652767
worksheet.set_column('A:Z', 35)
27662768

27672769

2770+
def calc_power_loss_from_MagNet_model_PB(material_name: Material, b_wave: np.ndarray, frequency: float, temperature: float) -> float | None:
2771+
"""
2772+
Calculate the power loss density with the help of the trained neural network of the MagNet Challenge 2023.
2773+
2774+
:param material_name: Name of the material
2775+
:type material_name: Material
2776+
:param b_wave: array containing the shape of the magnetic flux density in time domain in T
2777+
:type b_wave: np.ndarray
2778+
:param frequency: value of the frequency in Hz
2779+
:type frequency: float
2780+
:param temperature: value of the temperature in °C
2781+
:type temperature: float
2782+
:return: powerloss in W/m^3
2783+
"""
2784+
if material_name.value in ["3C90", "3C92", "3C94", "3C95", "3E6", "3F4", "77", "78", "79", "ML95S", "T37", "N27", "N30", "N49", "N87"]:
2785+
mdl = mh.loss.LossModel(material=material_name.value, team="paderborn")
2786+
power_loss_density, h_wave = mdl(b_wave, frequency, temperature)
2787+
return power_loss_density
2788+
else:
2789+
logger.warning("Material" + str(material_name.value) + " not supported by MagNet Models.")
2790+
return None
2791+
2792+
27682793
if __name__ == '__main__':
27692794
pass

0 commit comments

Comments
 (0)