Inductance calculation
This commit is contained in:
parent
96d6cbd125
commit
618058e3b7
@ -1,3 +1,4 @@
|
|||||||
matplotlib
|
matplotlib
|
||||||
numpy
|
numpy
|
||||||
pyinstaller
|
pyinstaller
|
||||||
|
scipy
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from tkinter.scrolledtext import ScrolledText
|
||||||
|
|
||||||
import matplotlib
|
import matplotlib
|
||||||
matplotlib.use("TkAgg")
|
matplotlib.use("TkAgg")
|
||||||
@ -10,11 +11,135 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import math as m
|
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
|
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:
|
class TabCoil:
|
||||||
def __init__(self, notebook, tab_simulator):
|
def __init__(self, notebook, tab_simulator):
|
||||||
"""
|
"""
|
||||||
@ -30,9 +155,6 @@ class TabCoil:
|
|||||||
self.frame = tk.Frame(notebook)
|
self.frame = tk.Frame(notebook)
|
||||||
self.frame.pack(fill="both", expand=True)
|
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)
|
# Layout principal: lado izq (parámetros) y lado der (dibujo)
|
||||||
frame_left = tk.Frame(self.frame)
|
frame_left = tk.Frame(self.frame)
|
||||||
frame_left.pack(side="left", fill="y", padx=5, pady=5)
|
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")
|
tk.Label(frame_left, text="Parámetros de la Bobina:").pack(anchor="w")
|
||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
self.var_N = tk.DoubleVar(value=500)
|
self.var_N = tk.DoubleVar(value=500) # Número de espiras
|
||||||
self.var_e_pvc = tk.DoubleVar(value=4.04)
|
self.var_e_pvc = tk.DoubleVar(value=2) # Espesor del plástico [mm]
|
||||||
self.var_r_int = tk.DoubleVar(value=12.07)
|
self.var_r_int = tk.DoubleVar(value=4.035) # Radio interior [mm]
|
||||||
self.var_r_ext = tk.DoubleVar(value=21.27)
|
self.var_r_ext = tk.DoubleVar(value=10.64) # Radio exterior [mm]
|
||||||
self.var_h_c = tk.DoubleVar(value=53.12)
|
self.var_h_c = tk.DoubleVar(value=53.12) # Altura de la bobina [mm]
|
||||||
self.var_d_cu = tk.DoubleVar(value=0.8)
|
self.var_d_cu = tk.DoubleVar(value=0.5) # Diámetro conductor [mm]
|
||||||
|
|
||||||
mu_0 = 4 * m.pi * 1e-7
|
# Variable para mostrar la inductancia (se mostrará en µH)
|
||||||
mu_r = 1
|
self.var_L = tk.StringVar(value="")
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Frame para agrupar los Entries
|
# Frame para agrupar los Entries
|
||||||
self.frame_params = tk.Frame(frame_left, bd=1, relief="sunken")
|
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 = tk.Entry(self.frame_params, textvariable=var, width=width)
|
||||||
ent.grid(row=row, column=1, padx=5, pady=2)
|
ent.grid(row=row, column=1, padx=5, pady=2)
|
||||||
|
|
||||||
# Creamos las filas
|
|
||||||
rowcount = 0
|
rowcount = 0
|
||||||
add_param_row("N (vueltas):", self.var_N, 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("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_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("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("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("d_cu (mm):", self.var_d_cu, rowcount); rowcount += 1
|
||||||
add_param_row("L (H):", self.var_L, 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(
|
btn_refrescar = tk.Button(
|
||||||
frame_left, text="Refrescar Vista 2D",
|
frame_left, text="Refrescar Vista 2D",
|
||||||
command=self.refrescar_2d
|
command=self.refrescar_2d
|
||||||
)
|
)
|
||||||
btn_refrescar.pack(pady=10)
|
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,
|
# Sección de Logs
|
||||||
# pues 'plot_coil_in_frame' creará su propia Figure).
|
#
|
||||||
|
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.fig = plt.Figure(figsize=(4,3), dpi=100)
|
||||||
self.ax = self.fig.add_subplot(111)
|
self.ax = self.fig.add_subplot(111)
|
||||||
@ -104,12 +245,21 @@ class TabCoil:
|
|||||||
# Dibujamos la bobina inicial
|
# Dibujamos la bobina inicial
|
||||||
self.refrescar_2d()
|
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):
|
def refrescar_2d(self):
|
||||||
"""
|
"""
|
||||||
Lee los valores de la bobina y llama a 'plot_coil_in_frame'
|
Lee los valores de la bobina y llama a 'plot_coil_in_frame'
|
||||||
para actualizar el dibujo en 2D.
|
para actualizar el dibujo en 2D.
|
||||||
"""
|
"""
|
||||||
# Obtenemos valores
|
|
||||||
N_val = self.var_N.get()
|
N_val = self.var_N.get()
|
||||||
e_pvc_val = self.var_e_pvc.get()
|
e_pvc_val = self.var_e_pvc.get()
|
||||||
r_int_val = self.var_r_int.get()
|
r_int_val = self.var_r_int.get()
|
||||||
@ -117,7 +267,7 @@ class TabCoil:
|
|||||||
h_c_val = self.var_h_c.get()
|
h_c_val = self.var_h_c.get()
|
||||||
d_cu_val = self.var_d_cu.get()
|
d_cu_val = self.var_d_cu.get()
|
||||||
|
|
||||||
# Limpiamos el Axes actual (si lo usáramos)
|
# Limpiar el Axes
|
||||||
self.ax.clear()
|
self.ax.clear()
|
||||||
|
|
||||||
# Llamada a la función que inserta la figura en self.frame_2d
|
# Llamada a la función que inserta la figura en self.frame_2d
|
||||||
@ -130,3 +280,56 @@ class TabCoil:
|
|||||||
h_c=h_c_val,
|
h_c=h_c_val,
|
||||||
d_cu=d_cu_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")
|
Loading…
Reference in New Issue
Block a user