<style> .markdown-body table{ display: unset; } </style> # 克希荷夫定律與矩陣運算 > 作者:王一哲 > 日期:2024年7月8日 <br /> ## 原理 當電路上只有電池與電阻器,但又不是單純的串聯、並聯電路時,可以利用**克希荷夫定律 (Kirchhoff's circuit laws)** 求所有的分支電流。克希荷夫定律有兩個定則 1. **節點定則**:對於電路上的某個節點而言,流入、流出的電流量值相等,實際上就是**電荷守恆**。 2. **迴路定則**:對於電路上的某個迴路而言,繞一圈回到出發點時電位相等,實際上就是**能量守恆**。 下圖是 [WikiPedia 克希荷夫定律英文版條目](https://en.wikipedia.org/wiki/Kirchhoff%27s_circuit_laws)的例子 <img height="40%" width="40%" src="https://upload.wikimedia.org/wikipedia/commons/7/77/Kirshhoff-example.svg" style="display: block; margin-left: auto; margin-right: auto;"/> <div style="text-align:center">圖片來源:<a href="https://upload.wikimedia.org/wikipedia/commons/7/77/Kirshhoff-example.svg" target="_blank">https://upload.wikimedia.org/wikipedia/commons/7/77/Kirshhoff-example.svg</a></div> <br /><br /> 由圖中右方的節點可得 $$ i_1 = i_2 + i_3 $$ 由上方的迴路可得 $$ \varepsilon_1 - i_1 R_1 - i_2 R_2 = 0 $$ 由下方的迴路可得 $$ -i_3 R_3 - \varepsilon_2 - \varepsilon_1 + i_2 R_2 = 0 $$ 整理以上3式可得 $$ \begin{cases} i_1 - i_2 - i_3 = 0 \\ i_1 R_1 - i_2 R_2 = \varepsilon_1 \\ i_2 R_2 - i_3 R_3 = \varepsilon_1 + \varepsilon_2 \end{cases} ~\Rightarrow~ \begin{cases} 1 \cdot i_1 + (-1) \cdot i_2 + (-1) \cdot i_3 = 0 \\ R_1 \cdot i_1 + (-R_2) \cdot i_2 + 0 \cdot i_3 = \varepsilon_1 \\ 0 \cdot i_1 + R_2 \cdot i_2 + (-R_3) \cdot i_3 = \varepsilon_1 + \varepsilon_2 \end{cases} $$ 如果轉換成矩陣型式 $$ \begin{bmatrix} 1 & -1 & -1 \\ R_1 & R_2 & 0 \\ 0 & R_2 & -R_3 \end{bmatrix} \begin{bmatrix} i_1 \\ i_2 \\ i_3 \end{bmatrix} = \begin{bmatrix} 0 \\ \varepsilon_1 \\ \varepsilon_1 + \varepsilon_2 \end{bmatrix} $$ 假設代入的數值分別為 $$ R_1 = 100 ~\mathrm{\Omega},~ R_2 = 200 ~\mathrm{\Omega},~ R_3 = 300 ~\mathrm{\Omega},~ \varepsilon_1 = 3 ~\mathrm{V},~ \varepsilon_2 = 4 ~\mathrm{V} $$ 電流分別為 $$ i_1 = \frac{1}{1100} ~\mathrm{A},~ i_2 = \frac{4}{275}~\mathrm{A},~ i_3 = -\frac{3}{220} ~\mathrm{A} $$ <br /><br /> ## 矩陣運算 假設要解的未知數有 $n$ 個,維度為 $n \times n$ 的矩陣 $A$ 是聯立方程式中每一個未知數的係數,維度為 $n \times 1$ 的矩陣 $x$ 是未知數,維度為 $n \times 1$ 的矩陣 $B$ 是每一列方程式對應的常數項。第一種計算方法是利用 $A$ 的反矩陣 $A^{-1}$,計算過程如下: $$ Ax = B ~\Rightarrow~ A^{-1}Ax = A^{-1}B ~\Rightarrow~ x = A^{-1} B $$ 以上面的電路圖為例, $$ A = \begin{bmatrix} 1 & -1 & -1 \\ 100 & 200 & 0 \\ 0 & 200 & -300 \end{bmatrix} ~~~~~ A^{-1} = \begin{bmatrix} \frac{6}{11} & \frac{1}{220} & -\frac{1}{550} \\ -\frac{3}{11} & \frac{3}{1100} & \frac{1}{1100} \\ -\frac{2}{11} & \frac{1}{550} & -\frac{3}{1100} \end{bmatrix} $$ 高中數學課本上比較常見的反矩陣求法,是將矩陣 $A$ 與對應的單位矩陣 $I$ 左、右並列,再用高斯消去法,將左側的矩陣化為單位矩陣,此時右側就是反矩陣 $A^{-1}$,計算過程如下 $$ \begin{bmatrix} 1 & -1 & -1 & | & 1 & 0 & 0 \\ 100 & 200 & 0 & | & 0 & 1 & 0 \\ 0 & 200 & -300 & | & 0 & 0 & 1 \end{bmatrix} $$ 先將第2、3列除以100可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 1 & 0 & 0 \\ 1 & 2 & 0 & | & 0 & \frac{1}{100} & 0 \\ 0 & 2 & -3 & | & 0 & 0 & \frac{1}{100} \end{bmatrix} $$ 將第1列乘以 $-1$ 加到第2列可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 1 & 0 & 0 \\ 0 & 3 & 1 & | & -1 & \frac{1}{100} & 0 \\ 0 & 2 & -3 & | & 0 & 0 & \frac{1}{100} \end{bmatrix} $$ 將第2列乘以 $-\frac{2}{3}$ 加到第3列可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 1 & 0 & 0 \\ 0 & 3 & 1 & | & -1 & \frac{1}{100} & 0 \\ 0 & 0 & -\frac{11}{3} & | & \frac{2}{3} & -\frac{1}{150} & \frac{1}{100} \end{bmatrix} $$ 將第2列乘以 $\frac{1}{3}$、第3列乘以 $-\frac{3}{11}$ 可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 1 & 0 & 0 \\ 0 & 1 & \frac{1}{3} & | & -\frac{1}{3} & \frac{1}{300} & 0 \\ 0 & 0 & 1 & | & -\frac{2}{11} & \frac{1}{550} & -\frac{3}{1100} \end{bmatrix} $$ 將第3列乘以 $-\frac{1}{3}$ 加到第2列、第3列加到第1列可得 $$ \begin{bmatrix} 1 & -1 & 0 & | & \frac{9}{11} & \frac{1}{550} & -\frac{3}{1100} \\ 0 & 1 & 0 & | & -\frac{3}{11} & \frac{3}{1100} & \frac{1}{1100} \\ 0 & 0 & 1 & | & -\frac{2}{11} & \frac{1}{550} & -\frac{3}{1100} \end{bmatrix} $$ 將第2列加到第1列可得 $$ \begin{bmatrix} 1 & 0 & 0 & | & \frac{6}{11} & \frac{1}{220} & -\frac{1}{550} \\ 0 & 1 & 0 & | & -\frac{3}{11} & \frac{3}{1100} & \frac{1}{1100} \\ 0 & 0 & 1 & | & -\frac{2}{11} & \frac{1}{550} & -\frac{3}{1100} \end{bmatrix} $$ 接下來再將 $A^{-1}$ 乘以 $B$ 即可求解。 $$A^{-1}B = \begin{bmatrix} \frac{6}{11} & \frac{1}{220} & -\frac{1}{550} \\ -\frac{3}{11} & \frac{3}{1100} & \frac{1}{1100} \\ -\frac{2}{11} & \frac{1}{550} & -\frac{3}{1100} \end{bmatrix} \begin{bmatrix} 0 \\ 3 \\ 7 \end{bmatrix} = \begin{bmatrix} \frac{1}{1100} \\ \frac{4}{275} \\ -\frac{3}{220} \end{bmatrix} $$ 高中數學課本上比較常見的寫法是將要解的矩陣 $A$ 與每一列對應的常數項 $B$ 左右並列,再用高斯消去法,將左側的矩陣化為單位矩陣,此時右側的矩陣就是對應的解。計算過程如下: $$ \begin{bmatrix} 1 & -1 & -1 & | & 0 \\ 100 & 200 & 0 & | & 3 \\ 0 & 200 & -300 & | & 7 \end{bmatrix} $$ 先將第2、3列除以100可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 0 \\ 1 & 2 & 0 & | & \frac{3}{100} \\ 0 & 2 & -3 & | & \frac{7}{100} \end{bmatrix} $$ 將第1列乘以 $-1$ 加到第2列可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 0 \\ 0 & 3 & 1 & | & \frac{3}{100} \\ 0 & 2 & -3 & | & \frac{7}{100} \end{bmatrix} $$ 將第2列乘以 $-\frac{2}{3}$ 加到第3列可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 0 \\ 0 & 3 & 1 & | & \frac{3}{100} \\ 0 & 0 & -\frac{11}{3} & | & \frac{1}{20} \end{bmatrix} $$ 將第3列乘以 $-\frac{3}{11}$ 加到第3列可得 $$ \begin{bmatrix} 1 & -1 & -1 & | & 0 \\ 0 & 3 & 1 & | & \frac{3}{100} \\ 0 & 0 & 1 & | & -\frac{3}{220} \end{bmatrix} $$ 將第3列加到第1列,第3列乘以 $-1$ 加到第2列可得 $$ \begin{bmatrix} 1 & -1 & 0 & | & -\frac{3}{220} \\ 0 & 3 & 0 & | & \frac{12}{275} \\ 0 & 0 & 1 & | & -\frac{3}{220} \end{bmatrix} $$ 將第2列除以3可得 $$ \begin{bmatrix} 1 & -1 & 0 & | & -\frac{3}{220} \\ 0 & 1 & 0 & | & \frac{4}{275} \\ 0 & 0 & 1 & | & -\frac{3}{220} \end{bmatrix} $$ 最後將第2列加到第1列可得 $$ \begin{bmatrix} 1 & 0 & 0 & | & \frac{1}{1100} \\ 0 & 1 & 0 & | & \frac{4}{275} \\ 0 & 0 & 1 & | & -\frac{3}{220} \end{bmatrix} $$ <br /><br /> ## 使用 Python 的 NumPy 套件求解 NumPy 有現成的函式 numpy.linalg.solve 可以求解,也可以先用 numpy.linalg.inv 求反矩陣 $A^{-1}$,再用矩陣乘法計算 $A^{-1} B = x$。 ```python= import numpy as np A = np.asarray([[1, -1, -1], [100, 200, 0], [0, 200, -300]]) B = np.asarray([[0], [3], [7]]) print(np.linalg.solve(A, B)) # 使用 numpy.linalg.solve 求解 AI = np.linalg.inv(A) # 使用 numpy.linalg.inv 求 A 的反矩陣 AI print(AI @ B) # 再用矩陣乘法求解 print(AI.dot(B)) print(np.dot(AI, B)) print(np.matmul(AI, B)) print(1/1100, 4/275, -3/220) # 印出理論值 ``` 輸出為 ```python # 5種寫法的輸出皆為 [[ 0.00090909] [ 0.01454545] [-0.01363636]] # 理論值 0.0009090909090909091 0.014545454545454545 -0.013636363636363636 ``` <br /><br /> ## 使用 Python 但不使用 NumPy 套件求解 以下的自訂函式可以參考前一篇文章〈[使用 Python 及 C++ 計算矩陣乘法、反矩陣](https://hackmd.io/@yizhewang/S1IUjnUDC)〉,可以得到一樣的答案。 ```python= import copy def matmul(A, B): m, n, p = len(A), len(A[0]), len(B[0]) C = [[0]*p for _ in range(m)] for i in range(m): for j in range(p): for k in range(n): C[i][j] += A[i][k]*B[k][j] return C def inv(A): # 取 A 的列、欄數量,如果不是方陣,回傳 ValueError 並中止程式 n, m = len(A), len(A[0]) if n != m: raise ValueError # 建立右側的單位矩陣 B B = [[0]*n for _ in range(n)] for i in range(n): B[i][i] = 1.0 # 使用 copy.deepcopy 複製 A test = copy.deepcopy(A) # 消除 test 左下角的數值 for i in range(n): # 依序處理第 0 ~ (n-1) 列 coef = test[i][i] # 以對角線上的元素作為係數 for j in range(i+1, n): # 處理第 (i+1) ~ (n-1) 列 mul = -test[j][i] / coef # 計算要將 test[i] 同乘的倍數 mul for k in range(n): # 處理第 0 ~ (n-1) 欄 test[j][k] += mul*test[i][k] # 化簡 test 的左下角 B[j][k] += mul*B[i][k] # 同時處理 B # 消除 test 右下角的數值 for i in range(n-1, -1, -1): # 依序處理第 (n-1) ~ 0 列 coef = test[i][i] # 以對角線上的元素作為係數 for j in range(i-1, -1, -1): # 處理第 (i-1) ~ (0) 列 mul = -test[j][i] / coef # 計算要將 test[i] 同乘的倍數 mul for k in range(n-1, -1, -1): # 處理第 (n-1) ~ 0 欄 test[j][k] += mul*test[i][k] # 化簡 test 的右下角 B[j][k] += mul*B[i][k] # 同時處理 B # 將 test 對角線上的元素都變為1 for i in range(n): # 依序處理第 0 ~ (n-1) 列 coef = test[i][i] # 以對角線上的元素作為係數 test[i][i] /= coef # 處理對角線上的元素 for j in range(n): # 依序處理 B 的第 0 ~ (n-1) 欄 B[i][j] /= coef # 回傳反矩陣 B return B if __name__ == "__main__": A = [[1, -1, -1], [100, 200, 0], [0, 200, -300]] B = [[0], [3], [7]] AI = inv(A) x = matmul(AI, B) print(x) ``` 輸出為 ```python [[0.000909090909090908], [0.014545454545454545], [-0.013636363636363637]] ``` <br /><br /> ## 使用 C++ 求解 以下的自訂函式可以參考前一篇文章〈[使用 Python 及 C++ 計算矩陣乘法、反矩陣](https://hackmd.io/@yizhewang/S1IUjnUDC)〉,可以得到一樣的答案。 ```cpp= #include <iostream> #include <vector> using namespace std; void inv(vector<vector<float>> &A, vector<vector<float>> &AI) { int n = (int)A.size(); // A 的列、欄數量 for(int i=0; i<n; i++) AI[i][i] = 1.0; // 將 AI 的對角線元素都設定為 1.0 vector<vector<float>> test (A.begin(), A.end()); // 複製 A 的資料到 test /* 消除 test 左下角的數值 */ for(int i=0; i<n; i++) { // 依序處理第 0 ~ (n-1) 列 float coef = test[i][i]; // 以對角線上的元素作為係數 for(int j=i+1; j<n; j++) { // 處理第 (i+1) ~ (n-1) 列 float mul = -test[j][i] / coef; // 計算要將 test[i] 同乘的倍數 mul for(int k=0; k<n; k++) { // 處理第 0 ~ (n-1) 欄 test[j][k] += mul*test[i][k]; // 化簡 test 的左下角 AI[j][k] += mul*AI[i][k]; // 同時處理 AI } } } /* 消除 test 右上角的數值 */ for(int i=n-1; i>=0; i--) { // 依序處理第 (n-1) ~ 0 列 float coef = test[i][i]; // 以對角線上的元素作為係數 for(int j=i-1; j>=0; j--) { // 處理第 (i-1) ~ (0) 列 float mul = -test[j][i] / coef; // 計算要將 A[i] 同乘的倍數 mul for(int k=n-1; k>=0; k--) { // 處理第 (n-1) ~ 0 欄 test[j][k] += mul*test[i][k]; // 化簡 test 的右下角 AI[j][k] += mul*AI[i][k]; // 同時處理 AI } } } /* 將 test 對角線上的元素都變為1 */ for(int i=0; i<n; i++) { // 依序處理第 0 ~ (n-1) 列 float coef = test[i][i]; // 以對角線上的元素作為係數 test[i][i] /= coef; // 處理對角線上的元素 for(int j=0; j<n; j++) AI[i][j] /= coef; // 依序處理 AI 的第 0 ~ (n-1) 欄 } } void matmul(vector<vector<float>> &A, vector<vector<float>> &B, vector<vector<float>> &x) { size_t m = A.size(), n = A[0].size(), p = B[0].size(); for(size_t i=0; i<m; i++) for(size_t j=0; j<p; j++) for(size_t k=0; k<n; k++) x[i][j] += A[i][k]*B[k][j]; } int main() { vector<vector<float>> A = {{1.0, -1.0, -1.0}, {100.0, 200.0, 0.0}, {0.0, 200.0, -300.0}}; vector<vector<float>> B = {{0.0}, {3.0}, {7.0}}; vector<vector<float>> AI (A.size(), vector<float> (A[0].size(), 0.0)); vector<vector<float>> x (B.size(), vector<float> (B[0].size(), 0.0)); inv(A, AI); matmul(AI, B, x); for(size_t i=0; i<x.size(); i++) for(size_t j=0; j<x[0].size(); j++) cout << x[i][j] << " \n"[j == x[0].size()-1]; return 0; } ``` 輸出為 ```cpp 0.000909091 0.0145455 -0.0136364 ``` <br /><br /> ### 執行時間 我在 Ubuntu 指令介面中使用 time 測試這幾個寫法的執行時間,很意外地發現用 Python list 的寫法執行速度居然有點快,C++ 的部分是測試編輯後的可執行檔,速度明顯快很多。 <div style="text-align:center"> <table> | <center>程式碼</center> | real (s) | user (s) | sys (s) | | ----- | -------- | -------- | ------- | | np.linalg.solve(A, B) | 0.153 | 0.432 | 1.230 | | 先用 np.linalg.inv 再用 AI @ B | 0.153 | 0.426 | 1.246 | | 先用 np.linalg.inv 再用 AI.dot(B) | 0.128 | 0.422 | 1.282 | | 先用 np.linalg.inv 再用 np.dot(AI, B) | 0.157 | 0.455 | 1.304 | | 先用 np.linalg.inv 再用 np.matmul(AI, B) | 0.132 | 0.423 | 1.264 | | Python list | 0.014 | 0.014 | 0.000 | | C++ | 0.002 | 0.002 | 0.000 | </table> </div> <br /><br /> ## 參考資料 1. [WikiPedia Kirchhoff's circuit laws](https://en.wikipedia.org/wiki/Kirchhoff%27s_circuit_laws) 2. [numpy.linalg.solve](https://numpy.org/doc/stable/reference/generated/numpy.linalg.solve.html) 3. [numpy.linalg.inv](https://numpy.org/doc/stable/reference/generated/numpy.linalg.inv.html) --- ###### tags:`Physics`、`Python`、`C++`、`Math`