0

I am working on a circuit simulator based on Modified Nodal Analysis (MNA), aiming to support nonlinear components such as diodes. To solve the nonlinear equations, I have implemented the Newton-Raphson method. However, I am facing issues with my implementation. Here is a simplified version of the code I am using to solve the below circuit:

enter image description here

// Function to calculate the diode current using the Shockley diode equation
double diodeEquation(double vd, double is, double vt) {
    return is * (exp(vd / vt) - 1.0);
}

// Function to calculate the diode current derivative w.r.t. voltage using the Shockley diode equation
double diodeDerivative(double vd, double is, double vt) {
    return is * exp(vd / vt) / vt;
}

int main() {
    // Step 1: Set up the MNA equations
    // Circuit components
    double vs = 5.0;  // Voltage source value
    double r1 = 10.0; // Resistor R1 value
    double r2 = 20.0; // Resistor R2 value
    double r3 = 30.0; // Resistor R3 value

    // Diode parameters for the Shockley diode equation
    double is = 1e-12;  // Saturation current
    double vt = 25.85e-3; // Thermal voltage

    // Step 2: Initialize variables
    double v1 = 0.0; // Initial guess for node voltage V1
    double v2 = 0.0; // Initial guess for node voltage V2
    double v3 = 0.0; // Initial guess for node voltage V3
    double v1_old, v2_old, v3_old; // Previous values for convergence check
    double tol = 1e-6; // Tolerance for convergence
    int maxIterations = 100; // Maximum number of iterations
    int iterations = 0; // Iteration counter
    bool converged = false; // Convergence flag

    // Step 4: Perform the Modified Newton-Raphson iteration
    while (!converged && iterations < maxIterations) {
        // Save the previous node voltages for convergence check
        v1_old = v1;
        v2_old = v2;
        v3_old = v3;

        // Calculate the diode current and its derivative
        double vd = v2 - v3;
        double id = diodeEquation(vd, is, vt);
        double g = diodeDerivative(vd, is, vt);

        // Formulate the MNA equations
        double i1 = (v1 - vs) / r1;
        double i2 = (v2 - v3) / r2;
        double i3 = v3 / r3;
        double i4 = id;

        // Construct the Jacobian matrix
        double j11 = 1.0 / r1;
        double j12 = -1.0 / r1;
        double j13 = 0.0;
        double j21 = 1.0 / r2;
        double j22 = -1.0 / r2 - g;
        double j23 = g;
        double j31 = 0.0;
        double j32 = g;
        double j33 = -g;
        
        // Construct the residual vector
        double r1 = i1 - i2;
        double r2 = -i1 + i2 - i3;
        double r3 = -i2 + i3 - i4;

        // Solve the linear system of equations to obtain the update
        double detJ = j11 * (j22 * j33 - j32 * j23) - j12 * (j21 * j33 - j31 * j23) + j13 * (j21 * j32 - j31 * j22);
        double dv1 = ((j22 * j33 - j32 * j23) * r1 - (j12 * j33 - j32 * j13) * r2 + (j12 * j23 - j22 * j13) * r3) / detJ;
        double dv2 = (-(j21 * j33 - j31 * j23) * r1 + (j11 * j33 - j31 * j13) * r2 - (j11 * j23 - j21 * j13) * r3) / detJ;
        double dv3 = ((j21 * j32 - j31 * j22) * r1 - (j11 * j32 - j31 * j12) * r2 + (j11 * j22 - j21 * j12) * r3) / detJ;

        // Update the solution vector
        v1 -= dv1;
        v2 -= dv2;
        v3 -= dv3;

        // Check for convergence
        double norm = sqrt(pow(v1 - v1_old, 2) + pow(v2 - v2_old, 2) + pow(v3 - v3_old, 2));
        if (norm < tol) {
            converged = true;
        }

        iterations++;
    }

    // Step 5: Output the final solution
    if (converged) {
        std::cout << "Converged to solution:\n";
        std::cout << "V1 = " << v1 << " V\n";
        std::cout << "V2 = " << v2 << " V\n";
        std::cout << "V3 = " << v3 << " V\n";
    }

    return 0;
}

I have tested the same circuit in two other circuit simulators, The first simulator reports Vd = 739 mV and Id = 70.74 mA, and the second simulator reports Vd = 765 mV and Id = 70 mA. However, in my implementation, the output is Vd = 175 mV and Id = 2.14 µA.

I would appreciate any insights into what might be wrong with my implementation of the Newton-Raphson method for solving the circuit equations, especially with regards to the diode component.

Edit:

After considering the suggestions from EvgsupportsModerator, I have formulated the equations based on Kirchhoff's Voltage Law (KVL) and Kirchhoff's Current Law (KCL). The equations for each node are as follows:

KCL at node 1: I + (v1 - v2)/R1 = 0 // I is the current through the voltage source
KCL at node 2: (v2 - v1)/R1 + v2/R2 + Is*(exp((v2 - v3)/vt) - 1) = 0
KCL at node 3: -Is*(exp((v2 - v3)/vt) - 1) + v3/R3 = 0
KVL at node 1: v1 - E = 0

I obtained the following vector solution when I execute the previous code that I provide:

v1 = 5
v2 = 3.33142
v3 = 0.00861309
I = -0.166858

However, when I substituted these values back into the system, I found that they do not satisfy all of the equations. They only satisfy the first and last equations.

To validate my calculations, I simulated the same circuit using the ngspice software, and it provided a different vector solution:

v1 = 5
v2 = 2.808430
v3 = 2.362064
I = -0.219157

Interestingly, when I substituted these values into the system, it appeared to be a correct solution. Therefore, it seems that the values obtained from the ngspice simulation align better with the given equations.

Abdo21
  • 498
  • 4
  • 14
  • The first thing I usually check (numerically) in such cases is that derivatives are calculated correctly. – Evg Jun 30 '23 at 17:41
  • @EvgsupportsModerator The formula used to calculate the derivative `is * exp(vd / vt) / vt` appears to be correct. I don't see any issues with it – Abdo21 Jun 30 '23 at 17:48
  • Do you observe quadratic convergence of your algorithm? You could also plug the calculated values and make sure that equations are satisfied. – Evg Jun 30 '23 at 17:50
  • @EvgsupportsModeratorStrike I will try to do that. Thank you for your guidance. – Abdo21 Jun 30 '23 at 17:57
  • I think (from an iterative solution of Kirchoff's laws) that V2=2.8089 V and V3=2.3601 V. I get the current through the diode as 78.67mA and that through the 10 ohm resistor as 219.1mA – lastchance Jun 30 '23 at 21:51
  • @lastchance , the last value I wrote (`I = -0.219157`) is actually the current flowing through the voltage source, not the diode. In order to calculate the diode current, I can use the formula `id = is * (exp((v2 - v3) / vt) - 1)`, which yields a value of approximately `id = 72mA`. – Abdo21 Jun 30 '23 at 22:06
  • @Abdo21, the current flowing through the battery is exactly the same as that flowing through the 10 ohm resistor: 219.1mA, or 0.2191A. Not sure why you wish to assign it a minus sign. – lastchance Jul 01 '23 at 05:22
  • @Abdo21, what are the units for saturation current in your code: mA or A? I was working on the basis of the latter, but that is unlikely for a semiconductor diode. – lastchance Jul 01 '23 at 05:34
  • @lastchance, It's generally understood that `Amps` refers to current and `Volts` refers to voltage when nothing specific is mentioned (`is = 2.52e-9 Amps`) – Abdo21 Jul 01 '23 at 11:59
  • @lastchance, I agree that the current flowing through the battery and the 10-ohm resistor is indeed the same. In my calculations, I use the convention where the current `I` is assigned from the positive terminal to the negative terminal of the battery (from the top to the bottom) that's why it has a **MINUS** sign and that's related to the way the Modified Nodal Analysis (MNA) matrix works. – Abdo21 Jul 01 '23 at 12:40

1 Answers1

1

First of all, you have only TWO free parameters, not four. For network problems (water flow, electric circuits, ... ) these are most conveniently taken as the driving potentials at nodes, since currents/flows depend only on the difference between these potentials and the resistances in the connectors. In this problem, the best two parameters are V2 and V3 (voltages at nodes 2 and 3).

Secondly, a diode is a highly NON-LINEAR device. Thus, Newton-Raphson is not necessarily a good way to solve this problem. (By hand, I used single-point iteration, since the inverted diode formula gives a logarithm, which changes relatively slowly.)

In the end I did manage to solve your problem by Newton-Raphson, but I had to do two key things. (1) I needed a decent initial guess (to find which I basically took the diode as offering no resistance). (2) I had to UNDER-RELAX the update step. (This is a classic technique in non-linear problems such as computational fluid mechanics.)

The solver that I have used here is a very cheap one (2x2 matrices only). You can use your library one for bigger problems.

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

void solve( vector<vector<double>> A, vector<double> &x, const vector<double> &rhs );

int main()
{
   // Convergence
   const int MAXITER = 1000;
   const double TOLERANCE = 1.0e-8;
   const double UNDER_RELAX = 0.1;

   // Resistors
   const double R1 = 10.0, R2 = 20.0, R3 = 30.0; // resistances in ohms

   // Diode
   const double Is = 2.52e-9;                    // saturation current in A (weird value, though)
   const double Vt = 0.026;                      // thermal potential in V

   // Fixed voltages
   const double EMF = 5;                         // fixed by the battery
   const double EARTH = 0.0;                     //

   // Initial guess for free parameters
   int N = 2;
   double V2 = 25.0 / 11.0;                      // potential at node 2 in V
   double V3 = V2;                               // potential at node 3 in V

   double I1, I2, I3;                            // currents through resistors
   double Id;                                    // current for diode
   vector<double> f( N );                        // net current out of node
   vector<vector<double>> dfdx( N, vector<double>(N) );   // derivatives of f

   // Solve f(x) = 0   by  f -> f(x*) + dfdx.dx = 0   (matrix Newton-Raphson)
   bool converged = false;
   for ( int iter = 1; iter <= MAXITER; iter++ )
   {
      // Currents through resistors
      I1 = ( EMF - V2   ) / R1;
      I2 = ( V2 - EARTH ) / R2;
      I3 = ( V3 - EARTH ) / R3;

      // Current through diode
      double expfunc = exp( ( V2 - V3 ) / Vt );
      Id = Is * ( expfunc - 1.0 );     // current from diode

      // Net current from nodes 2, 3
      f[0] = -I1 + I2 + Id;
      f[1] =       I3 - Id;

      // Check convergence
      double normsq = 0.0;
      for ( int i = 0; i < N; i++ ) normsq += f[i] * f[i];
      double error = sqrt( normsq );
      converged = error < TOLERANCE;
      if ( converged ) break;

      // Derivative of f with respect to parameters V2 and V3
      dfdx[0][0] = 1.0 / R1 + 1.0 / R2 + ( Is / Vt ) * expfunc;
      dfdx[0][1] =                     - ( Is / Vt ) * expfunc;
      dfdx[1][0] =                     - ( Is / Vt ) * expfunc;
      dfdx[1][1] = 1.0 / R3            + ( Is / Vt ) * expfunc;

      vector<double> rhs(N);                     
      for ( int i = 0; i < N; i++ ) rhs[i] = -f[i];

      // Solve for increments in parameters
      vector<double> x(N);              
      solve( dfdx, x, rhs );

      // Update parameters
      V2 += UNDER_RELAX * x[0];
      V3 += UNDER_RELAX * x[1];

      cout << "iter: " << iter << "    V2: " << V2 << "   V3: " << V3 << "    error: " << error << '\n';
   }

   if ( !converged ) cout << "Not converged\n";
   cout << "V2: " << V2      << '\n'
        << "V3: " << V3      << '\n'
        << "Vd: " << V2 - V3 << '\n'
        << "I1: " << I1      << '\n'
        << "I2: " << I2      << '\n'
        << "I3: " << I3      << '\n'
        << "Id: " << Id      << '\n';
}


void solve( vector<vector<double>> A, vector<double> &x, const vector<double> &b )
// !!!!!!! This solves a 2x2 system only - replace with something better !!!!!!!!!!!!!!!!!
{
   double det = A[0][0] * A[1][1] - A[0][1] * A[1][0];
   x[0] = (  A[1][1] * b[0] - A[0][1] * b[1] ) / det;
   x[1] = ( -A[1][0] * b[0] + A[0][0] * b[1] ) / det;
}

Final iterations and output:

.......
.......
iter: 149    V2: 2.80885   V3: 2.36018    error: 1.34202e-08
iter: 150    V2: 2.80885   V3: 2.36018    error: 1.20782e-08
iter: 151    V2: 2.80885   V3: 2.36018    error: 1.08704e-08
V2: 2.80885
V3: 2.36018
Vd: 0.44867
I1: 0.219115
I2: 0.140442
I3: 0.0786726
Id: 0.0786726
lastchance
  • 1,436
  • 1
  • 3
  • 12
  • Thanks, @lastchance, for providing the answer! I wanted to explore how the Newton-Raphson method can be applied within a **CIRCUIT SIMULATOR**, which typically relies on Modified Nodal Analysis [MNA](https://en.wikipedia.org/wiki/Modified_nodal_analysis). Unlike the "Raw" Newton-Raphson method, the MNA method is specifically designed for automated computer simulations and involves a 4x4 matrix in this case because it has 3 nodes and 1 voltage source. – Abdo21 Jul 01 '23 at 16:32
  • I must acknowledge that the result you provided is indeed correct. However, I'm curious to explore how this implementation integrates with the broader context of Modified Nodal Analysis to achieve more comprehensive circuit simulations. – Abdo21 Jul 01 '23 at 16:35
  • Ah, I don’t know, @Abdo21. The currents are known once the voltages are fixed, so there doesn’t seem much point including them as additional parameters. I’m actually translating what I do with fluid networks, when the “voltages” become pressure or head and the resistance law is quadratic, not the linear Ohm’s Law. – lastchance Jul 01 '23 at 19:02