Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
09bb2a4
example creation for PCB transformer
Jul 21, 2025
7aa5c27
Merge branch 'main' into planar_transformer
Jul 22, 2025
ca49d48
implement the insulation for foil horizontal (should be reviewd)
Jul 22, 2025
bb64b49
Merge branch 'main' into planar_transformer
Jul 22, 2025
dd92ca5
implenting new schems for planar transformer. Implementing the insula…
Aug 7, 2025
b54b06e
implementing interleaved winding for PCB transformer. Only vertical …
Aug 11, 2025
9e4e840
Merge branch 'main' into planar_transformer
Aug 11, 2025
b0d9374
thermal simulation does not work when the windings are in parallel.
Aug 11, 2025
283261d
give a unique physical number when the winding is parallel.
Aug 11, 2025
9803e67
Adding live visualization in time domain
Aug 20, 2025
353e7c2
fix physial number in thermal simulation
Aug 22, 2025
f54c09e
merge the main into branch
Aug 22, 2025
52fbfa4
adapt the PCB files to the latest changes in the main
Aug 22, 2025
a52d55d
edit ignore file
Aug 22, 2025
da03a5a
new visualization mode for time domain simulation
Aug 27, 2025
d200fb0
merge the main into branch
Aug 27, 2025
a5c0461
fix some bugs to the test file
Aug 27, 2025
7ebbcbb
Merge branch 'main' into planar_transformer
Aug 27, 2025
7bc3e0e
add some words to wordlist
Aug 27, 2025
6b07269
add words to wordlist
Aug 27, 2025
9fbda37
add planar_transformer_interleaved into test file
Aug 27, 2025
b588c11
fix pystyle..
Aug 27, 2025
6531f30
fix test for basic_planar_transformer_interleaved
gituser789 Aug 28, 2025
2f2c4cd
set the onelab path in the planar transformer function
Aug 28, 2025
7613b14
fix pycodestyle
Aug 28, 2025
e61c30c
fix some errors that happen because of using refractor
Aug 28, 2025
0637228
pooh fix pycodestyle
Aug 28, 2025
ddf3fac
try time domain simulation for planar transformer
Aug 28, 2025
db2d05f
fix thermal simulation and improve the visualization code in ield_tim…
Aug 29, 2025
5ce2908
update the fixture file of the thermal simulation to the corrected one
Aug 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/wordlist
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,17 @@ xlsxwriter
Thermoset
Polyphenylene

# Time domain Visualization
VisualizationMode
nopopup
ShowScale
PositionY
PositionX
AutoPosition

# Winding scheme
HorizontalRightward

# mdb
mdb
CoreMaterial
Expand Down
157 changes: 150 additions & 7 deletions femmt/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import csv
import fileinput
import os
import sys
import gmsh
import json
import warnings
Expand Down Expand Up @@ -51,7 +52,7 @@ class MagneticComponent:

def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain,
component_type: ComponentType = ComponentType.Inductor, working_directory: str = None,
clean_previous_results: bool = True, onelab_verbosity: Verbosity = 1, is_gui: bool = False,
clean_previous_results: bool = True, onelab_verbosity: Verbosity = 1, visualization_mode: VisualizationMode = 1, is_gui: bool = False,
simulation_name: str | None = None, wwr_enabled=True):
"""
Initialize the magnetic component.
Expand All @@ -63,6 +64,8 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain,
:type component_type: ComponentType
:param working_directory: Sets the working directory
:type working_directory: string
:param visualization_mode: Sets the visualization mode. it is only used in the time domain simulation.
:type visualization_mode: VisualizationMode
:param is_gui: Asks at first startup for onelab-path. Distinction between GUI and command line.
Defaults to 'False' in command-line-mode.
:type is_gui: bool
Expand Down Expand Up @@ -97,6 +100,9 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain,

self.wwr_enabled = wwr_enabled

# visulization
self.visualization_mode = visualization_mode

logger.info(f"\n"
f"Initialized a new Magnetic Component of type {component_type.name}\n"
f"--- --- --- ---")
Expand Down Expand Up @@ -1298,9 +1304,19 @@ def simulate(self):
self.onelab_client.runSubClient("myGetDP", getdp_filepath + " " + solver_freq + " -msh " + \
self.file_data.e_m_mesh_file + " -solve Analysis -v2 " + verbose + to_file_str)
if self.simulation_type == SimulationType.TimeDomain:
# the two commands work but some changes should be done in fields_time.pro
self.onelab_client.runSubClient("myGetDP", getdp_filepath + " " + solver_time + " -msh " + self.file_data.e_m_mesh_file + \
" -solve Analysis -pos Map_local " + verbose + to_file_str)
if self.visualization_mode == VisualizationMode.Stream:
# 1) Start GUI
if not gmsh.isInitialized():
gmsh.initialize()
if '-nopopup' not in sys.argv:
gmsh.fltk.initialize()
gmsh.onelab.run("myGetDP", getdp_filepath + " " + solver_time + " -msh " + self.file_data.e_m_mesh_file + \
" -solve Analysis -pos Map_local " + verbose + to_file_str)
gmsh.fltk.run()
else:
# the two commands work but some changes should be done in fields_time.pro
self.onelab_client.runSubClient("myGetDP", getdp_filepath + " " + solver_time + " -msh " + self.file_data.e_m_mesh_file + \
" -solve Analysis -pos Map_local " + verbose + to_file_str)
# self.onelab_client.runSubClient("myGetDP", getdp_filepath + " " + solver + " -msh " + self.file_data.e_m_mesh_file +
# " -solve Analysis -v2 " + verbose) # freeing solutions
if self.simulation_type == SimulationType.ElectroStatic:
Expand Down Expand Up @@ -1453,7 +1469,6 @@ def time_domain_simulation(self, current_period_vec: list[list[float]], time_per
:param rolling_avg_window_size: how many data points used in each calculation of the average
:param benchmark: Benchmark simulation (stop time). Defaults to False.
:type benchmark: bool

"""
self.check_create_empty_material_log()

Expand All @@ -1480,7 +1495,10 @@ def time_domain_simulation(self, current_period_vec: list[list[float]], time_per
self.calculate_average_files()
self.calculate_and_write_time_domain_log() # TODO: reuse center tapped
if show_fem_simulation_results:
self.visualize()
if self.visualization_mode == VisualizationMode.Final:
self.visualize()
elif self.visualization_mode == VisualizationMode.Post:
self.live_visualization_2d()
if show_rolling_average:
self.get_rolling_average(window_size=rolling_avg_window_size)

Expand All @@ -1496,7 +1514,10 @@ def time_domain_simulation(self, current_period_vec: list[list[float]], time_per
self.calculate_and_write_time_domain_log() # TODO: reuse center tapped

if show_fem_simulation_results:
self.visualize()
if self.visualization_mode == VisualizationMode.Final:
self.visualize()
elif self.visualization_mode == VisualizationMode.Post:
self.live_visualization_2d()
if show_rolling_average:
self.get_rolling_average(window_size=rolling_avg_window_size)

Expand Down Expand Up @@ -3460,6 +3481,15 @@ def write_electro_magnetic_parameter_pro(self):
text_file.write("Flag_Time_Domain = 1;\n")
text_file.write("Flag_Freq_Domain = 0;\n")
text_file.write("Flag_Static = 0;\n")
# Needed for visualization
if self.visualization_mode == VisualizationMode.Stream:
text_file.write("Flag_Stream_Visualization = 1;\n")
else:
text_file.write("Flag_Stream_Visualization = 0;\n")
if self.simulation_type == SimulationType.ElectroStatic:
text_file.write("Flag_Static = 1;\n")
text_file.write("Flag_Freq_Domain = 0;\n")
text_file.write("Flag_Time_Domain = 0;\n")

# Frequency
text_file.write("Freq = %s;\n" % self.frequency)
Expand Down Expand Up @@ -4758,6 +4788,119 @@ def visualize(self):
gmsh.fltk.run()
# gmsh.finalize()

def live_visualization_2d(self):
"""
Show live visualization of the time-domain simulation results.

- a post simulation viewer
- allows to open ".pos"-files in gmsh
- For example current density, ohmic losses or the magnetic field density can be visualized
"""
logger.info("\n---\nVisualize fields in GMSH front end:\n")
gmsh.initialize()
epsilon = 1e-9
Comment thread
tillpiepenbrock marked this conversation as resolved.

# merge view files
gmsh.merge(os.path.join(self.file_data.e_m_fields_folder_path, 'j2F_density.pos'))
gmsh.merge(os.path.join(self.file_data.e_m_fields_folder_path, 'j2H_density.pos'))
gmsh.merge(os.path.join(self.file_data.e_m_fields_folder_path, 'Magb.pos'))
# to show the magnetic field in space and time step, merge b_grid, if it is existed
b_grid = os.path.join(self.file_data.e_m_fields_folder_path, "b_grid.pos")
has_b_grid = os.path.exists(b_grid)
if has_b_grid:
gmsh.merge(b_grid)

# Show the GUI:
if '-nopopup' not in sys.argv:
gmsh.fltk.initialize()

v = gmsh.view.getTags()
if len(v) == 0:
logger.info("No .pos views found in:", self.file_data.e_m_fields_folder_path)
gmsh.finalize()
return
# We set some options for each post-processing view:
# Mesh
gmsh.option.setNumber("Mesh.SurfaceEdges", 0)

if any(self.windings[i].conductor_type != ConductorType.RoundLitz for i in range(len(self.windings))):
# Ohmic losses (weighted effective value of current density)
# gmsh.open(os.path.join(self.file_data.e_m_fields_folder_path, "j2F_density.pos"))
gmsh.option.setNumber(f"View[{v[0]}].ScaleType", 2)
gmsh.option.setNumber(f"View[{v[0]}].RangeType", 3)
gmsh.option.setNumber(f"View[{v[0]}].SaturateValues", 1)
gmsh.option.setNumber(f"View[{v[0]}].CustomMin", gmsh.option.getNumber(f"View[{v[0]}].Min") + epsilon)
gmsh.option.setNumber(f"View[{v[0]}].CustomMax", gmsh.option.getNumber(f"View[{v[0]}].Max"))
gmsh.option.setNumber(f"View[{v[0]}].ColormapNumber", 1)
gmsh.option.setNumber(f"View[{v[0]}].IntervalsType", 2)
gmsh.option.setNumber(f"View[{v[0]}].NbIso", 40)
gmsh.option.setNumber(f"View[{v[0]}].ShowTime", 4)
gmsh.option.setNumber(f"View[{v[0]}].TimeStep", 1)
gmsh.option.setNumber(f"View[{v[0]}].Time", 1)
gmsh.option.setNumber(f"View[{v[0]}].NbTimeStep", 1)

if any(self.windings[i].conductor_type == ConductorType.RoundLitz for i in range(len(self.windings))):
# Ohmic losses (weighted effective value of current density)
# gmsh.open(os.path.join(self.file_data.e_m_fields_folder_path, "j2H_density.pos"))
gmsh.option.setNumber(f"View[{v[1]}].ScaleType", 2)
gmsh.option.setNumber(f"View[{v[1]}].RangeType", 3)
gmsh.option.setNumber(f"View[{v[1]}].SaturateValues", 1)
gmsh.option.setNumber(f"View[{v[1]}].CustomMin",
gmsh.option.getNumber(f"View[{v[1]}].Min") + epsilon)
gmsh.option.setNumber(f"View[{v[1]}].CustomMax", gmsh.option.getNumber(f"View[{v[1]}].Max"))
gmsh.option.setNumber(f"View[{v[1]}].ColormapNumber", 1)
gmsh.option.setNumber(f"View[{v[1]}].IntervalsType", 2)
gmsh.option.setNumber(f"View[{v[1]}].NbIso", 40)
gmsh.option.setNumber(f"View[{v[1]}].ShowTime", 4)
gmsh.option.setNumber(f"View[{v[1]}].TimeStep", 1)
gmsh.option.setNumber(f"View[{v[1]}].Time", 1)
gmsh.option.setNumber(f"View[{v[1]}].NbTimeStep", 1)

# Magnetic flux density
# gmsh.open(os.path.join(self.file_data.e_m_fields_folder_path, "Magb.pos"))
gmsh.option.setNumber(f"View[{v[2]}].ScaleType", 1)
gmsh.option.setNumber(f"View[{v[2]}].RangeType", 3)
gmsh.option.setNumber(f"View[{v[2]}].CustomMin", gmsh.option.getNumber(f"View[{v[2]}].Min") + epsilon)
gmsh.option.setNumber(f"View[{v[2]}].CustomMax", gmsh.option.getNumber(f"View[{v[2]}].Max"))
gmsh.option.setNumber(f"View[{v[2]}].ColormapNumber", 1)
gmsh.option.setNumber(f"View[{v[2]}].IntervalsType", 2)
gmsh.option.setNumber(f"View[{v[2]}].ShowTime", 4)
gmsh.option.setNumber(f"View[{v[2]}].NbIso", 40)
gmsh.option.setNumber(f"View[{v[2]}].TimeStep", 1)
gmsh.option.setNumber(f"View[{v[2]}].Time", 1)
gmsh.option.setNumber(f"View[{v[2]}].NbTimeStep", 1)

if has_b_grid:
bgrid_view = v[-1] # last merged
gmsh.view.option.setString(bgrid_view, "Name", "B_field...")
gmsh.view.option.setNumber(bgrid_view, "Axes", 1)
gmsh.view.option.setNumber(bgrid_view, "IntervalsType", 2)
gmsh.view.option.setNumber(bgrid_view, "Type", 2) # graph mode
gmsh.view.option.setNumber(bgrid_view, "AutoPosition", 0)
gmsh.view.option.setNumber(bgrid_view, "PositionX", 50)
gmsh.view.option.setNumber(bgrid_view, "PositionY", 50)
gmsh.view.option.setNumber(bgrid_view, "Width", 350)
gmsh.view.option.setNumber(bgrid_view, "Height", 250)

# Configure views for 2D
for vt in v:
gmsh.view.option.setNumber(vt, "IntervalsType", 2) # color shading
gmsh.view.option.setNumber(vt, "ShowScale", 1) # colorbar
gmsh.view.option.setNumber(vt, "Visible", 1) # ensure visible

# Animate through time steps
frames = int(gmsh.view.option.getNumber(v[0], "NbTimeStep"))
t = 0
while gmsh.fltk.isAvailable():
for vt in v:
gmsh.view.option.setNumber(vt, "TimeStep", t)
gmsh.graphics.draw()
time.sleep(0.2) # seconds per frame
t = (t + 1) % frames
if '-nopopup' not in sys.argv:
gmsh.fltk.run()
gmsh.finalize()

def get_loss_data(self, last_n_values: int, loss_type: str = 'litz_loss'):
"""
Return the last n values from the chosen loss type logged in the result folder.
Expand Down
Loading