Experiment using Matrix Equation in C program - Page 1
Forum section: electronics / projects

author: JOJOElectro29
author_rank: Contributor
timestamp: May 24, 2025, 12:58:01 pm
content: Hi I have an Experiment where I have to make a C program to calculate nodal voltages using 5x5 Matrix but I can't seem to get the code to work. I used PSIM to simulate the nodal voltages values and attached to desired output. Would appreciate any help Thank You.

author: ledtester
author_rank: Super Contributor
timestamp: May 24, 2025, 05:58:27 pm
content: Some comments:1. You should print out the matrix equation you are solving and compare it with what the PDF gives. For that matter, you should print out the matrix for a simpler circuit so you can hand check the results.2. The way I understand the methodology you have to pick a value for "R" and I'm not seeing that in the program.3. Voltage sources will affect the Y matrix as well as the constants vector and I'm not seeing that.

author: ledtester
author_rank: Super Contributor
timestamp: May 24, 2025, 07:49:33 pm
content: Here is what I get:Code:Equations:0 = 0.001*U0 - 0.001*U5 + 2*U0/R - U1/R - U3/R + 8.3/R0 = 0.003*U1 - 0.001*U2 - 0.001*U4 - 0.001*U5 - U0/R + U1/R - 5.0/R0 = -0.001*U1 + 0.003*U2 - 0.001*U3 - 0.001*U40 = -0.001*U2 + 0.001*U3 - U0/R + U3/R - 3.3/R0 = -0.001*U1 - 0.001*U2 + 0.003*U4 - 0.001*U50 = -0.001*U0 - 0.001*U1 - 0.001*U4 + 0.003*U50 = U00 = R - 0.001Solution:U0 = 0.0U1 = 4.99999617143341U2 = 4.11428383673643U3 = 3.30000081428302U4 = 4.04285452449287U5 = 3.01428356530876R = 0.00100000000000000I've attached the code and you can also run it here:https://python-fiddle.com/saved/acbbb6bc-20b2-4c35-b8ed-2e4a82e5e21cBtw - thanks for bringing up this method. I've never seen it before and it's a pretty slick idea.

author: ledtester
author_rank: Super Contributor
timestamp: May 26, 2025, 05:38:13 am
content: So I've figured out how to modify the method which does not rely on transforming voltage sources into current sources. This enables you to get exact answers (as if there are perfect voltage sources anyway).The key is to combine voltage sources into "supernodes".In the example above, we combine nodes 0, 1 and 3 into a supernode. We will replace the equations:Y = 0, Y = 0 and Y = 0with these:Y+Y+Y = 0, U1-U0 = 5, U3-U0 = 3.3The expression Y+Y+Y represents the current into the supernode {0,1,3}.You can run the modified code here:https://python-fiddle.com/saved/063bbc81-972a-4e52-8f35-64b3f5090820The output:Code:Supernodes: [[0, 1, 3]]Equations:0 = 0.001*U0 + 0.003*U1 - 0.002*U2 + 0.001*U3 - 0.001*U4 - 0.002*U50 = 00 = -0.001*U1 + 0.003*U2 - 0.001*U3 - 0.001*U40 = 00 = -0.001*U1 - 0.001*U2 + 0.003*U4 - 0.001*U50 = -0.001*U0 - 0.001*U1 - 0.001*U4 + 0.003*U50 = -U0 + U1 - 5.00 = -U0 + U3 - 3.30 = U0Solution:U0 = 0.0U1 = 5.00000000000000U2 = 4.11428571428571U3 = 3.30000000000000U4 = 4.04285714285714U5 = 3.01428571428571

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 26, 2025, 06:35:41 am
content: Quote from: ledtester on May 26, 2025, 05:38:13 amIf you installMaxima, you can trivially verify your solution:Code:solve([ U1/R + (U1-U2)/1000 + (U1-U4)/1000 + (U1-U5)/1000 = 5/R,(U2-U1)/1000 + (U2-U3)/1000 + (U2-U4)/1000 = 0,U3/R + (U3-U2)/1000 = 3.3/R,(U4-U2)/1000 + (U4-U5)/1000 + (U4-U1)/1000 = 0,U5/1000 + (U5-U4)/1000 + (U5-U1)/1000 = 0,U1 = 5 ], [ U1, U2, U3, U4, U5, R ]);float(%);There are two solutions, one withR=0.0, and one withR=-2010. Assuming we reject negative resistances, then the mathematically accurate solution to the system of equations isU1=5,U2≃4.114,U3=3.3,U4≃4.043, andU5≃3.014. The values in ledtester's post above match that mathematically accurate result to at least 15 digits of precision.Maxima actually computes the exact solution as a fraction (if you replace=3.3/Rwith=33/(10*R)), and thefloat(%);reprints the solution converting fractions to floating-point numbers. If you want that at a specific number of decimal digitsN, usefpprec:N;andbfloat(%);instead.

author: ledtester
author_rank: Super Contributor
timestamp: May 26, 2025, 07:24:32 am
content: The Maxima solutions are very interesting and it has led me to a simpler algorithm...1) create the equations as before2) for every equation with /R terms, multiply the equation by R and set R = 0; if an equations doesn't have any /R terms leave it alone3) solve the resulting (now linear) system in terms of the voltage variablesMultiplying an equation by R and then setting R to zero is just removing the non-/R terms.For instance, in the above example we would transform the equations as follows:U1/R + (U1-U2)/1000 + (U1-U4)/1000 + (U1-U5)/1000 = 5/R --> U1 = 5(U2-U1)/1000 + (U2-U3)/1000 + (U2-U4)/1000 = 0 -> no /R terms so no changeU3/R + (U3-U2)/1000 = 3.3/R --> U3 = 3.3(U4-U2)/1000 + (U4-U5)/1000 + (U4-U1)/1000 = 0 --> no changeU5/1000 + (U5-U4)/1000 + (U5-U1)/1000 = 0 --> no change

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 26, 2025, 08:08:56 am
content: Sure, should work just fine.In case anyone wonders, we are dealing with the simple system of linear equations, say$$\left \lbrace \, \begin{aligned}a_{11} U_1 + a_{12} U_2 + a_{13} U_3 &= b_1 \\a_{21} U_1 + a_{22} U_2 + a_{23} U_3 &= b_2 \\a_{31} U_1 + a_{32} U_2 + a_{33} U_3 &= b_3 \\\end{aligned} \right .$$which in linear algebra is usually written as$$\mathbf{A} \mathbf{U} = \mathbf{b}$$where$$\mathbf{A} = \left [ \begin{matrix} a_{11} & a_{12} & a_{13} \\a_{21} & a_{22} & a_{23} \\a_{31} & a_{32} & a_{33} \\\end{matrix} \right ], \quad \mathbf{U} = \left[ \begin{matrix} U_1 \\U_2 \\U_3 \\\end{matrix} \right ], \quad \mathbf{b} = \left[ \begin{matrix} b_1 \\b_2 \\b_3 \\\end{matrix} \right ]$$Note that \$\mathbf{U}\$ and \$\mathbf{b}\$ arecolumn vectors, and that \$\mathbf{A}\$ has as many columns as \$\mathbf{U}\$ has elements, and as many rows as \$\mathbf{b}\$ has elements.When \$\mathbf{A}\$ is square, i.e. \$\mathbf{U}\$ and \$\mathbf{b}\$ have the same number of elements, then if a solution exists it is$$\mathbf{U} = \mathbf{A}^{-1} \mathbf{b}$$where \$\mathbf{A}^{-1}\$ is thematrix inverseof \$\mathbf{A}\$.When \$\mathbf{A}\$ is not square, the best solution in the least squares sense is$$\mathbf{U} = \mathbf{A}^{*} \mathbf{b}$$where \$\mathbf{A}^{*}\$ is thepseudoinverse or Moore–Penrose inverseof matrix \$\mathbf{A}\$.The two inverse above link to corresponding Wikipedia articles, which list some of the common methods of computing the solutions. With numpy, you can usenumpy.linalg.inv()andnumpy.linalg.pinv(), respectively. In ones own C code, without any supporting matrix or linear algebra library, follow your course notes, or any of the many numerical computation approaches you find online and in references; but usually matrix inverse exercises are done using Cramer's rule, and pseudoinverse via singular value decomposition.

author: ledtester
author_rank: Super Contributor
timestamp: May 26, 2025, 08:10:12 am
content: I've updated my python code:https://python-fiddle.com/saved/a7996e17-5715-4cae-aab4-f84166f367e7Setting up the equations now boils down to this:Code:U = [ sympy.symbols("U"+str(k)) for k in range(0,Nnodes) ]Y = *NnodesYR = *Nnodesfor type, i, j, value in Circuit:value = float(value)if type == 0: # resistor from node i to node jg = 1/valueY += U*g - U*gY += U*g - U*gelse: # voltage source from i to jYR += U - U + valueYR += U - U - valueeqns = []for i in range(Nnodes):if YR != 0:eqns.append(YR)else:eqns.append(Y)Basically, if a node is part of a voltage source we only use the constraints given by the voltage sources that node is part of. Otherwise the equation for that node is the KCL equation.

author: radiolistener
author_rank: Super Contributor
timestamp: May 26, 2025, 08:33:14 am
content: You can solve system of equations using Octave with symbolic package:Code:pkg load symbolic;% define symbolic variablessyms U0 U1 U2 U3 U4 U5 R;% define system of equationseqns = [0 == 0.001*U0 - 0.001*U5 + 2*U0/R - U1/R - U3/R + 8.3/R,0 == 0.003*U1 - 0.001*U2 - 0.001*U4 - 0.001*U5 - U0/R + U1/R - 5.0/R,0 == -0.001*U1 + 0.003*U2 - 0.001*U3 - 0.001*U4,0 == -0.001*U2 + 0.001*U3 - U0/R + U3/R - 3.3/R,0 == -0.001*U1 - 0.001*U2 + 0.003*U4 - 0.001*U5,0 == -0.001*U0 - 0.001*U1 - 0.001*U4 + 0.003*U5,0 == U0,0 == R - 0.001,];% list of variables to solve forvars = [U0, U1, U2, U3, U4, U5, R];% solve the systemsol = solve(eqns, vars);% print resultsfprintf('U0 = %g\n', double(sol.U0));fprintf('U1 = %g\n', double(sol.U1));fprintf('U2 = %g\n', double(sol.U2));fprintf('U3 = %g\n', double(sol.U3));fprintf('U4 = %g\n', double(sol.U4));fprintf('U5 = %g\n', double(sol.U5));fprintf('R = %g\n', double(sol.R));Result:Code:U0 = 0U1 = 5U2 = 4.11428U3 = 3.3U4 = 4.04285U5 = 3.01428R = 0.001PS: it will spam you with some warnings "passing floating-point values to sym is dangerous", you can fix it by using sym constants instead of floating point values, or just by disable the warnings:Code:warning off all;as alternative, you can also write equations in this way:Code:eqns = [0 == sym('0.001*U0 - 0.001*U5 + 2*U0/R - U1/R - U3/R + 8.3/R'),0 == sym('0.003*U1 - 0.001*U2 - 0.001*U4 - 0.001*U5 - U0/R + U1/R - 5.0/R'),0 == sym('-0.001*U1 + 0.003*U2 - 0.001*U3 - 0.001*U4'),0 == sym('-0.001*U2 + 0.001*U3 - U0/R + U3/R - 3.3/R'),0 == sym('-0.001*U1 - 0.001*U2 + 0.003*U4 - 0.001*U5'),0 == sym('-0.001*U0 - 0.001*U1 - 0.001*U4 + 0.003*U5'),0 == sym('U0'),0 == sym('R - 0.001'),];

author: JOJOElectro29
author_rank: Contributor
timestamp: May 26, 2025, 09:36:07 am
content: Here is my C program.Code:#include <stdio.h>#include <stdlib.h>#include <string.h>#include <math.h>#define MAX_NODES 10#define MAX_ELEMENTS 100typedef struct {int type; // 0 = resistor, 1 = voltage sourceint i;int j;double value;} Component;Component elements;int element_count = 0;int node_count = 0;void read_circuit(const char *filename) {FILE *file = fopen(filename, "r");if (!file) {perror("Can't open file");exit(1);}char line;while (fgets(line, sizeof(line), file)) {Component e;char *token = strtok(line, ",");e.type = atoi(token);token = strtok(NULL, ",");e.i = atoi(token);token = strtok(NULL, ",");e.j = atoi(token);token = strtok(NULL, ",");e.value = atof(token);if (e.i > node_count) node_count = e.i;if (e.j > node_count) node_count = e.j;elements[element_count++] = e;}fclose(file);node_count++; // include max node index}void solve_circuit() {int Nnodes = node_count;// Y: KCL current equations// YR: Voltage constraintsdouble Y; // KCL contributionsdouble YR; // Voltage constraintsdouble I; // Constant termsmemset(Y, 0, sizeof(Y));memset(YR, 0, sizeof(YR));memset(I, 0, sizeof(I));// Build equationsfor (int k = 0; k < element_count; k++) {int type = elements.type;int i = elements.i;int j = elements.j;double val = elements.value;if (type == 0) { // Resistordouble g = 1.0 / val;Y += g;Y -= g;Y += g;Y -= g;} else if (type == 1) { // Voltage Source// Add constraint: U - U + V = 0YR += 1;YR -= 1;I += -val;YR -= 1;YR += 1;I += val;}}// Total system size = Nnodes equationsdouble A;double B;for (int i = 0; i < Nnodes; i++) {for (int j = 0; j < Nnodes; j++) {if (YR != 0 || YR != 0) {A = YR;} else {A = Y;}}B = I;}// Ground node: set U0 = 0for (int j = 0; j < Nnodes; j++) {A = 0;}A = 1;B = 0;// Solve using Gaussian Elimination with partial pivotingfor (int k = 0; k < Nnodes; k++) {int max_row = k;for (int r = k + 1; r < Nnodes; r++) {if (fabs(A) > fabs(A)) {max_row = r;}}// Swap rowsif (max_row != k) {for (int c = 0; c < Nnodes; c++) {double tmp = A;A = A;A = tmp;}double tmp = B;B = B;B = tmp;}// Normalize pivot rowdouble pivot = A;if (fabs(pivot) < 1e-9) {printf("Singular matrix\n");exit(1);}for (int c = k; c < Nnodes; c++) {A /= pivot;}B /= pivot;// Eliminate other rowsfor (int r = 0; r < Nnodes; r++) {if (r != k && fabs(A) > 1e-9) {double factor = A;for (int c = k; c < Nnodes; c++) {A -= factor * A;}B -= factor * B;}}}// Recover voltagesdouble U;for (int i = 0; i < Nnodes; i++) {U = B;}// Print resultsprintf("\nNode voltages (reference node U0 = 0):\n");for (int i = 0; i < Nnodes; i++) {printf("U%d = %.2f V\n", i, U);}}int main() {read_circuit("exp5_circuit.txt");solve_circuit();return 0;}

author: ledtester
author_rank: Super Contributor
timestamp: May 28, 2025, 03:38:31 pm
content: Ok - I’ve found a flaw in the approach I discussed in replies #5 and #7.One thing that bothered me was that the approach replaced a voltage source with two equations which were the same and, indeed, this runs into trouble in the following circuit:Code:--5V --- 1k --- GND --- 1k --- 3.3V--node id: 2 1 0 3 4The corresponding circuit definition:Code:Trouble = [[0, 0, 1, 1000],[1, 1, 2, 5],[0, 0, 3, 1000],[1, 3, 4, 3.3]]Here are the generated system of equations generated by that approach compared to the original algorithm which converts voltage sources into current sources with a resistance of R:Code:Reply #5 approach Original algorithm----------------- ------------------0 = 0.002*U0 - 0.001*U1 - 0.001*U3 0 = 0.002*U0 - 0.001*U1 - 0.001*U30 = U1 - U2 + 5.0 0 = -0.001*U0 + 0.001*U1 + U1/R - U2/R + 5.0/R0 = -U1 + U2 - 5.0 0 = -U1/R + U2/R - 5.0/R0 = U3 - U4 + 3.3 0 = -0.001*U0 + 0.001*U3 + U3/R - U4/R + 3.3/R0 = -U3 + U4 - 3.3 0 = -U3/R + U4/R - 3.3/R0 = U0 0 = U00 = R - 0.001In the Reply #5 approach there are effectively only 3 independent equations which leaves the system underdetermined.Even though the equation:0 = -0.001*U0 + 0.001*U1 + U1/R - U2/R + 5.0/Rtransforms into0 = U1 - U2 + 5.0in the limit as R goes to 0 apparently it is not valid to make that substitution.The equations from my "supernode" algorithm (reply #3) are:Code:0 = 0.002*U0 - 0.001*U1 - 0.001*U30 = -0.001*U0 + 0.001*U10 = -0.001*U0 + 0.001*U30 = -U1 + U2 - 5.00 = -U3 + U4 - 3.30 = U0and this does give the correct answer for this case and I have a lot more confidence that it is correct in all cases.