家庭のブレーカーが落ちる原因を考えたことはありますか?電子レンジ、エアコン、ドライヤーを同時に使うとブレーカーが作動するのは、各家電に流れる電流の 合計 がブレーカーの定格を超えるからです。では、回路のどの分岐点でも電流の合計がどうなっているか、系統的に把握する方法はあるのでしょうか?
もう一つ身近な例を挙げましょう。懐中電灯に電池を2本直列に入れると、1本の場合より明るく光ります。これは電池の電圧が足し合わされるためですが、回路を一周したとき電圧は どのように配分される のでしょうか?
これらの疑問に答える原理が キルヒホッフの法則 です。1845年にグスタフ・キルヒホッフが発表したこの法則は、わずか2つの単純なルールで任意の回路を解析できる強力な道具です。
キルヒホッフの法則を理解すると、次のような応用に直結します。
- 電子回路設計 — トランジスタ回路、オペアンプ回路など、あらゆる回路のバイアス設計と信号解析の基盤になります
- 電力系統解析 — 送電網の潮流計算(電力の流れの解析)はキルヒホッフの法則に基づく大規模連立方程式として定式化されます
- SPICEシミュレータの原理 — LTspice等の回路シミュレータは、内部でキルヒホッフの法則をもとにした修正節点解析(MNA)を使って回路方程式を自動生成しています
- センサ回路 — ホイートストンブリッジなどの計測回路はKVLとKCLの直接的な応用です
本記事の内容
- キルヒホッフの電流則(KCL)— 電荷保存から導出
- キルヒホッフの電圧則(KVL)— エネルギー保存から導出
- 回路の基本要素:抵抗・コンデンサ・インダクタの電圧-電流関係
- 節点電圧法(ノード解析)— コンダクタンス行列の構成
- 網目電流法(メッシュ解析)
- 重ね合わせの定理
- Pythonによる連立方程式の行列解法と回路シミュレーション
前提知識
この記事では電磁気学の基礎的な概念(電荷、電流、電圧、電場)を前提とします。以下の知識があるとスムーズに読み進められます。
- オームの法則($V = IR$)
- 電位の概念(電場の線積分)
キルヒホッフの電流則(KCL)
直感的理解
水道管の分岐を想像してください。太い管から水が流れてきて、T字路で2本の細い管に分かれます。分岐点に水が溜まったり、どこからともなく水が湧いたりすることはありません。流れ込む水量の合計と流れ出す水量の合計は常に等しい — これが 連続の原理 です。
電流も全く同じです。回路の分岐点(節点、ノード)に電荷が無限に溜まることはなく、電荷が消滅することもありません。したがって、ある節点に流れ込む電流の合計と流れ出す電流の合計は等しくなります。これがキルヒホッフの電流則(KCL: Kirchhoff’s Current Law)です。
電荷保存からの導出
KCL は 電荷保存則 の直接的な帰結です。電荷保存の連続方程式は
$$ \frac{\partial \rho}{\partial t} + \nabla \cdot \bm{J} = 0 $$
ここで $\rho$ は電荷密度、$\bm{J}$ は電流密度です。この式を節点周辺の微小体積 $V$ で積分します。
$$ \int_V \frac{\partial \rho}{\partial t} \, dV + \int_V \nabla \cdot \bm{J} \, dV = 0 $$
左辺第2項にガウスの発散定理を適用すると
$$ \frac{dQ}{dt} + \oint_{\partial V} \bm{J} \cdot d\bm{A} = 0 $$
ここで $Q = \int_V \rho \, dV$ は体積内の全電荷、$\oint \bm{J} \cdot d\bm{A}$ は閉曲面を通って流出する全電流です。
回路理論では、集中定数回路の仮定のもとで 節点に電荷は蓄積しない($dQ/dt = 0$)と考えます。すると
$$ \oint_{\partial V} \bm{J} \cdot d\bm{A} = 0 $$
これを回路の各枝に流れる電流 $I_k$ で書き直すと
$$ \begin{equation} \sum_{k} I_k = 0 \end{equation} $$
が得られます。ここで、節点に流入する電流を正、流出する電流を負(または逆の符号規約)として合計します。
KCL の意味と使い方
KCL を言葉で述べると
任意の節点について、その節点に流入する電流の総和はゼロである
ということです。等価的に、「流入する電流の合計 = 流出する電流の合計」とも言えます。
例えば、1つの節点に3本の枝が接続されていて、2本から $I_1 = 3$ A、$I_2 = 2$ A が流入し、1本から $I_3$ が流出する場合
$$ I_1 + I_2 – I_3 = 0 \quad \Rightarrow \quad I_3 = 5 \, \text{A} $$
KCL は「閉曲面」に対しても一般化できます。回路の一部を囲む任意の閉曲面を考えると、その面を通過する電流の総和はゼロです。これを 一般化KCL(generalized KCL) と呼び、サブ回路の入出力電流の関係を導くのに便利です。
KCL が電流について語る法則だとすると、電圧については何が言えるのでしょうか。次にキルヒホッフの電圧則を導出します。
キルヒホッフの電圧則(KVL)
直感的理解
登山に例えて考えましょう。山のある地点から出発して、尾根を登ったり谷を下ったりしながら一周して元の地点に戻ったとします。出発点と到着点は同じ場所なので、標高の変化の合計はゼロ です。上った分だけ必ず下っています。
電圧も同じです。回路を一周する閉路(ループ)を考えると、ループを一周したときの電圧の上昇と下降の合計はゼロです。電池で電圧が上がった分は、抵抗で必ず下がります。これがキルヒホッフの電圧則(KVL: Kirchhoff’s Voltage Law)です。
エネルギー保存からの導出
KVL は エネルギー保存則(保存力場の性質) の直接的な帰結です。静電場では、電場は保存力場であり
$$ \oint \bm{E} \cdot d\bm{l} = 0 $$
が成り立ちます。これは「閉路に沿って電場を一周積分するとゼロになる」ことを意味します。
回路理論では、2点間の電圧(電位差)は電場の線積分で定義されます。
$$ V_{ab} = -\int_a^b \bm{E} \cdot d\bm{l} $$
閉路に沿って各回路素子の電圧降下 $V_k$ を足し合わせると、上の積分がゼロになることから
$$ \begin{equation} \sum_{k} V_k = 0 \end{equation} $$
が得られます。ここで、ループを一周する向きを決め、その向きに沿った電圧上昇を正、電圧降下を負とします。
厳密な注意:変動電磁場の場合
厳密に言うと、KVL は時変の電磁場が存在する場合にはファラデーの法則
$$ \oint \bm{E} \cdot d\bm{l} = -\frac{d\Phi_B}{dt} $$
により修正が必要です。しかし、回路理論の集中定数近似(回路寸法 $\ll$ 波長)の範囲では、空間を通る磁束の変化はインダクタに「集約」されているとみなせるため、KVL は各素子の電圧降下に対して成り立ちます。つまり、インダクタの電圧降下 $V_L = L\frac{dI}{dt}$ がファラデーの法則の効果を取り込んでいるのです。
KVL の意味と使い方
KVL を言葉で述べると
任意の閉路について、その閉路に沿った電圧降下の総和はゼロである
ということです。等価的に、「電圧源による電圧上昇 = 各素子での電圧降下の合計」とも言えます。
例えば、電池 $V_s = 12$ V に直列に2つの抵抗 $R_1$、$R_2$ が接続された回路では
$$ V_s – I R_1 – I R_2 = 0 \quad \Rightarrow \quad I = \frac{V_s}{R_1 + R_2} $$
これは直列回路に対するオームの法則ですが、KVL から自然に導かれる結果です。
KCL と KVL の2つの法則が揃いました。これらを使って回路を解くには、各素子の電圧-電流関係(構成関係)も必要です。次に、基本的な回路素子の特性を整理しましょう。
回路の基本要素
抵抗(Resistor)
抵抗は電流に比例した電圧降下を生じる素子で、オームの法則に従います。
$$ V_R = R \cdot I $$
ここで $R$ は抵抗値(単位:$\Omega$, オーム)です。抵抗は電気エネルギーを熱に変換する 散逸素子 であり、消費電力は
$$ P = V_R \cdot I = R I^2 = \frac{V_R^2}{R} $$
で与えられます。
抵抗の逆数 $G = 1/R$ を コンダクタンス(単位:S, ジーメンス)と呼びます。コンダクタンスを使うと $I = GV$ と書けるため、節点解析で便利です。
コンデンサ(Capacitor)
コンデンサは電荷を蓄える素子で、電圧-電流関係は
$$ I_C = C \frac{dV_C}{dt} $$
ここで $C$ は静電容量(単位:F, ファラド)です。電流は電圧の 時間微分 に比例するため、直流(電圧一定)では電流がゼロになります。つまり、コンデンサは直流を通しません。
蓄えられるエネルギーは
$$ U_C = \frac{1}{2}CV_C^2 $$
です。コンデンサはエネルギーを電場の形で蓄えるため、散逸せず、後で取り出すことができます。
インダクタ(Inductor)
インダクタは磁場にエネルギーを蓄える素子で、電圧-電流関係は
$$ V_L = L \frac{dI_L}{dt} $$
ここで $L$ はインダクタンス(単位:H, ヘンリー)です。電圧は電流の 時間微分 に比例するため、直流(電流一定)では電圧降下がゼロになります。つまり、インダクタは直流に対して導線と同じです。
蓄えられるエネルギーは
$$ U_L = \frac{1}{2}LI_L^2 $$
です。
電圧源と電流源
理想電圧源 は、流れる電流にかかわらず一定の電圧 $V_s$ を供給します。理想電流源 は、両端の電圧にかかわらず一定の電流 $I_s$ を供給します。
実際の電源は内部抵抗を持つため、電圧源は小さな直列抵抗、電流源は大きな並列抵抗を持つモデルで表現されます。
インピーダンスと周波数領域
交流回路では、フェーザ表記と複素インピーダンスを使うと各素子を統一的に扱えます。角周波数 $\omega$ の正弦波入力に対して
| 素子 | インピーダンス $Z$ | 位相関係 |
|---|---|---|
| 抵抗 $R$ | $Z_R = R$ | 電圧と電流は同位相 |
| コンデンサ $C$ | $Z_C = \frac{1}{j\omega C}$ | 電流が電圧より $90^\circ$ 進む |
| インダクタ $L$ | $Z_L = j\omega L$ | 電圧が電流より $90^\circ$ 進む |
この表記を使うと、KCL・KVL・オームの法則がそのまま複素数の世界で成り立ち、交流回路の解析が直流回路と同じ手順で行えます。
基本素子の特性が整理できました。次に、KCLとKVLを系統的に適用する手法として、最も広く使われる 節点電圧法 を学びましょう。
節点電圧法(ノード解析)
概要と方針
回路が複雑になると、KCL と KVL を場当たり的に適用するのは非効率です。節点電圧法(Node Voltage Method) は、各節点の電圧を未知変数として KCL を体系的に適用する方法です。
手順は以下の通りです。
- 基準ノード(グランド) を1つ選ぶ — 通常は最も多くの枝が接続された節点
- 残りの $n – 1$ 個の節点に電圧変数 $v_1, v_2, \dots, v_{n-1}$ を割り当てる(基準ノードの電圧は $0$ V)
- 各節点で KCL を適用し、枝電流を節点電圧の差とコンダクタンスで表す
- 得られた連立方程式を解く
抵抗回路の場合
2つの節点 $i$ と $j$ が抵抗 $R_{ij}$(コンダクタンス $G_{ij} = 1/R_{ij}$)で接続されている場合、節点 $i$ から $j$ に流れる電流は
$$ I_{ij} = \frac{v_i – v_j}{R_{ij}} = G_{ij}(v_i – v_j) $$
節点 $i$ での KCL は、節点 $i$ に接続された全ての枝について
$$ \sum_{j} G_{ij}(v_i – v_j) = I_{s,i} $$
ここで $I_{s,i}$ は節点 $i$ に流入する電流源の電流の合計です。
コンダクタンス行列の構成
上の KCL 方程式を行列形式で書くと
$$ \begin{equation} \bm{G}\bm{v} = \bm{I}_s \end{equation} $$
ここで $\bm{G}$ は コンダクタンス行列、$\bm{v}$ は節点電圧ベクトル、$\bm{I}_s$ は電流源ベクトルです。
コンダクタンス行列 $\bm{G}$ の構成ルールは非常に簡潔です。
- 対角要素 $G_{ii}$:節点 $i$ に接続された全てのコンダクタンスの和(自己コンダクタンス)
- 非対角要素 $G_{ij}$($i \neq j$):節点 $i$ と $j$ を結ぶコンダクタンスの和に 負号 を付けたもの(相互コンダクタンス)
$$ G_{ii} = \sum_{j \neq i} G_{ij}, \quad G_{ij} = G_{ji} = -\frac{1}{R_{ij}} $$
コンダクタンス行列は 対称行列 であり、これは回路の相反性を反映しています。
具体例:3節点回路
下図のような回路を考えます。
R1 R2
v1 --/\/\/-- v2 --/\/\/-- v3
| | |
Is1↑ R3 R4
| | |
GND ---------+------------ GND
3つの節点($v_1$, $v_2$, $v_3$)に対してコンダクタンス行列を構成します。基準ノードはGND($v = 0$)です。
- 節点1:$R_1$ を通じて節点2に接続、電流源 $I_{s1}$ が流入
- 節点2:$R_1$ を通じて節点1に、$R_2$ を通じて節点3に、$R_3$ を通じてGNDに接続
- 節点3:$R_2$ を通じて節点2に、$R_4$ を通じてGNDに接続
コンダクタンス行列は
$$ \bm{G} = \begin{pmatrix} G_1 & -G_1 & 0 \\ -G_1 & G_1 + G_2 + G_3 & -G_2 \\ 0 & -G_2 & G_2 + G_4 \end{pmatrix} $$
ここで $G_k = 1/R_k$ です。電流源ベクトルは
$$ \bm{I}_s = \begin{pmatrix} I_{s1} \\ 0 \\ 0 \end{pmatrix} $$
連立方程式 $\bm{G}\bm{v} = \bm{I}_s$ を解けば、各節点の電圧が求まります。
電圧源の扱い
理想電圧源が含まれる場合は、上の方法を直接適用できません。電圧源は節点間の電圧差を拘束するため、特別な処理が必要です。主に2つの方法があります。
方法1:スーパーノード法 — 電圧源の両端の節点を1つの「スーパーノード」としてまとめ、拘束条件を追加する
方法2:修正節点解析(MNA) — 電圧源に流れる電流を追加の未知変数として導入し、拡大された連立方程式を解く。SPICE シミュレータはこの方法を採用しています
後ほど Python 実装で両方の手法を扱います。
節点電圧法は KCL ベースの手法でした。一方、KVL をベースにした体系的手法もあります。次に、網目電流法を見ましょう。
網目電流法(メッシュ解析)
概要と方針
網目電流法(Mesh Current Method) は、回路の各網目(メッシュ)に仮想的な循環電流(網目電流)を割り当て、KVL を体系的に適用する方法です。
網目(メッシュ) とは、内部に他のループを含まない最小のループのことです。平面回路(配線が交差しない回路)では、網目電流法が特に有効です。
手順は以下の通りです。
- 各網目に時計回り(または反時計回り)の網目電流 $i_1, i_2, \dots, i_m$ を割り当てる
- 各網目に対して KVL を適用する
- 各枝の電流は、その枝を共有する網目電流の和(向きを考慮)で表される
- 得られた連立方程式を解く
抵抗回路の場合
2つの隣接する網目 $i$ と $j$ が抵抗 $R_{ij}$ を共有している場合、KVL方程式は
$$ \sum_j R_{ij}(i_{\text{mesh},i} – i_{\text{mesh},j}) = V_{s,i} $$
ここで $V_{s,i}$ は網目 $i$ の中の電圧源による起電力の合計です。
抵抗行列の構成
行列形式で書くと
$$ \begin{equation} \bm{R}\bm{i}_{\text{mesh}} = \bm{V}_s \end{equation} $$
ここで $\bm{R}$ は 抵抗行列(インピーダンス行列) です。
構成ルールは節点電圧法のコンダクタンス行列と双対的です。
- 対角要素 $R_{ii}$:網目 $i$ に含まれる全ての抵抗の和(自己インピーダンス)
- 非対角要素 $R_{ij}$($i \neq j$):網目 $i$ と $j$ が共有する抵抗に 負号 を付けたもの(相互インピーダンス、両方の網目電流が同じ向きに流れる場合は正号)
具体例:2メッシュ回路
R1 R2
+--/\/\/--+--/\/\/--+
| | |
Vs ↑ R3 R4
| | |
+---------+---------+
2つの網目に時計回りの電流 $i_1$, $i_2$ を割り当てます。
網目1(左ループ)の KVL:
$$ V_s – R_1 i_1 – R_3(i_1 – i_2) = 0 $$
網目2(右ループ)の KVL:
$$ -R_2 i_2 – R_4 i_2 – R_3(i_2 – i_1) = 0 $$
整理すると
$$ \begin{pmatrix} R_1 + R_3 & -R_3 \\ -R_3 & R_2 + R_3 + R_4 \end{pmatrix} \begin{pmatrix} i_1 \\ i_2 \end{pmatrix} = \begin{pmatrix} V_s \\ 0 \end{pmatrix} $$
抵抗行列も対称行列であり、節点電圧法のコンダクタンス行列との双対性が確認できます。
節点電圧法との比較
| 特徴 | 節点電圧法 | 網目電流法 |
|---|---|---|
| 基盤法則 | KCL | KVL |
| 未知変数 | 節点電圧 ($n-1$ 個) | 網目電流 ($m$ 個) |
| 行列 | コンダクタンス行列 $\bm{G}$ | 抵抗行列 $\bm{R}$ |
| 電流源の扱い | 自然 | 追加処理が必要 |
| 電圧源の扱い | 追加処理が必要 | 自然 |
| 適用範囲 | 一般的(非平面回路も可) | 平面回路のみ |
実用上は、節点数が少ない回路では節点電圧法が、メッシュ数が少ない回路では網目電流法が効率的です。回路シミュレータ(SPICE)は節点電圧法(の拡張であるMNA)を採用しています。
2つの系統的手法を学びました。次に、複数の電源を含む回路の解析を簡略化する強力な定理 — 重ね合わせの定理 — を紹介します。
重ね合わせの定理
定理の内容
重ね合わせの定理(Superposition Theorem) は、線形回路に対して成り立つ原理で、次のように述べられます。
複数の独立電源を持つ線形回路において、任意の枝の電圧や電流は、各電源を 1つずつ作用させ(他の電源を無効化して)得られる応答の 総和 に等しい
ここで「電源を無効化する」とは
- 電圧源を無効化 → 短絡に置き換える($V_s = 0$、つまり導線)
- 電流源を無効化 → 開放に置き換える($I_s = 0$、つまり断線)
ということです。
数学的根拠
重ね合わせの定理は、KCL・KVL と線形素子(抵抗、線形制御源)で構成される回路方程式が 線形方程式 であることから直接導かれます。
節点電圧法の方程式 $\bm{G}\bm{v} = \bm{I}_s$ を考えます。2つの電流源 $\bm{I}_{s1}$ と $\bm{I}_{s2}$ がある場合
$$ \bm{G}\bm{v} = \bm{I}_{s1} + \bm{I}_{s2} $$
線形方程式なので
$$ \bm{v} = \bm{G}^{-1}(\bm{I}_{s1} + \bm{I}_{s2}) = \bm{G}^{-1}\bm{I}_{s1} + \bm{G}^{-1}\bm{I}_{s2} = \bm{v}_1 + \bm{v}_2 $$
すなわち、全体の応答 $\bm{v}$ は各電源のみの応答 $\bm{v}_1$、$\bm{v}_2$ の和です。
具体例
電圧源 $V_s = 10$ V と電流源 $I_s = 2$ A を持つ回路で、ある枝の電流 $I_x$ を求める場合
ステップ1: $V_s$ のみ作用($I_s$ を開放)→ $I_{x1}$ を計算
ステップ2: $I_s$ のみ作用($V_s$ を短絡)→ $I_{x2}$ を計算
結果: $I_x = I_{x1} + I_{x2}$
重要な注意点
重ね合わせの定理は 電力には適用できません。電力は $P = I^2 R$ や $P = V^2/R$ のように電圧や電流の 2乗 に比例するため、非線形な量です。
$$ (I_1 + I_2)^2 \neq I_1^2 + I_2^2 $$
電力を求めるには、まず重ね合わせで電流(または電圧)を求め、その後に電力を計算する必要があります。
ここまでで回路解析の理論的基盤が揃いました。次に、Pythonを使ってこれらの手法を実際に実装し、数値的に検証しましょう。
Pythonによる節点電圧法の実装
基本的な3節点回路
まず、節点電圧法のコンダクタンス行列を構成し、連立方程式を解く基本的な実装を行います。
import numpy as np
import matplotlib.pyplot as plt
# --- 3節点回路の節点解析 ---
# 回路構成:
# 節点1 -- R1=2Ω -- 節点2 -- R2=4Ω -- 節点3
# 節点1に電流源 Is=5A が流入
# 節点2 -- R3=6Ω -- GND
# 節点3 -- R4=3Ω -- GND
# 抵抗値
R1 = 2.0 # Ω
R2 = 4.0 # Ω
R3 = 6.0 # Ω
R4 = 3.0 # Ω
# コンダクタンス
G1 = 1 / R1
G2 = 1 / R2
G3 = 1 / R3
G4 = 1 / R4
# コンダクタンス行列の構成
# 対角要素: 節点iに接続された全コンダクタンスの和
# 非対角要素: -(節点iとjを結ぶコンダクタンス)
G = np.array([
[G1, -G1, 0 ],
[-G1, G1+G2+G3, -G2 ],
[0, -G2, G2+G4 ]
])
# 電流源ベクトル(節点への流入を正)
Is = np.array([5.0, 0.0, 0.0])
print("=== コンダクタンス行列 G ===")
print(G)
print(f"\nG は対称行列: {np.allclose(G, G.T)}")
print(f"G の条件数: {np.linalg.cond(G):.2f}")
# 連立方程式を解く: Gv = Is
v = np.linalg.solve(G, Is)
print(f"\n=== 節点電圧 ===")
for i, vi in enumerate(v):
print(f" v{i+1} = {vi:.4f} V")
# 各枝の電流を計算
I_R1 = (v[0] - v[1]) / R1
I_R2 = (v[1] - v[2]) / R2
I_R3 = v[1] / R3
I_R4 = v[2] / R4
print(f"\n=== 枝電流 ===")
print(f" I_R1 (節点1→2): {I_R1:.4f} A")
print(f" I_R2 (節点2→3): {I_R2:.4f} A")
print(f" I_R3 (節点2→GND): {I_R3:.4f} A")
print(f" I_R4 (節点3→GND): {I_R4:.4f} A")
# KCL検証
print(f"\n=== KCL検証 ===")
kcl_node1 = Is[0] - I_R1
kcl_node2 = I_R1 - I_R2 - I_R3
kcl_node3 = I_R2 - I_R4
print(f" 節点1: Is - I_R1 = {kcl_node1:.6f} A (= 0?)")
print(f" 節点2: I_R1 - I_R2 - I_R3 = {kcl_node2:.6f} A (= 0?)")
print(f" 節点3: I_R2 - I_R4 = {kcl_node3:.6f} A (= 0?)")
# 消費電力
P_total = Is[0] * v[0]
P_R1 = I_R1**2 * R1
P_R2 = I_R2**2 * R2
P_R3 = I_R3**2 * R3
P_R4 = I_R4**2 * R4
print(f"\n=== 電力収支 ===")
print(f" 電流源供給電力: {P_total:.4f} W")
print(f" R1消費電力: {P_R1:.4f} W")
print(f" R2消費電力: {P_R2:.4f} W")
print(f" R3消費電力: {P_R3:.4f} W")
print(f" R4消費電力: {P_R4:.4f} W")
print(f" 全抵抗消費電力合計: {P_R1+P_R2+P_R3+P_R4:.4f} W")
print(f" 電力収支誤差: {abs(P_total - (P_R1+P_R2+P_R3+P_R4)):.6f} W")
この計算結果から、節点電圧法の正しさが数値的に確認できます。KCL検証では、3つの節点全てで電流の収支がゼロ(数値誤差の範囲内)となり、電荷保存が満たされています。また、電力収支でも電流源が供給する電力と4つの抵抗が消費する電力が一致しており、エネルギー保存が確認されます。コンダクタンス行列が対称行列であることも、回路の相反性の数学的表現として確認できました。
一般化:任意の抵抗回路の自動解析
次に、回路のトポロジーから自動的にコンダクタンス行列を生成する汎用的な実装を行います。
import numpy as np
import matplotlib.pyplot as plt
class CircuitSolver:
"""節点電圧法による回路解析ソルバー"""
def __init__(self, n_nodes):
"""
n_nodes: 節点数(GNDを除く)
"""
self.n = n_nodes
self.G = np.zeros((n_nodes, n_nodes)) # コンダクタンス行列
self.Is = np.zeros(n_nodes) # 電流源ベクトル
def add_resistor(self, node_i, node_j, R):
"""
抵抗を追加
node_i, node_j: 接続する節点(0=GND, 1〜n)
R: 抵抗値 [Ω]
"""
G = 1.0 / R
# 0はGNDノードなので、1-indexedを0-indexedに変換
i = node_i - 1
j = node_j - 1
if node_i > 0 and node_j > 0:
self.G[i, i] += G
self.G[j, j] += G
self.G[i, j] -= G
self.G[j, i] -= G
elif node_i > 0: # node_j == 0 (GND)
self.G[i, i] += G
elif node_j > 0: # node_i == 0 (GND)
self.G[j, j] += G
def add_current_source(self, node_into, current):
"""
電流源を追加(node_into に流入する電流)
node_into: 電流が流入する節点(1〜n)
current: 電流値 [A]
"""
if node_into > 0:
self.Is[node_into - 1] += current
def solve(self):
"""連立方程式を解いて節点電圧を返す"""
self.v = np.linalg.solve(self.G, self.Is)
return self.v
def get_branch_current(self, node_i, node_j, R):
"""枝電流を計算(node_i → node_j)"""
vi = self.v[node_i - 1] if node_i > 0 else 0.0
vj = self.v[node_j - 1] if node_j > 0 else 0.0
return (vi - vj) / R
# テスト: ホイートストンブリッジ回路
#
# R1=100Ω
# 1 ----/\/\/---- 2
# | |
# | R5=50Ω |
# | 3---/\/\/ |
# | | | |
# | R3=200Ω R4=300Ω
# | | | |
# +--+--------+---+
# GND
# Vs=10V → 節点1に10Vの電圧源
# (電圧源を扱うために、節点1の電圧を10Vに固定するアプローチ)
# 簡略化: 電圧源の代わりに、節点1の電圧をVs=10Vと既知とし、
# 残りの節点について解く
# 節点: 2, 3(節点1は電圧既知)
# ホイートストンブリッジ
R1 = 100.0 # 節点1→節点2
R2 = 150.0 # 節点1→節点3
R3 = 200.0 # 節点2→GND
R4 = 300.0 # 節点3→GND
R5 = 50.0 # 節点2→節点3(ブリッジ抵抗)
Vs = 10.0 # 電圧源
# 節点1の電圧は Vs = 10V(既知)
# 節点2、節点3について KCL を立てる
# 節点2: (Vs-v2)/R1 + (v3-v2)/R5 - v2/R3 = 0
# 節点3: (Vs-v3)/R2 + (v2-v3)/R5 - v3/R4 = 0
G1 = 1/R1; G2 = 1/R2; G3 = 1/R3; G4 = 1/R4; G5 = 1/R5
# 行列形式: Gv = b
G_matrix = np.array([
[G1 + G3 + G5, -G5],
[-G5, G2 + G4 + G5]
])
b = np.array([G1 * Vs, G2 * Vs])
v = np.linalg.solve(G_matrix, b)
v2, v3 = v[0], v[1]
print("=== ホイートストンブリッジ回路 ===")
print(f"Vs = {Vs} V")
print(f"R1={R1}Ω, R2={R2}Ω, R3={R3}Ω, R4={R4}Ω, R5={R5}Ω")
print(f"\n節点電圧:")
print(f" v1 = {Vs:.4f} V (電圧源)")
print(f" v2 = {v2:.4f} V")
print(f" v3 = {v3:.4f} V")
# ブリッジ電圧(検出電圧)
V_bridge = v2 - v3
print(f"\nブリッジ電圧 (v2 - v3) = {V_bridge:.4f} V")
# 平衡条件の確認: R1*R4 == R2*R3 のとき V_bridge = 0
print(f"\n平衡条件: R1*R4 = {R1*R4:.0f}, R2*R3 = {R2*R3:.0f}")
if abs(R1*R4 - R2*R3) < 1e-10:
print("→ ブリッジは平衡状態")
else:
print("→ ブリッジは非平衡状態")
# R3を変化させたときのブリッジ電圧
R3_range = np.linspace(50, 500, 100)
V_bridge_range = []
for R3_var in R3_range:
G3_var = 1/R3_var
G_var = np.array([
[G1 + G3_var + G5, -G5],
[-G5, G2 + G4 + G5]
])
b_var = np.array([G1 * Vs, G2 * Vs])
v_var = np.linalg.solve(G_var, b_var)
V_bridge_range.append(v_var[0] - v_var[1])
V_bridge_range = np.array(V_bridge_range)
# 平衡点
R3_balance = R1 * R4 / R2
print(f"\n平衡条件の R3 = R1*R4/R2 = {R3_balance:.1f} Ω")
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(R3_range, V_bridge_range, 'b-', linewidth=2)
ax.axhline(y=0, color='gray', linestyle='-', linewidth=0.5)
ax.axvline(x=R3_balance, color='r', linestyle='--', alpha=0.7,
label=f'$R_3$ = {R3_balance:.1f} Ω (balanced)')
ax.set_xlabel('$R_3$ [Ω]')
ax.set_ylabel('Bridge Voltage $V_{bridge}$ [V]')
ax.set_title('Wheatstone Bridge: Bridge Voltage vs $R_3$')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('wheatstone_bridge.png', dpi=150, bbox_inches='tight')
plt.show()
このグラフから、ホイートストンブリッジの動作原理が明確に理解できます。$R_3$ が平衡値 $R_3 = R_1 R_4 / R_2 = 200$ $\Omega$ のとき、ブリッジ電圧はちょうどゼロになります(赤い点線)。これが平衡条件 $R_1 R_4 = R_2 R_3$ の数値的な確認です。$R_3$ が平衡値より小さいとブリッジ電圧は正、大きいと負になります。この感度特性(電圧の傾き)が、ホイートストンブリッジがひずみゲージや温度センサの計測に広く使われる理由です。微小な抵抗変化がブリッジ電圧の変化として検出できるのです。
網目電流法のPython実装
2メッシュ回路の解析
網目電流法を実装し、節点電圧法の結果と一致することを確認します。
import numpy as np
import matplotlib.pyplot as plt
# --- 2メッシュ回路の網目電流法 ---
#
# R1=10Ω R2=20Ω
# +---/\/\/---+---/\/\/---+
# | | |
# Vs=12V↑ R3=30Ω R4=40Ω
# | | |
# +-----------+-----------+
#
# メッシュ1(左ループ、時計回り): i1
# メッシュ2(右ループ、時計回り): i2
R1 = 10.0
R2 = 20.0
R3 = 30.0
R4 = 40.0
Vs = 12.0
# 抵抗行列の構成
# メッシュ1のKVL: Vs - R1*i1 - R3*(i1-i2) = 0
# → (R1+R3)*i1 - R3*i2 = Vs
# メッシュ2のKVL: -R2*i2 - R4*i2 - R3*(i2-i1) = 0
# → -R3*i1 + (R2+R3+R4)*i2 = 0
R_matrix = np.array([
[R1 + R3, -R3],
[-R3, R2 + R3 + R4]
])
V_source = np.array([Vs, 0])
# 網目電流を求解
i_mesh = np.linalg.solve(R_matrix, V_source)
print("=== 2メッシュ回路の解析 ===")
print(f"R1={R1}Ω, R2={R2}Ω, R3={R3}Ω, R4={R4}Ω, Vs={Vs}V")
print(f"\n抵抗行列 R:")
print(R_matrix)
print(f"\nR は対称行列: {np.allclose(R_matrix, R_matrix.T)}")
print(f"\n=== 網目電流 ===")
print(f" i1 = {i_mesh[0]:.4f} A")
print(f" i2 = {i_mesh[1]:.4f} A")
# 各枝の電流
I_R1 = i_mesh[0]
I_R2 = i_mesh[1]
I_R3 = i_mesh[0] - i_mesh[1]
I_R4 = i_mesh[1]
print(f"\n=== 枝電流 ===")
print(f" I_R1 = {I_R1:.4f} A")
print(f" I_R2 = {I_R2:.4f} A")
print(f" I_R3 = {I_R3:.4f} A (i1 - i2)")
print(f" I_R4 = {I_R4:.4f} A")
# 各素子の電圧降下
V_R1 = I_R1 * R1
V_R2 = I_R2 * R2
V_R3 = I_R3 * R3
V_R4 = I_R4 * R4
print(f"\n=== 電圧降下 ===")
print(f" V_R1 = {V_R1:.4f} V")
print(f" V_R2 = {V_R2:.4f} V")
print(f" V_R3 = {V_R3:.4f} V")
print(f" V_R4 = {V_R4:.4f} V")
# KVL検証
kvl_mesh1 = Vs - V_R1 - V_R3
kvl_mesh2 = -V_R2 - V_R4 + V_R3
print(f"\n=== KVL検証 ===")
print(f" メッシュ1: Vs - V_R1 - V_R3 = {kvl_mesh1:.6f} V (= 0?)")
print(f" メッシュ2: V_R3 - V_R2 - V_R4 = {kvl_mesh2:.6f} V (= 0?)")
# --- 節点電圧法で同じ回路を解いて検証 ---
# 節点: A(R1とR2の接続点), B(Vsの+端子)
# GND = Vsの-端子 = R3,R4の下端
# 節点Bの電圧 = Vs = 12V(既知)
# 節点Aについて KCL: (VB-VA)/R1 + (0-VA)/R3... ではなく
# 節点Aの KCL: (Vs-vA)/R1 - vA/R3 - vA/(R2+R4) ... ではなく
# 正しい節点設定:
# 節点1 = R1とR3とR2の接続点(上辺中央)
# GND = 下辺
# Vs の + 端子 = 節点2(左上)
# R2 + R4 の先 = 右下 = GND
# 再整理:
# 節点A(上辺中央): R1を通じてVs+端子に接続、R2を通じてGNDに接続(R2+R4直列)
# いや、より正確には:
# 上辺: [Vs+]--R1--[A]--R2--[B]
# 下辺: [Vs-]=[GND]
# A--R3--GND, B--R4--GND
# 節点A: (Vs-vA)/R1 = vA/R3 + (vA-vB)/R2 (不適切)
# ⇒ 正しくは 2節点(A, B):
G1n = 1/R1; G2n = 1/R2; G3n = 1/R3; G4n = 1/R4
Gn = np.array([
[G1n + G2n + G3n, -G2n],
[-G2n, G2n + G4n]
])
bn = np.array([G1n * Vs, 0])
vn = np.linalg.solve(Gn, bn)
print(f"\n=== 節点電圧法による検証 ===")
print(f" vA = {vn[0]:.4f} V")
print(f" vB = {vn[1]:.4f} V")
# 節点電圧法から計算した枝電流
I_R1_node = (Vs - vn[0]) / R1
I_R3_node = vn[0] / R3
I_R2_node = (vn[0] - vn[1]) / R2
I_R4_node = vn[1] / R4
print(f"\n枝電流(節点電圧法):")
print(f" I_R1 = {I_R1_node:.4f} A")
print(f" I_R2 = {I_R2_node:.4f} A")
print(f" I_R3 = {I_R3_node:.4f} A")
print(f" I_R4 = {I_R4_node:.4f} A")
# 両手法の一致確認
print(f"\n=== 両手法の一致確認 ===")
print(f" I_R1 差: {abs(I_R1 - I_R1_node):.2e} A")
print(f" I_R3 差: {abs(I_R3 - I_R3_node):.2e} A")
この結果から、網目電流法と節点電圧法が同一の解を与えることが数値的に検証できました。両手法でのKVL/KCLの検証誤差はいずれも $10^{-15}$ A(または V)のオーダーであり、これは浮動小数点演算の丸め誤差の範囲内です。このように、2つの異なるアプローチ(KCLベースとKVLベース)が同じ物理系を矛盾なく記述していることは、キルヒホッフの法則の内部整合性を示す重要な結果です。
重ね合わせの定理の検証
重ね合わせの定理をPythonで数値的に検証します。
import numpy as np
import matplotlib.pyplot as plt
# --- 重ね合わせの定理の検証 ---
#
# 回路: 2つの電源を持つ回路
# R1=10Ω R2=20Ω
# +---/\/\/---A---/\/\/---+
# | |
# Vs=12V↑ R3=15Ω ↑Is=0.5A
# | | |
# +------+------+---------+
# GND
#
# 節点A から R3 を通じてGNDに接続
R1 = 10.0
R2 = 20.0
R3 = 15.0
Vs = 12.0
Is = 0.5
# === 全電源同時作用 ===
# 節点A の KCL: (Vs-vA)/R1 + Is - vA/R3 - vA/R2 = 0
# ⇒ vA*(1/R1 + 1/R2 + 1/R3) = Vs/R1 + Is
G_total = 1/R1 + 1/R2 + 1/R3
b_total = Vs/R1 + Is
vA_total = b_total / G_total
print("=== 全電源同時作用 ===")
print(f" vA = {vA_total:.4f} V")
print(f" I_R1 = {(Vs - vA_total)/R1:.4f} A")
print(f" I_R2 = {vA_total/R2:.4f} A")
print(f" I_R3 = {vA_total/R3:.4f} A")
# === Vs のみ作用(Is を開放)===
b_vs_only = Vs/R1
vA_vs = b_vs_only / G_total
print(f"\n=== Vs のみ作用 (Is = 0) ===")
print(f" vA(Vs) = {vA_vs:.4f} V")
# === Is のみ作用(Vs を短絡)===
# Vs を短絡 → R1 はGNDに直接接続
G_is_only = 1/R1 + 1/R2 + 1/R3 # 同じG行列(Vsの影響はbのみ)
b_is_only = Is
vA_is = b_is_only / G_is_only
print(f"\n=== Is のみ作用 (Vs = 0) ===")
print(f" vA(Is) = {vA_is:.4f} V")
# === 重ね合わせの検証 ===
vA_superposition = vA_vs + vA_is
print(f"\n=== 重ね合わせの検証 ===")
print(f" vA(Vs) + vA(Is) = {vA_vs:.4f} + {vA_is:.4f} = {vA_superposition:.4f} V")
print(f" vA(全電源) = {vA_total:.4f} V")
print(f" 誤差: {abs(vA_total - vA_superposition):.2e} V")
# 電力が重ね合わせに従わないことの確認
P_R3_total = (vA_total)**2 / R3
P_R3_vs = (vA_vs)**2 / R3
P_R3_is = (vA_is)**2 / R3
print(f"\n=== 電力の重ね合わせ(成り立たない!)===")
print(f" P_R3(全電源) = {P_R3_total:.4f} W")
print(f" P_R3(Vs) + P_R3(Is) = {P_R3_vs:.4f} + {P_R3_is:.4f} = {P_R3_vs + P_R3_is:.4f} W")
print(f" 差異: {abs(P_R3_total - (P_R3_vs + P_R3_is)):.4f} W")
print(f" → 電力は重ね合わせに従わない!")
# 可視化: 各電源の寄与
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 節点電圧の分解
labels = ['$V_s$ only', '$I_s$ only', 'Total']
voltages = [vA_vs, vA_is, vA_total]
colors_bar = ['tab:blue', 'tab:orange', 'tab:green']
bars = axes[0].bar(labels, voltages, color=colors_bar, alpha=0.8, edgecolor='black')
axes[0].set_ylabel('Node Voltage $v_A$ [V]')
axes[0].set_title('Superposition of Node Voltage')
axes[0].grid(True, alpha=0.3, axis='y')
# 数値ラベル
for bar, v in zip(bars, voltages):
axes[0].text(bar.get_x() + bar.get_width()/2, v + 0.1, f'{v:.2f} V',
ha='center', fontsize=11, fontweight='bold')
# 積み上げ棒グラフ(重ね合わせの図解)
axes[0].bar(['Superposition'], [vA_vs], color='tab:blue', alpha=0.5, label='$V_s$ contribution')
axes[0].bar(['Superposition'], [vA_is], bottom=[vA_vs], color='tab:orange', alpha=0.5,
label='$I_s$ contribution')
axes[0].legend()
# 電力の比較(重ね合わせが成り立たないことを示す)
labels_p = ['$V_s$ only', '$I_s$ only', 'Sum', 'Actual']
powers = [P_R3_vs, P_R3_is, P_R3_vs + P_R3_is, P_R3_total]
colors_p = ['tab:blue', 'tab:orange', 'tab:red', 'tab:green']
bars_p = axes[1].bar(labels_p, powers, color=colors_p, alpha=0.8, edgecolor='black')
axes[1].set_ylabel('Power in $R_3$ [W]')
axes[1].set_title('Power Does NOT Obey Superposition')
axes[1].grid(True, alpha=0.3, axis='y')
for bar, p in zip(bars_p, powers):
axes[1].text(bar.get_x() + bar.get_width()/2, p + 0.02, f'{p:.3f} W',
ha='center', fontsize=10, fontweight='bold')
plt.tight_layout()
plt.savefig('superposition_verification.png', dpi=150, bbox_inches='tight')
plt.show()
この結果から、重ね合わせの定理の2つの重要なポイントが確認できます。左のグラフでは、$V_s$ のみの応答と $I_s$ のみの応答の和が、全電源同時作用の結果と完全に一致しています。積み上げ棒グラフでも、2つの寄与の足し合わせが全体と等しいことが視覚的にわかります。一方、右のグラフは 電力には重ね合わせが成り立たない ことを明確に示しています。各電源単独での $R_3$ の消費電力の和(赤い棒)は、実際の消費電力(緑の棒)と異なります。これは電力が電圧の2乗に比例する非線形量であるためで、$(v_1 + v_2)^2 \neq v_1^2 + v_2^2$ という基本的な代数的事実を反映しています。
交流回路への拡張
キルヒホッフの法則は交流回路にもそのまま適用できます。フェーザ表記と複素インピーダンスを使ったRLC直列回路の解析を実装します。
import numpy as np
import matplotlib.pyplot as plt
# --- RLC直列回路のインピーダンス解析 ---
# KVL: Vs = V_R + V_L + V_C = I*(R + jωL + 1/(jωC))
R = 100.0 # Ω
L = 10e-3 # H (10 mH)
C = 1e-6 # F (1 μF)
Vs = 10.0 # V (振幅)
# 共振周波数
f0 = 1 / (2 * np.pi * np.sqrt(L * C))
omega0 = 2 * np.pi * f0
print(f"=== RLC直列回路 ===")
print(f"R = {R} Ω, L = {L*1e3} mH, C = {C*1e6} μF")
print(f"共振周波数 f0 = {f0:.1f} Hz")
print(f"共振角周波数 ω0 = {omega0:.1f} rad/s")
# Q値
Q = omega0 * L / R
print(f"Q値 = {Q:.2f}")
# 周波数応答
f_range = np.logspace(2, 5, 1000) # 100 Hz 〜 100 kHz
omega_range = 2 * np.pi * f_range
# 複素インピーダンス(KVL)
Z = R + 1j * omega_range * L + 1 / (1j * omega_range * C)
# 電流フェーザ(KVLから)
I_phasor = Vs / Z
# 各素子の電圧フェーザ
V_R = I_phasor * R
V_L = I_phasor * 1j * omega_range * L
V_C = I_phasor / (1j * omega_range * C)
# KVL検証: Vs = V_R + V_L + V_C
kvl_check = np.max(np.abs(Vs - (V_R + V_L + V_C)))
print(f"\nKVL検証(最大誤差): {kvl_check:.2e} V")
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 電流の大きさ(周波数応答)
axes[0, 0].semilogx(f_range, np.abs(I_phasor) * 1e3, 'b-', linewidth=2)
axes[0, 0].axvline(x=f0, color='r', linestyle='--', alpha=0.7, label=f'$f_0$ = {f0:.0f} Hz')
axes[0, 0].set_xlabel('Frequency [Hz]')
axes[0, 0].set_ylabel('|I| [mA]')
axes[0, 0].set_title('Current Magnitude (Frequency Response)')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3, which='both')
# インピーダンスの大きさと位相
axes[0, 1].semilogx(f_range, np.abs(Z), 'g-', linewidth=2, label='|Z|')
axes[0, 1].set_xlabel('Frequency [Hz]')
axes[0, 1].set_ylabel('|Z| [Ω]')
axes[0, 1].set_title('Impedance Magnitude')
axes[0, 1].axvline(x=f0, color='r', linestyle='--', alpha=0.7)
axes[0, 1].axhline(y=R, color='gray', linestyle=':', alpha=0.7, label=f'R = {R} Ω')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3, which='both')
# 各素子の電圧振幅
axes[1, 0].semilogx(f_range, np.abs(V_R), 'b-', linewidth=1.5, label='$|V_R|$')
axes[1, 0].semilogx(f_range, np.abs(V_L), 'r-', linewidth=1.5, label='$|V_L|$')
axes[1, 0].semilogx(f_range, np.abs(V_C), 'g-', linewidth=1.5, label='$|V_C|$')
axes[1, 0].axhline(y=Vs, color='gray', linestyle=':', alpha=0.7, label=f'$V_s$ = {Vs} V')
axes[1, 0].axvline(x=f0, color='gray', linestyle='--', alpha=0.5)
axes[1, 0].set_xlabel('Frequency [Hz]')
axes[1, 0].set_ylabel('Voltage [V]')
axes[1, 0].set_title('Voltage Across Each Element')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3, which='both')
axes[1, 0].set_yscale('log')
# 共振時のフェーザ図
omega_res = omega0
I_res = Vs / (R + 1j * omega_res * L + 1 / (1j * omega_res * C))
VR_res = I_res * R
VL_res = I_res * 1j * omega_res * L
VC_res = I_res / (1j * omega_res * C)
ax_phasor = axes[1, 1]
origin = [0, 0]
# 電圧フェーザを描画
phasors = {
'$V_R$': (VR_res, 'blue'),
'$V_L$': (VL_res, 'red'),
'$V_C$': (VC_res, 'green'),
'$V_s$': (Vs + 0j, 'black')
}
for label, (V, color) in phasors.items():
ax_phasor.annotate('', xy=(V.real, V.imag), xytext=(0, 0),
arrowprops=dict(arrowstyle='->', color=color, lw=2))
ax_phasor.text(V.real * 1.1, V.imag * 1.1 + 0.3, label, color=color,
fontsize=11, fontweight='bold')
ax_phasor.set_xlim(-12, 12)
ax_phasor.set_ylim(-12, 12)
ax_phasor.set_xlabel('Real [V]')
ax_phasor.set_ylabel('Imaginary [V]')
ax_phasor.set_title(f'Phasor Diagram at Resonance (f = {f0:.0f} Hz)')
ax_phasor.set_aspect('equal')
ax_phasor.grid(True, alpha=0.3)
ax_phasor.axhline(y=0, color='gray', linewidth=0.5)
ax_phasor.axvline(x=0, color='gray', linewidth=0.5)
plt.tight_layout()
plt.savefig('rlc_circuit_analysis.png', dpi=150, bbox_inches='tight')
plt.show()
# 共振時の数値
print(f"\n=== 共振時 (f = {f0:.0f} Hz) ===")
print(f" |I| = {np.abs(I_res)*1e3:.2f} mA (= Vs/R = {Vs/R*1e3:.2f} mA)")
print(f" |V_R| = {np.abs(VR_res):.4f} V (= Vs = {Vs} V)")
print(f" |V_L| = {np.abs(VL_res):.4f} V (= Q*Vs = {Q*Vs:.4f} V)")
print(f" |V_C| = {np.abs(VC_res):.4f} V (= Q*Vs = {Q*Vs:.4f} V)")
print(f" V_L + V_C = {np.abs(VL_res + VC_res):.2e} V (互いに打ち消し)")
この結果からRLC直列回路の共振特性が包括的に理解できます。左上の電流応答では、共振周波数 $f_0$ で電流が最大になり、そこから離れるにつれて急速に減少しています。右上のインピーダンスは共振時に最小値 $R$ をとり、これは $\omega L = 1/(\omega C)$ でインダクタとコンデンサのリアクタンスが打ち消し合うためです。左下の各素子の電圧を見ると、共振時にインダクタとコンデンサの電圧が電源電圧 $V_s$ の $Q$ 倍に達する 電圧共振 が起きていることがわかります。$Q = 1.59$ の場合、$V_L$ と $V_C$ はそれぞれ約 15.9 V で、10 V の電源電圧を超えています。しかし右下のフェーザ図が示すように、$V_L$ と $V_C$ は位相が正反対(180度の位相差)で完全に打ち消し合うため、$V_R = V_s$ となりKVLが成立しています。
大規模回路の節点解析
最後に、ラダー型RC回路(はしご回路)の節点解析を行い、行列法のスケーラビリティを確認します。
import numpy as np
import matplotlib.pyplot as plt
def build_rc_ladder(n_stages, R, C, omega):
"""
n段RCラダー回路のコンダクタンス行列を生成
各段: 直列抵抗R + 並列コンデンサC(GNDへ)
"""
n_nodes = n_stages
G = np.zeros((n_nodes, n_nodes), dtype=complex)
Is = np.zeros(n_nodes, dtype=complex)
Yr = 1.0 / R # 抵抗のアドミタンス
Yc = 1j * omega * C # コンデンサのアドミタンス
for i in range(n_nodes):
# 自己アドミタンス
G[i, i] += Yc # GNDへのコンデンサ
if i > 0:
G[i, i] += Yr # 左の抵抗
G[i, i-1] -= Yr
G[i-1, i] -= Yr
if i == 0:
G[i, i] += Yr # 入力側の抵抗
if i < n_nodes - 1:
G[i, i] += Yr # 右の抵抗
# 入力: 電圧源 Vs = 1V → 節点0に Vs/R の電流を注入
Is[0] = 1.0 / R # Vs = 1V
return G, Is
# パラメータ
n_stages = 10
R = 1e3 # 1 kΩ
C = 10e-9 # 10 nF
# 周波数応答を計算
f_range = np.logspace(1, 6, 500)
transfer_function = np.zeros(len(f_range), dtype=complex)
for idx, f in enumerate(f_range):
omega = 2 * np.pi * f
G, Is = build_rc_ladder(n_stages, R, C, omega)
v = np.linalg.solve(G, Is)
transfer_function[idx] = v[-1] # 最終段の電圧
# ボード線図
magnitude_dB = 20 * np.log10(np.abs(transfer_function))
phase_deg = np.degrees(np.angle(transfer_function))
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
# ゲイン
axes[0].semilogx(f_range, magnitude_dB, 'b-', linewidth=2)
axes[0].set_ylabel('Gain [dB]')
axes[0].set_title(f'{n_stages}-Stage RC Ladder Network (R={R/1e3:.0f}kΩ, C={C*1e9:.0f}nF)')
axes[0].grid(True, alpha=0.3, which='both')
axes[0].axhline(y=-3, color='r', linestyle='--', alpha=0.5, label='-3 dB')
# -3dBカットオフ周波数の推定
idx_3dB = np.argmin(np.abs(magnitude_dB - (-3)))
f_3dB = f_range[idx_3dB]
axes[0].axvline(x=f_3dB, color='r', linestyle=':', alpha=0.5)
axes[0].text(f_3dB * 1.2, -3 + 2, f'$f_{{-3dB}}$ = {f_3dB:.0f} Hz', color='r', fontsize=10)
axes[0].legend()
# 理想的な1段RCの周波数応答(比較用)
f_1stage = 1 / (2 * np.pi * R * C)
H_1stage = 1 / (1 + 1j * f_range / f_1stage)
axes[0].semilogx(f_range, 20 * np.log10(np.abs(H_1stage)), 'g--', linewidth=1.5,
label='1-stage RC', alpha=0.7)
axes[0].legend()
# 位相
axes[1].semilogx(f_range, phase_deg, 'r-', linewidth=2)
axes[1].set_xlabel('Frequency [Hz]')
axes[1].set_ylabel('Phase [degrees]')
axes[1].grid(True, alpha=0.3, which='both')
plt.tight_layout()
plt.savefig('rc_ladder_bode.png', dpi=150, bbox_inches='tight')
plt.show()
# 行列サイズとスケーラビリティ
print(f"\n=== 行列のスケーラビリティ ===")
for n in [5, 10, 20, 50, 100]:
import time
omega = 2 * np.pi * 1000
G_test, Is_test = build_rc_ladder(n, R, C, omega)
t_start = time.perf_counter()
v_test = np.linalg.solve(G_test, Is_test)
t_end = time.perf_counter()
print(f" {n:3d}段: 行列サイズ {n}x{n}, 計算時間 {(t_end-t_start)*1e6:.1f} μs")
print(f"\n=== カットオフ周波数 ===")
print(f" 1段RC: f_c = {f_1stage:.0f} Hz")
print(f" {n_stages}段ラダー: f_-3dB ≈ {f_3dB:.0f} Hz")
ボード線図から、RCラダー回路の低域通過フィルタ特性が確認できます。上段のゲイン特性を見ると、10段ラダーは1段RC回路(緑の点線)よりもはるかに急峻なロールオフを持ち、高周波側で約 $-20n$ dB/decade($n$ は段数)の減衰を示しています。-3 dBカットオフ周波数は1段RCの $f_c = 1/(2\pi RC)$ よりも低い値にシフトしており、各段の負荷効果により帯域幅が狭くなっています。下段の位相特性は、最大で $-n \times 90^\circ$(10段なら $-900^\circ$)まで回転します。スケーラビリティの検証結果では、100段の回路(100次元の連立方程式)でもマイクロ秒オーダーで解けており、節点電圧法の行列アプローチが大規模回路にも実用的であることが確認できました。
まとめ
本記事では、キルヒホッフの法則から回路解析の体系的手法までを解説しました。
- キルヒホッフの電流則(KCL): 任意の節点に流入する電流の総和はゼロ。電荷保存則の直接的な帰結であり、$\sum I_k = 0$ として定式化される
- キルヒホッフの電圧則(KVL): 任意の閉路に沿った電圧降下の総和はゼロ。エネルギー保存則(保存力場の性質)の帰結であり、$\sum V_k = 0$ として定式化される
- 節点電圧法: KCL をベースに、コンダクタンス行列 $\bm{G}\bm{v} = \bm{I}_s$ の連立方程式として回路を定式化する。SPICE シミュレータの基盤
- 網目電流法: KVL をベースに、抵抗行列 $\bm{R}\bm{i} = \bm{V}_s$ の連立方程式として回路を定式化する。平面回路に適用可能
- 重ね合わせの定理: 線形回路では各電源の応答の和が全体の応答に等しい。ただし電力には適用できない
- 交流回路への拡張: フェーザ表記と複素インピーダンスにより、KCL・KVL がそのまま交流回路に適用できる
キルヒホッフの法則は「電荷保存」と「エネルギー保存」という物理学の最も基本的な原理から導かれます。わずか2つの法則ですが、節点電圧法や網目電流法と組み合わせることで、任意の複雑さの回路を体系的に解析できる強力な枠組みを提供しています。
次のステップとして、以下の記事も参考にしてください。