Objetivos de aprendizaje
Plantear el análisis nodal de un circuito resistivo como un sistema lineal .
Escribir la matriz de conductancias por inspección y reconocer su estructura: simetría y diagonal dominante.
Resolver el mismo circuito de forma simbólica (SymPy) y numérica (NumPy) y comprobar que coinciden.
Explicar qué significa físicamente que la matriz no tenga inversa.
Teoría: Volumen I, capítulo 5 (método de nodos), capítulo 6 (formulación matricial) y Anexo B (Miguel-Medina, 2026). Tiempo: ~1,5 h
import sys
if "google.colab" in sys.modules:
%pip install -q git+https://github.com/telmomm/math-tools-biomedical-electronicsimport matplotlib.pyplot as plt
import numpy as np
import schemdraw
import schemdraw.elements as elm
import sympy as sp
from circuitmath.plotting import use_style
use_style()Idea clave¶
El análisis nodal toma como incógnitas las tensiones de los nodos respecto a uno de referencia y aplica la ley de Kirchhoff de corrientes (KCL) en cada uno de los demás. Cada ecuación es lineal en las tensiones, así que todas juntas forman un sistema que se escribe de forma compacta como
donde son las tensiones de nodo, las corrientes que las fuentes inyectan en cada nodo y la matriz de conductancias. El capítulo 6 del Volumen I da la regla para escribirla sin plantear las ecuaciones una a una:
es la suma de las conductancias conectadas al nodo ;
es la suma de las conductancias conectadas entre los nodos y , con signo negativo.
En este cuaderno vas a comprobar con el ordenador de dónde sale esa regla, qué forma tiene la matriz y qué ocurre cuando el sistema no se puede resolver.
El circuito¶
Trabajaremos con una red en escalera de tres nodos alimentada por una fuente de corriente. El nodo inferior es la referencia (0 V).
with schemdraw.Drawing() as d:
d.config(unit=2.5, fontsize=12)
fuente = elm.SourceI().up().label("$I_s$")
elm.Line().right().length(1.5)
elm.Dot().label("1", loc="top")
d.push()
elm.Resistor().down().label("$R_1$", loc="bottom")
d.pop()
elm.Resistor().right().label("$R_2$")
elm.Dot().label("2", loc="top")
d.push()
elm.Resistor().down().label("$R_3$", loc="bottom")
d.pop()
elm.Resistor().right().label("$R_4$")
elm.Dot().label("3", loc="top")
elm.Resistor().down().label("$R_5$", loc="bottom")
elm.Line().left().tox(fuente.start)
elm.Ground()# Valores de los componentes <- cámbialos cuando quieras experimentar
R = {"R1": 1e3, "R2": 2.2e3, "R3": 4.7e3, "R4": 1e3, "R5": 3.3e3} # ohmios
I_FUENTE = 2e-3 # amperios1. Predice¶
Antes de ejecutar nada más, responde en papel o en la celda siguiente:
¿Cuántas ecuaciones tendrá el sistema?
Sin calcular, ¿qué nodo tendrá la tensión más alta y cuál la más baja? ¿Por qué?
La matriz tendrá algunos elementos iguales a cero. ¿Cuáles y por qué?
Tu predicción:
2. Ejecuta y explora¶
2.1 Las ecuaciones de nodo, escritas tal cual¶
Empezamos sin ninguna regla: escribimos KCL en cada nodo igualando la corriente que sale por las resistencias a la que inyecta la fuente. SymPy trabaja con los símbolos, sin números.
v1, v2, v3 = sp.symbols("v1 v2 v3")
R1, R2, R3, R4, R5, Is = sp.symbols("R1 R2 R3 R4 R5 I_s")
nodo1 = sp.Eq(v1 / R1 + (v1 - v2) / R2, Is)
nodo2 = sp.Eq((v2 - v1) / R2 + v2 / R3 + (v2 - v3) / R4, 0)
nodo3 = sp.Eq((v3 - v2) / R4 + v3 / R5, 0)
nodo2Ahora pedimos a SymPy que agrupe los coeficientes de , y . Lo que devuelve es la matriz del sistema y el vector de términos independientes.
G_sim, i_sim = sp.linear_eq_to_matrix([nodo1, nodo2, nodo3], [v1, v2, v3])
G_simCompara esta matriz con la regla de inspección de la Idea clave: en la diagonal aparece la suma de las conductancias de cada nodo, y fuera de ella, la conductancia entre dos nodos con signo negativo. La regla no es un truco: es lo que queda al ordenar las ecuaciones de KCL.
2.2 Solución simbólica¶
Con la matriz en la mano, SymPy puede resolver el sistema con letras. El resultado es una fórmula válida para cualquier valor de los componentes.
v_sim = sp.simplify(G_sim.LUsolve(i_sim))
v_sim[2]La expresión de ya es larga para un circuito de tres nodos: a mano sería fácil equivocarse. Sustituimos los valores numéricos:
v_simbolica = np.array(v_sim.subs({**R, "I_s": I_FUENTE}), dtype=float).ravel()
v_simbolicaarray([1.63272801, 0.82472965, 0.63293205])2.3 Solución numérica¶
En la práctica no se busca la fórmula: se construye la matriz con números y se resuelve el sistema. Escribimos por inspección.
def matriz_G(R1, R2, R3, R4, R5):
"""Matriz de conductancias del circuito, escrita por inspección."""
G1, G2, G3, G4, G5 = 1 / R1, 1 / R2, 1 / R3, 1 / R4, 1 / R5
return np.array(
[
[G1 + G2, -G2, 0],
[-G2, G2 + G3 + G4, -G4],
[0, -G4, G4 + G5],
]
)
G = matriz_G(**R)
i_s = np.array([I_FUENTE, 0, 0])
v = np.linalg.solve(G, i_s)
print("G (mS):")
print(np.round(G * 1e3, 3))
print("v (V):", np.round(v, 4))
print("¿Coincide con la solución simbólica?", np.allclose(v, v_simbolica))G (mS):
[[ 1.455 -0.455 0. ]
[-0.455 1.667 -1. ]
[ 0. -1. 1.303]]
v (V): [1.6327 0.8247 0.6329]
¿Coincide con la solución simbólica? True
Una solución numérica siempre se puede comprobar con la física. Dos comprobaciones independientes: que KCL se cumple en cada nodo (el residuo es cero, salvo el error de redondeo) y que la potencia que entrega la fuente es la que disipan las resistencias.
residuo = G @ v - i_s
print("Residuo de KCL (A):", residuo)
tensiones = {"R1": v[0], "R2": v[0] - v[1], "R3": v[1], "R4": v[1] - v[2], "R5": v[2]}
p_resistencias = sum(tensiones[nombre] ** 2 / R[nombre] for nombre in R)
p_fuente = I_FUENTE * v[0]
print(f"Potencia de la fuente: {p_fuente * 1e3:.4f} mW")
print(f"Potencia disipada: {p_resistencias * 1e3:.4f} mW")Residuo de KCL (A): [ 0.00000000e+00 -1.93586787e-19 4.76773731e-20]
Potencia de la fuente: 3.2655 mW
Potencia disipada: 3.2655 mW
2.4 La estructura de la matriz¶
no es una matriz cualquiera. Compruébalo:
print("¿Es simétrica?", np.allclose(G, G.T))
diagonal = np.diag(G)
fuera = np.sum(np.abs(G), axis=1) - np.abs(diagonal)
for j in range(3):
print(
f"Fila {j + 1}: diagonal = {diagonal[j] * 1e3:.3f} mS, "
f"resto de la fila = {fuera[j] * 1e3:.3f} mS, "
f"diferencia = {(diagonal[j] - fuera[j]) * 1e3:.3f} mS"
)
print("Conductancias a referencia (mS):", [round(1e3 / R[k], 3) for k in ("R1", "R3", "R5")])¿Es simétrica? True
Fila 1: diagonal = 1.455 mS, resto de la fila = 0.455 mS, diferencia = 1.000 mS
Fila 2: diagonal = 1.667 mS, resto de la fila = 1.455 mS, diferencia = 0.213 mS
Fila 3: diagonal = 1.303 mS, resto de la fila = 1.000 mS, diferencia = 0.303 mS
Conductancias a referencia (mS): [1.0, 0.213, 0.303]
En cada fila, el elemento de la diagonal es mayor que la suma de los demás en valor absoluto: la matriz es de diagonal dominante. Y lo que «sobra» en cada fila es exactamente la conductancia que une ese nodo con la referencia. Esas resistencias a referencia son las que garantizan que el sistema tenga solución única, como vas a ver ahora.
2.5 Rompe el circuito¶
¿Qué ocurre si quitamos las tres resistencias que van a referencia (, y )? Una resistencia infinita es una conductancia nula.
G_flotante = matriz_G(np.inf, R["R2"], np.inf, R["R4"], np.inf)
print(np.round(G_flotante * 1e3, 3))
print("Suma de cada fila:", G_flotante.sum(axis=1))
print("Rango de la matriz:", np.linalg.matrix_rank(G_flotante), "de 3")[[ 0.455 -0.455 0. ]
[-0.455 1.455 -1. ]
[ 0. -1. 1. ]]
Suma de cada fila: [0. 0. 0.]
Rango de la matriz: 2 de 3
Todas las filas suman cero y el rango baja de 3 a 2: la matriz es singular, no tiene inversa y el sistema no tiene solución única. No es un problema numérico, sino físico: la fuente inyecta corriente en una red que no tiene ningún camino de retorno, de modo que KCL no puede cumplirse. Un simulador como SPICE se detiene en esta situación con un error de «nodo flotante» o «matriz singular».
2.6 La inversa también tiene significado¶
Para resolver un sistema no hace falta calcular la inversa (np.linalg.solve es más rápido y preciso), pero tiene una lectura física útil: el elemento es la tensión que aparece en el nodo por cada amperio inyectado en el nodo . Son resistencias.
R_transferencia = np.linalg.inv(G)
print("Inversa de G (ohmios):")
print(np.round(R_transferencia, 1))
print("¿Es simétrica?", np.allclose(R_transferencia, R_transferencia.T))
print("Primera columna por I_s (V):", np.round(R_transferencia[:, 0] * I_FUENTE, 4))Inversa de G (ohmios):
[[ 816.4 412.4 316.5]
[ 412.4 1319.6 1012.7]
[ 316.5 1012.7 1544.6]]
¿Es simétrica? True
Primera columna por I_s (V): [1.6327 0.8247 0.6329]
La primera columna, multiplicada por , es la solución que ya conocías. Las otras dos columnas responden a preguntas que no habíamos hecho: qué tensiones aparecerían si la fuente se conectara al nodo 2 o al nodo 3. Y la simetría dice algo que no es evidente mirando el circuito: inyectar corriente en el nodo 1 y medir en el 3 da la misma tensión que inyectar en el 3 y medir en el 1 (reciprocidad).
2.7 Un parámetro que cambia¶
Con la matriz en una función, repetir el cálculo para muchos valores no cuesta nada. Barremos , la resistencia que une los nodos 1 y 2, a lo largo de seis décadas.
valores_R2 = np.logspace(0, 6, 200) # de 1 ohmio a 1 megaohmio
tensiones_barrido = np.array(
[np.linalg.solve(matriz_G(**{**R, "R2": r2}), i_s) for r2 in valores_R2]
)
fig, ax = plt.subplots()
for j in range(3):
ax.semilogx(valores_R2, tensiones_barrido[:, j], label=f"$v_{j + 1}$")
ax.axvline(R["R2"], color="gray", linestyle=":", linewidth=1)
ax.set(xlabel="$R_2$ (Ω)", ylabel="Tensión de nodo (V)", title="Efecto de $R_2$ en las tensiones")
ax.legend()
plt.show()
3. Explica¶
Responde con tus palabras:
En la figura anterior, ¿por qué y se juntan cuando es muy pequeña? ¿Y por qué y tienden a cero cuando es muy grande? ¿A qué valor tiende entonces ?
El elemento es cero. ¿Qué tendría que cambiar en el circuito para que dejara de serlo?
Para comparar la solución simbólica con la numérica hemos usado
np.allclosey no==. ¿Por qué? Fíjate en el residuo de KCL que has obtenido.Al quitar las resistencias a referencia la matriz se vuelve singular. ¿Bastaría con dejar una sola de las tres para que el sistema volviera a tener solución? Compruébalo con
matriz_G.¿Por qué con solo resistencias y fuentes de corriente la matriz sale siempre simétrica?
4. Ejercicios¶
Básico. Reproduce con NumPy dos ejemplos resueltos a mano en el Volumen I y comprueba que obtienes los mismos resultados:
Capítulo 6, ejemplo de la matriz de conductancias por inspección: fuente de 3 A que inyecta en el nodo 1 y tres resistencias iguales de 2 Ω (nodo 1 a referencia, nodo 1 a nodo 2, nodo 2 a referencia). Solución: V, V.
Anexo B, ejemplo del sistema : S e A. Resuélvelo además con la regla de Cramer usando
np.linalg.det.
Aplicación. Una fuente de tensión real de 5 V con una resistencia en serie de 1 kΩ se conecta entre el nodo 3 y la referencia del circuito de este cuaderno. Sustitúyela por su equivalente Norton (Volumen I, capítulo 5), modifica e y calcula las nuevas tensiones. Comprueba el resultado por superposición usando las columnas de .
Reto biomédico. El ejemplo del estimulador eléctrico del capítulo 5 del Volumen I modela un estimulador de corriente constante de mA conectado a un nodo A, del que salen un camino de fuga kΩ a referencia y la resistencia del electrodo kΩ hasta un nodo B; entre B y la referencia está el tejido, Ω. Solo el 80 % de la corriente programada llega al tejido.
Plantea y reproduce el resultado del libro ( V, V).
Cuando el gel del electrodo se seca, aumenta. Representa el porcentaje de corriente que llega al tejido para entre 100 Ω y 20 kΩ.
¿A partir de qué valor de llega al tejido menos de la mitad de la corriente programada? ¿Qué tensión tiene que dar entonces el estimulador?
Resumen¶
Aplicar KCL en cada nodo de un circuito resistivo da un sistema lineal , y la matriz se puede escribir por inspección.
Con resistencias y fuentes de corriente, es simétrica y de diagonal dominante; lo que sobra en cada fila es la conductancia a referencia.
Si ningún nodo tiene camino a referencia, la matriz es singular: el circuito no tiene solución única.
La solución simbólica da la fórmula y la numérica da el resultado; para explorar un circuito, lo práctico es una función que construya la matriz y un
np.linalg.solve.
→ Siguiente: B2 · Análisis de mallas y comparación de métodos
- Miguel-Medina, T. (2026). Electrónica en Ingeniería Biomédica — Volumen I. Autopublicación. 10.5281/zenodo.22913382