Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thermal simulation: Update Tests and fixed issues with thermal core_parts #58

Merged
merged 8 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
166 changes: 77 additions & 89 deletions femmt/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def thermal_simulation(self, thermal_conductivity_dict: Dict, boundary_temperatu
# Power density for volumes W/m^3
#core_area = self.calculate_core_volume()
core_area = self.calculate_core_parts_volume()
#core_area = ff.calculate_cylinder_volume()



# Set wire radii
Expand Down Expand Up @@ -646,176 +646,160 @@ def calculate_core_volume(self) -> float:
return np.pi * (core_width ** 2 * core_height - (
inner_leg_width + winding_width) ** 2 * winding_height + inner_leg_width ** 2 * winding_height) - air_gap_volume


def calculate_core_parts_volume(self) -> list:

"""Calculates the volume of the core excluding air.
"""Calculates the volume of the part core excluding air.

:return: Volume of the core.
:return: Volume of the core part.
:rtype: list
"""
# Extract heights from the midpoints of air gaps
heights = [point[2] for point in self.air_gaps.midpoints]
core_part_volume = []
core_parts_volumes = []

def get_width(part_number):
if self.stray_path:
if self.stray_path.start_index == 0:
if part_number == 2:
return self.core.core_inner_diameter / 2
elif part_number == 3:
return self.stray_path.length
elif self.stray_path.start_index == 1:
if part_number == 2:
return self.stray_path.length
elif part_number == 3:
return self.core.core_inner_diameter / 2
"""
If there is a stray path, calculate width based on its starting index and part number.
part_number is the core_part_i+2; means that if the start_index is 0, the stray path is in core_part_2
if the start_index is 1, the stray path is in core_part_3 and so on

"""

if self.stray_path and part_number == self.stray_path.start_index + 2:
return self.stray_path.length
return self.core.core_inner_diameter / 2

# if single core
if self.core.core_type == CoreType.Single:
if self.component_type == ComponentType.IntegratedTransformer:
# For single core type and many core_parts (due to multiple airgaps)
if len(self.mesh.plane_surface_core) > 1:

# Calculate heights dynamically
# For single core and more than one core_part, volume for every core part is calculated
# # Sorting air gaps from lower to upper
sorted_midpoints = sorted(self.air_gaps.midpoints, key=lambda x: x[1])
#finding position of first airgap
# Finding position of first airgap
bottommost_airgap_position = sorted_midpoints[0][1]
bottommost_airgap_height = sorted_midpoints[0][2]
#finding position of last airgap
# Finding position of last airgap
topmost_airgap_position = sorted_midpoints[-1][1]
topmost_airgap_height = sorted_midpoints[-1][2]
# finding position to get the distance between two airgaps
second_topmost_airgap_position = sorted_midpoints[-2][1]
second_topmost_airgap_height = sorted_midpoints[-2][2]
# finding position to get the distance between two airgaps
third_topmost_airgap_position = sorted_midpoints[-3][1]
third_topmost_airgap_height = sorted_midpoints[-3][2]



# core_part_1 is divided into subparts cores
# subpart1: bottom left subpart
subpart1_1_height = bottommost_airgap_position + self.core.window_h / 2 - bottommost_airgap_height / 2 + self.core.core_inner_diameter / 4
subpart1_1_width = self.core.core_inner_diameter / 2
subpart1_1_volume = np.pi * subpart1_1_width ** 2 * subpart1_1_height

#subpart2
# subpart2: bottom mid subpart
subpart1_2_height = self.core.core_inner_diameter / 4
subpart1_2_width = self.core.window_w
subpart1_2_volume = np.pi * subpart1_2_width ** 2 * subpart1_2_height

#subpart2
# subpart3: right subpart
subpart1_3_height = self.core.window_h + self.core.core_inner_diameter / 2
subpart1_3_width = self.core.r_outer - self.core.r_inner
subpart1_3_volume = np.pi * subpart1_3_width ** 2 * subpart1_3_height

#subpart4
# subpart4: top mid subpart
subpart1_4_height = self.core.core_inner_diameter / 4
subpart1_4_width = self.core.window_w
subpart1_4_volume = np.pi * subpart1_4_width ** 2 * subpart1_4_height

#subpart1_5_height = self.air_gaps.midpoints[self.stray_path.start_index+1][1] - self.air_gaps.midpoints[self.stray_path.start_index+1][2] / 2
# subpart5: top left subpart
subpart1_5_height = self.core.window_h / 2 - topmost_airgap_position - topmost_airgap_height / 2 + self.core.core_inner_diameter / 4
subpart1_5_width = self.core.core_inner_diameter / 2
subpart1_5_volume = np.pi * subpart1_5_width ** 2 * subpart1_5_height

# Calculate the volume of core part 1 by summing up subpart volumes
core_part_1_volume = subpart1_1_volume + subpart1_2_volume + subpart1_3_volume + subpart1_4_volume + subpart1_5_volume
core_part_volume.append(core_part_1_volume)

# #core_part_2
if len(sorted_midpoints) > 1:
core_part_2_height = topmost_airgap_position - topmost_airgap_height / 2 - (second_topmost_airgap_position + second_topmost_airgap_height / 2)
core_part_2_width = get_width(2)

core_part_2_volume = np.pi * core_part_2_width ** 2 * core_part_2_height
core_part_volume.append(core_part_2_volume)

#core_part_3
if len(sorted_midpoints) > 2:
core_part_3_height = second_topmost_airgap_position - second_topmost_airgap_height / 2 - (third_topmost_airgap_position + third_topmost_airgap_height / 2)
core_part_3_width = get_width(3)

core_part_3_volume = np.pi * core_part_3_width ** 2 * core_part_3_height
core_part_volume.append(core_part_3_volume)


return core_part_volume


core_parts_volumes.append(core_part_1_volume)

# Calculate the volumes of the core parts between the air gaps
for i in range(len(sorted_midpoints) - 1):
air_gap_1_position = sorted_midpoints[i][1]
air_gap_1_height = sorted_midpoints[i][2]
air_gap_2_position = sorted_midpoints[i + 1][1]
air_gap_2_height = sorted_midpoints[i + 1][2]
# calculate the height based on airgap positions and heights, and the width
core_part_height = air_gap_2_position - air_gap_2_height / 2 - (
air_gap_1_position + air_gap_1_height / 2)
core_part_width = get_width(i + 2)
# calculate the volume
core_part_volume = np.pi * core_part_width ** 2 * core_part_height
core_parts_volumes.append(core_part_volume)

# Return the total core part volume
return core_parts_volumes
# for single core and only one core part
else:
return [self.calculate_core_volume()]

elif self.core.core_type == CoreType.Stacked:

# For stacked core types, the volume is divided into different core parts, each of which is further
# divided into subparts to calculate the total volume of each core part.

# core_part_1
# core_part_1 is divided into 3 subparts
# subpart_1
# Core Part 1 Calculation
# Core part 1 is calculated as the sum of three different subparts
# subpart_1: bottom left subpart
subpart1_1_height = self.core.window_h_bot / 2 + self.core.core_inner_diameter / 4 - heights[0] / 2
subpart1_1_width = self.core.core_inner_diameter / 2
subpart1_1_volume = np.pi * subpart1_1_width ** 2 * subpart1_1_height

# subpart_2
# subpart_2 : bottom mid subpart
subpart1_2_height = self.core.core_inner_diameter / 4
subpart1_2_width = self.core.window_w
subpart1_2_volume = np.pi * subpart1_2_width ** 2 * subpart1_2_height

# subpart 3
# subpart_3: bottom right subpart
subpart1_3_height = self.core.window_h_bot + self.core.core_inner_diameter / 4
subpart1_3_width = self.core.r_outer - self.core.r_inner
subpart1_3_volume = np.pi * subpart1_3_width ** 2 * subpart1_3_height

# Summing up the volumes of the subparts to get the total volume of core part 1
core_part_1_volume = subpart1_1_volume + subpart1_2_volume + subpart1_3_volume
core_part_volume.append(core_part_1_volume)
core_parts_volumes.append(core_part_1_volume)

# core_part_2
# core_part_2 : core part between the bottom airgap and subpart_1 of core_part_1
core_part_2_height = self.core.window_h_bot / 2 - heights[0] / 2
core_part_2_width = self.core.core_inner_diameter / 2
core_part_2_volume = np.pi * core_part_2_width ** 2 * core_part_2_height
core_part_volume.append(core_part_2_volume)
core_parts_volumes.append(core_part_2_volume)

# core_part_3
# core_part_3 : left mid core part (stacked)
core_part_3_height = self.core.core_inner_diameter / 4
core_part_3_width = self.core.r_inner
core_part_3_volume = np.pi * core_part_3_width ** 2 * core_part_3_height
core_part_volume.append(core_part_3_volume)
core_parts_volumes.append(core_part_3_volume)

# core_part_4
# core_part_4: right mid core part
core_part_4_height = self.core.core_inner_diameter / 4
core_part_4_width = self.core.r_outer - self.core.r_inner
core_part_4_volume = np.pi * core_part_4_width ** 2 * core_part_4_height
core_part_volume.append(core_part_4_volume)
core_parts_volumes.append(core_part_4_volume)

# core_part_5
# core_part_5 is divided into 3 parts
# subpart_1
# subpart_1: top right subpart
subpart5_1_height = self.core.window_h_top + self.core.core_inner_diameter / 4 - heights[1] / 2
subpart5_1_width = self.core.r_inner - self.core.window_w
subpart5_1_width = self.core.core_inner_diameter / 2
subpart5_1_volume = np.pi * subpart5_1_width ** 2 * subpart5_1_height

# subpart_2
# subpart_2: mid top subpart
subpart5_2_height = self.core.core_inner_diameter / 4
subpart5_2_width = self.core.window_w
subpart5_2_volume = np.pi * subpart5_2_width ** 2 * subpart5_2_height

# subpart 3
# subpart 3: left top subpart
subpart5_3_height = self.core.window_h_top + self.core.core_inner_diameter / 4
subpart5_3_width = self.core.r_outer - self.core.r_inner
subpart5_3_volume = np.pi * subpart5_3_width ** 2 * subpart5_3_height

# Summing up the volumes of the subparts to get the total volume of core_part_5
core_part_5_volume = subpart5_1_volume + subpart5_2_volume + subpart5_3_volume
core_part_volume.append(core_part_5_volume)

return core_part_volume
# if self.core.core_type == CoreType.Single:
# cylinder_height = self.core.window_h + self.core.core_inner_diameter / 2
# elif self.core.core_type == CoreType.Stacked:
# cylinder_height = self.core.window_h_bot + self.core.window_h_top + self.core.core_inner_diameter * 3 / 4
# cylinder_diameter = self.core.r_outer
#
# core_volumes = [] # Initialize list to store each volume
#
# for _ in self.mesh.plane_surface_core:
# volume = ff.calculate_cylinder_volume(cylinder_diameter, cylinder_height)
# core_volumes.append(volume)
#
# return core_volumes # Return the list of volumes
core_parts_volumes.append(core_part_5_volume)

# Returning the final list of core part volumes
return core_parts_volumes

def calculate_core_weight(self) -> float:
"""
Expand Down Expand Up @@ -2261,6 +2245,8 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None,
sweep_dict["core_hyst_losses"] = self.load_result(res_name="p_hyst", last_n=sweep_number)[sweep_run]

# Core Part losses
# If there are multiple core parts, calculate losses for each part individually
# the core losses are caluclated by summing the eddy and hyst losses
if len(self.mesh.plane_surface_core) > 1:
sweep_dict["core_parts"] = {}
for i in range(0, len(self.mesh.plane_surface_core)):
Expand All @@ -2274,6 +2260,7 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None,
hyst = sweep_dict["core_parts"][f"core_part_{i + 1}"]["hyst_losses"]
sweep_dict["core_parts"][f"core_part_{i + 1}"][f"total_core_part_{i + 1}"] = eddy + hyst

# For a single core part, total losses are simply the sum of eddy and hysteresis losses
else:
# if I have only one core_part
sweep_dict["core_parts"] = {} # parent dictionary is initialized first
Expand Down Expand Up @@ -2322,12 +2309,13 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None,
# Core
log_dict["total_losses"]["eddy_core"] = sum(
log_dict["single_sweeps"][d]["core_eddy_losses"] for d in range(len(log_dict["single_sweeps"])))
# setting the total for every core_part in total losses dict
# In the total losses calculation, individual core part losses are also accounted for
if len(self.mesh.plane_surface_core) > 1:
for i in range(len(self.mesh.plane_surface_core)):
log_dict["total_losses"][f"total_core_part_{i + 1}"] = sweep_dict["core_parts"][f"core_part_{i + 1}"][
f"total_core_part_{i + 1}"]
if len(self.mesh.plane_surface_core) == 1: # core and core_part_1 will be the same
# If there is only one core part, set its total losses directly
if len(self.mesh.plane_surface_core) == 1:

log_dict["total_losses"]["total_core_part_1"] = sweep_dict["core_parts"]["core_part_1"]["total_core_part_1"]

Expand Down
34 changes: 27 additions & 7 deletions femmt/thermal/thermal_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,28 @@ def create_background(background_tag, k_air, function_pro: FunctionPro, group_pr

def create_core_and_air_gaps(core_tags, k_core, core_area, core_parts_losses, air_gaps_tag, k_air_gaps,
function_pro: FunctionPro, group_pro: GroupPro):


"""
Creates core and air gaps configurations with associated thermal properties.

:param core_tags: Tags identifying different parts of the core.
:param k_core: Thermal conductivity of the core.
:param core_area: Areas of different core parts.
:param core_parts_losses: losses in different core parts.
:param air_gaps_tag: Tag identifying the air gaps.
:param k_air_gaps: Thermal conductivity of the air gaps.
:param function_pro: Object to manage function properties.
:param group_pro: Object to manage group properties.
"""
# Initialize dictionaries for volumetric heat flux, thermal conductivity, and region
q_vol = {} # Dictionary to hold volumetric heat flux for each core part
k = {} # Dictionary to hold thermal conductivity for each core part
regions = {} # Dictionary to hold regions for each core part
core_total_str = "{"

# Convert core tags to a list of lists for consistency
core_tags = [[tag] for tag in core_tags]
for index, tag in enumerate(core_tags):
#tag = tag_list[0]
# Create a unique name for each core part based on its index
name = f"core_part_{index + 1}" # Create a name for the core part based on its position
core_total_str += f"{name}, "

Expand All @@ -89,6 +101,7 @@ def create_core_and_air_gaps(core_tags, k_core, core_area, core_parts_losses, ai
# Associate the core part name with its tag
regions[name] = tag[0] # as Tag is list of list

# If air gaps are defined, assign thermal conductivity and tag
if air_gaps_tag is not None:
k["air_gaps"] = k_air_gaps
regions["air_gaps"] = air_gaps_tag
Expand Down Expand Up @@ -148,6 +161,14 @@ def create_windings(winding_tags, k_windings, winding_losses, conductor_radii, w
def create_post_operation(thermal_file_path, thermal_influx_file_path, thermal_material_file_path, sensor_points_file,
core_file, insulation_file, winding_file, windings, core_parts, print_sensor_values,
post_operation_pro: PostOperationPro, flag_insulation):
"""
Configures post-operation properties for a thermal simulation.

:param ...: (Include detailed description for each parameter as needed)
:param post_operation_pro: Object to manage post-operation properties.
:param flag_insulation: Flag indicating the presence of insulation.
"""

# Add pos file generation
post_operation_pro.add_on_elements_of_statement("T", "Total", thermal_file_path)
post_operation_pro.add_on_elements_of_statement("influx", "Warm", thermal_influx_file_path)
Expand Down Expand Up @@ -176,9 +197,7 @@ def create_post_operation(thermal_file_path, thermal_influx_file_path, thermal_m

# Add regions
#core file will be GmshParsed file as we have many core parts
# for core_index, core_part in enumerate(core_parts): # Assuming core_tags is a list of core tags
# name = f"core_part_{core_index + 1}"
# post_operation_pro.add_on_elements_of_statement("T", name, core_file, "GmshParsed", 0, name, True)
# Adding temperature post-operation statements for each core part if they are defined
core_parts = [[part] for part in core_parts]
core_append = False # Initialize the append flag for core parts
for core_index, core_part in enumerate(core_parts):
Expand All @@ -191,10 +210,11 @@ def create_post_operation(thermal_file_path, thermal_influx_file_path, thermal_m
core_append = True



# Adding temperature post-operation statement for insulation if insulation is present
if flag_insulation:
post_operation_pro.add_on_elements_of_statement("T", "insulation", insulation_file, "SimpleTable", 0)

# Adding temperature post-operation statements for each winding, considering the case of parallel windings
append = False
for winding_index, winding in enumerate(windings):
if winding is not None and len(winding) > 0:
Expand Down
Loading
Loading