Inductance calculation

This commit is contained in:
Pedro Jose Romero Gombau 2025-01-27 11:14:11 +01:00
parent 96d6cbd125
commit 618058e3b7
2 changed files with 235 additions and 31 deletions

View File

@ -1,3 +1,4 @@
matplotlib
numpy
pyinstaller
pyinstaller
scipy

View File

@ -2,6 +2,7 @@
import tkinter as tk
from tkinter import ttk
from tkinter.scrolledtext import ScrolledText
import matplotlib
matplotlib.use("TkAgg")
@ -10,11 +11,135 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import math as m
import decimal as dec
# Importamos la función para dibujar la bobina 2D
# Se importan las integrales elípticas de SciPy para el cálculo preciso de la inductancia
from scipy.special import ellipk, ellipe
# -------------------------------------------------------------------
# Constantes y funciones para el cálculo de inductancia
# -------------------------------------------------------------------
MU0 = 4.0e-7 * m.pi # Permeabilidad magnética del vacío (H/m)
def self_inductance_loop(r, a_eff=1e-4):
"""
Autoinductancia aproximada de un anillo fino de radio r.
Emplea la fórmula: L ~ mu0*r * [ln(8r/a_eff) - 2].
"""
if r <= 0:
return 0.0
return MU0 * r * (m.log(8.0 * r / a_eff) - 2.0)
def mutual_inductance_coaxial(r1, r2, z_dist):
"""
Inductancia mutua entre dos anillos coaxiales de radios r1 y r2,
separados una distancia axial z_dist.
Emplea las integrales elípticas de primera y segunda especie.
"""
if r1 < 1e-12 or r2 < 1e-12:
return 0.0
k_sq = 4.0 * r1 * r2 / ((r1 + r2)**2 + z_dist**2)
if k_sq > 1.0:
# Caso no físico (puede ocurrir por redondeos), retornamos 0
return 0.0
k = np.sqrt(k_sq)
# Caso casi degenerado (r1 ~ r2, z_dist ~ 0)
if (1.0 - k_sq) < 1e-12:
# Aproximación cuando r1 ≈ r2 y z ≈ 0
return MU0 * min(r1, r2) * m.pi
else:
Kk = ellipk(k_sq)
Ek = ellipe(k_sq)
factor = MU0 * np.sqrt(r1 * r2)
# Expresión: M = mu0 * sqrt(r1*r2)*((2-k)*K - 2E) / k
return factor * ((2.0 - k) * Kk - 2.0 * Ek) / k
def compute_coil_geometry(N_total, R_int, R_ext, espesor_plast, H, wire_d):
"""
Genera la lista de (r_i, z_i) para las N_total espiras, asumiendo
que se llenan capas radiales y, en cada capa, vueltas a lo largo de la altura.
Todos los parámetros deben estar en metros.
"""
radial_thickness = R_ext - (R_int + 2 * espesor_plast)
if radial_thickness <= 0:
raise ValueError("No hay espacio radial para el bobinado.")
max_layers = int(np.floor(radial_thickness / wire_d))
if max_layers < 1:
raise ValueError("No cabe ni una capa con el grosor de hilo dado.")
max_turns_per_layer = int(np.floor(H / wire_d))
if max_turns_per_layer < 1:
raise ValueError("No cabe ni una vuelta en la altura dada.")
max_turns_possible = max_layers * max_turns_per_layer
if max_turns_possible < N_total:
raise ValueError(
f"Con {max_layers} capas y {max_turns_per_layer} vueltas/capa "
f"sólo se pueden alojar {max_turns_possible} espiras, "
f"pero se solicitan {N_total}."
)
espiras = []
vueltas_restantes = N_total
layer_index = 0
while vueltas_restantes > 0 and layer_index < max_layers:
r_layer = R_int + espesor_plast + layer_index * wire_d + wire_d / 2.0
if vueltas_restantes >= max_turns_per_layer:
n_vueltas = max_turns_per_layer
else:
n_vueltas = vueltas_restantes
for turn in range(n_vueltas):
z_i = (turn + 0.5) * wire_d
espiras.append((r_layer, z_i))
vueltas_restantes -= n_vueltas
layer_index += 1
if vueltas_restantes > 0:
raise ValueError(
f"No se pudo colocar toda la bobina. "
f"Faltan {vueltas_restantes} espiras por ubicar."
)
return espiras
def inductance_from_spiralist(espiras, wire_radius):
"""
Calcula la inductancia total (en henrios) a partir de la lista (r_i, z_i),
sumando la autoinductancia de cada espira y la mutua entre pares (O(N^2)).
"""
N = len(espiras)
L_total = 0.0
for i in range(N):
r_i, z_i = espiras[i]
# Autoinductancia
L_total += self_inductance_loop(r_i, wire_radius)
# Mutua con las espiras j > i
for j in range(i + 1, N):
r_j, z_j = espiras[j]
dist_z = abs(z_j - z_i)
M_ij = mutual_inductance_coaxial(r_i, r_j, dist_z)
L_total += 2.0 * M_ij
return L_total
# -------------------------------------------------------------------
# Importamos la función para dibujar la bobina 2D (tal y como estaba).
# -------------------------------------------------------------------
from tabs.geometry_viewer import plot_coil_in_frame
# -------------------------------------------------------------------
# Definición de la clase TabCoil, añadiendo la ventana de logs y
# el botón de cálculo de inductancia con mensajes de estado.
# -------------------------------------------------------------------
class TabCoil:
def __init__(self, notebook, tab_simulator):
"""
@ -30,9 +155,6 @@ class TabCoil:
self.frame = tk.Frame(notebook)
self.frame.pack(fill="both", expand=True)
# (Este add lo hace habitualmente main.py, pero se podría hacer aquí)
# notebook.add(self.frame, text="Bobinas")
# Layout principal: lado izq (parámetros) y lado der (dibujo)
frame_left = tk.Frame(self.frame)
frame_left.pack(side="left", fill="y", padx=5, pady=5)
@ -50,18 +172,15 @@ class TabCoil:
tk.Label(frame_left, text="Parámetros de la Bobina:").pack(anchor="w")
# Variables
self.var_N = tk.DoubleVar(value=500)
self.var_e_pvc = tk.DoubleVar(value=4.04)
self.var_r_int = tk.DoubleVar(value=12.07)
self.var_r_ext = tk.DoubleVar(value=21.27)
self.var_h_c = tk.DoubleVar(value=53.12)
self.var_d_cu = tk.DoubleVar(value=0.8)
mu_0 = 4 * m.pi * 1e-7
mu_r = 1
inductance = (self.var_N.get()**2 * mu_0 * mu_r * (2 * m.pi * (self.var_r_ext.get() * 1e-3)**2)) / (self.var_h_c.get() * 1e-3)
self.var_L = tk.DoubleVar(value=inductance)
self.var_N = tk.DoubleVar(value=500) # Número de espiras
self.var_e_pvc = tk.DoubleVar(value=2) # Espesor del plástico [mm]
self.var_r_int = tk.DoubleVar(value=4.035) # Radio interior [mm]
self.var_r_ext = tk.DoubleVar(value=10.64) # Radio exterior [mm]
self.var_h_c = tk.DoubleVar(value=53.12) # Altura de la bobina [mm]
self.var_d_cu = tk.DoubleVar(value=0.5) # Diámetro conductor [mm]
# Variable para mostrar la inductancia (se mostrará en µH)
self.var_L = tk.StringVar(value="")
# Frame para agrupar los Entries
self.frame_params = tk.Frame(frame_left, bd=1, relief="sunken")
@ -74,26 +193,48 @@ class TabCoil:
ent = tk.Entry(self.frame_params, textvariable=var, width=width)
ent.grid(row=row, column=1, padx=5, pady=2)
# Creamos las filas
rowcount = 0
add_param_row("N (vueltas):", self.var_N, rowcount); rowcount += 1
add_param_row("e_pvc (mm):", self.var_e_pvc, rowcount); rowcount += 1
add_param_row("r_int (mm):", self.var_r_int, rowcount); rowcount += 1
add_param_row("r_ext (mm):", self.var_r_ext, rowcount); rowcount += 1
add_param_row("h_c (mm):", self.var_h_c, rowcount); rowcount += 1
add_param_row("d_cu (mm):", self.var_d_cu, rowcount); rowcount += 1
add_param_row("L (H):", self.var_L, rowcount); rowcount += 1
add_param_row("N (vueltas):", self.var_N, rowcount); rowcount += 1
add_param_row("e_pvc (mm):", self.var_e_pvc, rowcount); rowcount += 1
add_param_row("r_int (mm):", self.var_r_int, rowcount); rowcount += 1
add_param_row("r_ext (mm):", self.var_r_ext, rowcount); rowcount += 1
add_param_row("h_c (mm):", self.var_h_c, rowcount); rowcount += 1
add_param_row("d_cu (mm):", self.var_d_cu, rowcount); rowcount += 1
# Botón para actualizar la vista 2D
# Campo para ver la inductancia en microHenrios (µH).
lblL = tk.Label(self.frame_params, text="Induct. (µH):")
lblL.grid(row=rowcount, column=0, sticky="w", padx=5, pady=2)
entL = tk.Entry(self.frame_params, textvariable=self.var_L, width=10, state="readonly")
entL.grid(row=rowcount, column=1, padx=5, pady=2)
rowcount += 1
# Botones
btn_refrescar = tk.Button(
frame_left, text="Refrescar Vista 2D",
command=self.refrescar_2d
)
btn_refrescar.pack(pady=10)
btn_calc_L = tk.Button(
frame_left, text="Calcular Inductancia",
command=self.calcular_inductancia
)
btn_calc_L.pack(pady=5)
#
# Creamos la figura y canvas para la bobina en 2D (opcional,
# pues 'plot_coil_in_frame' creará su propia Figure).
# Sección de Logs
#
tk.Label(frame_left, text="Logs:").pack(anchor="w")
self.log_area = ScrolledText(frame_left, wrap="word", height=8, width=35)
self.log_area.pack(fill="x", padx=5, pady=5)
self.log_area.configure(state="disabled")
# Definimos etiquetas de color
self.log_area.tag_config("info", foreground="green")
self.log_area.tag_config("error", foreground="red")
#
# Figura y canvas para la bobina en 2D
#
self.fig = plt.Figure(figsize=(4,3), dpi=100)
self.ax = self.fig.add_subplot(111)
@ -104,12 +245,21 @@ class TabCoil:
# Dibujamos la bobina inicial
self.refrescar_2d()
def log_message(self, text, mode="info"):
"""
Inserta un mensaje en el log_area, en color definido por 'mode'.
mode puede ser 'info' (verde) o 'error' (rojo).
"""
self.log_area.configure(state="normal")
self.log_area.insert("end", text + "\n", mode)
self.log_area.see("end")
self.log_area.configure(state="disabled")
def refrescar_2d(self):
"""
Lee los valores de la bobina y llama a 'plot_coil_in_frame'
para actualizar el dibujo en 2D.
"""
# Obtenemos valores
N_val = self.var_N.get()
e_pvc_val = self.var_e_pvc.get()
r_int_val = self.var_r_int.get()
@ -117,7 +267,7 @@ class TabCoil:
h_c_val = self.var_h_c.get()
d_cu_val = self.var_d_cu.get()
# Limpiamos el Axes actual (si lo usáramos)
# Limpiar el Axes
self.ax.clear()
# Llamada a la función que inserta la figura en self.frame_2d
@ -130,3 +280,56 @@ class TabCoil:
h_c=h_c_val,
d_cu=d_cu_val
)
def calcular_inductancia(self):
"""
Cálculo de la inductancia a partir de los parámetros introducidos.
Muestra el resultado en µH en el campo correspondiente y en la log_area.
"""
try:
# Lectura de parámetros
N_espiras = int(self.var_N.get())
e_pvc_val = self.var_e_pvc.get()
r_int_val = self.var_r_int.get()
r_ext_val = self.var_r_ext.get()
h_c_val = self.var_h_c.get()
d_cu_val = self.var_d_cu.get()
# Conversión mm -> m
R_in = r_int_val / 1000.0
R_out = r_ext_val / 1000.0
plast = e_pvc_val / 1000.0
altura = h_c_val / 1000.0
diam_hilo = d_cu_val / 1000.0
# Construir la geometría de la bobina
espiras_xyz = compute_coil_geometry(
N_total=N_espiras,
R_int=R_in,
R_ext=R_out,
espesor_plast=plast,
H=altura,
wire_d=diam_hilo
)
# Radio efectivo para autoinductancia
wire_radius = diam_hilo / 2.0
# Calcular la inductancia total (en henrios)
L_h = inductance_from_spiralist(espiras_xyz, wire_radius)
L_uH = L_h * 1e6
# Actualizar variable de resultado
self.var_L.set(f"{L_uH:.3f}")
# Log de éxito
self.log_message("Inductancia calculada correctamente", "info")
except ValueError as e:
# Si hay algún problema con la geometría, etc.
self.var_L.set("Error")
self.log_message(f"Error en cálculo: {str(e)}", "error")
except Exception as ex:
# Errores generales
self.var_L.set("Error")
self.log_message(f"Error inesperado: {str(ex)}", "error")