-- File: [Thyme]<Thyme>System>CSIM01>spSolve.mesa
-- Last editted:
-- by Barth, July 11, 1983  1:42 PM
-- Chen, April 22, 1983  10:56 AM. Changed dvdtmax from 1E13 to 1E15.
-- Wilhelm April 27, 1982  4:04 PM, reformated by Barth and stored under
--   [Cherry]<Barth>Thyme>1.97> .
DIRECTORY spGlobals, spModelDefs, AltoDefs, Real, CWF, RealFns, StringDefs;
spSolve:  PROGRAM
  IMPORTS spGlobals, Real, CWF, RF:  RealFns, StringDefs
  EXPORTS spGlobals, spModelDefs =
  BEGIN
    OPEN spGlobals;

    intNodeList:     PUBLIC nodePtr    ← NIL;
    intNodeModFunc:  PUBLIC modFuncPtr ← NIL;
    otherModFunc:    PUBLIC modFuncPtr ← NIL;

    solveError:  SIGNAL[s:  STRING] = CODE;
    solveTest:   SIGNAL[s:  STRING] = CODE;
    Retreat:     PUBLIC SIGNAL[cause:  STRING] = CODE;
    Failure:     PUBLIC SIGNAL[errorNum: CARDINAL] = CODE;

    worstNode:  nodePtr;
    maxLog:  CARDINAL = 16;
    curLog:  CARDINAL ← 0;
    worstNodeLog:  ARRAY[0..maxLog) OF RECORD[node:  nodePtr,
                                              t:     REAL,
                                              v,
                                              dvdt:  REAL];

    icsSet:         BOOLEAN ← FALSE;
    checkPoint:     BOOLEAN ← FALSE;
    canned:         BOOLEAN ← FALSE;
    numGoodSteps:   CARDINAL ← 0;
    modelsSkipped:  CARDINAL ← 0;

    dvdtTrunc:         REAL;
    t:                 REAL ← 0.0;
    dT:                REAL;
    AMcount:           CARDINAL ← 0;
    goodNodeCount:     CARDINAL ← 0;
    numberOfSteps:     LONG CARDINAL;
    highStepCount:     LONG CARDINAL;
    lowStepCount:      LONG CARDINAL;
    countsPerStep:     LONG CARDINAL ← 100000000;
    maxCountsPerStep:  LONG CARDINAL = 1000000000;

    nParms:  CARDINAL = 28;
    parmNames:  ARRAY[0..nParms) OF STRING ← ["maxiter",
                                              "printiter",
                                              "initstep",
                                              "squish",
                                              "tol",
                                              "dt",
                                              "tmax",
                                              "printstep",
                                              "rungekutta",
                                              "tmin",
                                              "printall",
                                              "amiter",
                                              "amup",
                                              "inttol",
                                              "amdown",
                                              "vmin",
                                              "vmax",
                                              "dvdtmax",
                                              "dvdtfac",
                                              "tbreak",
                                              "rkerr",
                                              "amerr",
                                              "amdelay",
                                              "floor",
                                              "minratio",
                                              "rkfac",
                                              "retratio",
                                              ""];
    Vmin:       REAL ← -100.0;
    Vmax:       REAL ←  100.0;
    dvdtmax:    REAL ← 1.0e15;
    dfltInts:   CARDINAL ← 200;

    retRatio:   REAL ← 0.5;
    RKfac:      REAL ← 1.0;
    minRatio:   REAL ← 0.8;
    floor:      REAL ← 0.01;
    AMerr:      REAL ← 0.01;
    RKerr:      REAL ← 0.01;
    tBreak:     REAL ← 0.0;
    dvdtFac:    REAL ← 5.0;
    squish:     REAL ← 0.2;
    initStep:   REAL ← 0.1;
    tol:        REAL ← 0.001;
    intTol:     REAL ← 0.001;
    AMup:       REAL ← 1.25;
    AMdown:     REAL ← 0.8;
    printdT:    REAL ← 0.0;
    tMin:       REAL ← 0.0;
    tMax:       REAL ← 0.0;
    maxIter:    CARDINAL ← 20;
    AMiter:     CARDINAL ← 2;
    AMdelay:    CARDINAL ← 5;
    printIter:     BOOLEAN ← FALSE;
    printStep:     BOOLEAN ← FALSE;
    doRungeKutta:  BOOLEAN ← FALSE;
    printAll:      BOOLEAN ← FALSE;


    printMsg:  PROCEDURE[fmt:  STRING, a1, a2, a3, a4:  LONG POINTER ← NIL] =
      BEGIN
        s:  STRING = [128];

        CWF.SWF[s, fmt, a1, a2, a3, a4];
        printSysWindow[s]
      END;

    updateFunctions:  PROCEDURE[f:  modFuncPtr] =
      BEGIN
        v:  REAL;
        mb:  modBrPtr;
        source:  argSource;
        args, res:  argList;
        allInactive:  BOOLEAN;

        modelsSkipped ← 0;
        UNTIL f = NIL DO
          source ← f↑.arguments;
          args ← f↑.argVector;
          allInactive ← TRUE;
          FOR i:  CARDINAL IN [0..LENGTH[source]) DO
            args[i] ← source[i]↑.nHist.y;
            allInactive ← allInactive AND source[i]↑.marked
          ENDLOOP;
          WITH mf:  f↑ SELECT FROM
            fn =>
              BEGIN
                v ← mf.functionProc[t, args, mf.parmVector];
                mf.branch↑.comVal ← v
              END;
            mod =>
              IF ~allInactive THEN
                BEGIN
                  res ← mf.modelResults;
                  mf.modelProc[args, mf.parmVector, res];
                  mb ← mf.modelBranches;
                  UNTIL mb = NIL DO
                    v ← mb↑.b↑.comVal ← res[mb↑.b↑.modelIndex];
                    mb ← mb↑.nextBranch
                  ENDLOOP
                END
              ELSE modelsSkipped ← modelsSkipped + 1
          ENDCASE;
          f ← f↑.nextFunction
        ENDLOOP;
      END;
          


    GSiteration:  PROCEDURE[dT:  REAL,
                            print, firstTime, mark:  BOOLEAN ← FALSE]
                    RETURNS[nIter:  CARDINAL ← 0] =
      BEGIN
        E, newE, dvdt, I, newI, cond, csum, otherV, maxI, denom:  REAL;
        n:  nodePtr;
        b:  branchPtr;
        vCur:  vSourcePtr;
        bLink:  branchLinkPtr;
        plus, nodeOK, OK:  BOOLEAN;

        updateFunctions[intNodeModFunc];
        IF print THEN printNodeEqn[];
        UNTIL nIter = maxIter + maxIter DO
          OK ← TRUE;
          IF print THEN
            CWF.WF2["t = %10.4f, iteration %2d --*n", @t, @nIter];
          IF otherModFunc # NIL THEN updateFunctions[otherModFunc];
          IF firstTime THEN updateFunctions[intNodeModFunc];
          FOR n ← nodeList, n↑.nextNode UNTIL n = NIL DO
            IF n = gndNode THEN LOOP;
            IF nIter > 1 AND n↑.converged THEN LOOP;
            IF print THEN CWF.WF1["Node %s:  ", n↑.nodeName↑.name];
            bLink ← n↑.branches;
            IF n↑.integrate THEN
              BEGIN
                maxI ← csum ← I ← 0.0;
                UNTIL bLink = NIL DO
                  b      ← bLink↑.branch;
                  plus   ← bLink↑.pos;
                  WITH b↑ SELECT FROM
                    resistor =>
                      newI ← (bLink↑.otherNode↑.nHist.y -
                              n↑.nHist.y)/comVal;
                    capacitor =>
                      BEGIN
                        csum ← csum + comVal;
                        newI ← comVal*bLink↑.otherNode↑.nHist.f0
                      END;
                    inductor =>
                      newI ← (IF plus THEN -iHist.y
                              ELSE iHist.y)/comVal;
                    vSource =>
                      newI ← IF plus THEN -vsCurrent ELSE vsCurrent;
                    iSource =>
                      newI ← IF plus THEN -comVal ELSE comVal
                  ENDCASE;
                  maxI ← MAX[maxI, ABS[newI]];
                  I ← I + newI;
                  bLink ← bLink↑.nextLink
                ENDLOOP;
                E ← n↑.nHist.f0;
                dvdt ← n↑.nHist.f0 ← I/csum;
                IF mark THEN n↑.marked ← ABS[I] <= dvdtTrunc*maxI;
                denom ← MAX[ABS[E], ABS[dvdt]];
                nodeOK ← IF denom <= Real.SmallestNormalizedNumber THEN TRUE
                         ELSE tol >= ABS[(dvdt - E)/denom];
                IF print THEN
                  CWF.WF3["Old v' = %9.2f New v' = %9.2f C = %9.2f.*n",
                          @E, @dvdt, @csum]
              END
            ELSE
              BEGIN
                E ← n↑.nHist.y;
                IF n↑.curPtr # NIL THEN
                  BEGIN
                    vCur ← n↑.curPtr;
                    newE ← IF vCur↑.posNode = n
                           THEN vCur↑.negNode↑.nHist.y + vCur↑.comVal
                           ELSE vCur↑.posNode↑.nHist.y - vCur↑.comVal;
                    I    ← vCur↑.vsCurrent;
                    newI ← 0.0;
                    UNTIL bLink = NIL DO
                      b      ← bLink↑.branch;
                      otherV ← bLink↑.otherNode↑.nHist.y;
                      plus   ← bLink↑.pos;
                      WITH b↑ SELECT FROM
                        resistor =>
                          newI ← newI + (otherV - newE)/comVal;
                        capacitor =>
                          newI ← newI + comVal*
                                 (bLink↑.otherNode↑.nHist.f0 -
                                  n↑.nHist.f0);
                        inductor =>
                          newI ← newI + (IF plus THEN -iHist.y
                                         ELSE iHist.y)/comVal;
                        vSource =>
                          newI ← IF plus THEN newI - vsCurrent
                                         ELSE newI + vsCurrent;
                        iSource =>
                          newI ← IF plus THEN newI - comVal
                                         ELSE newI + comVal
                      ENDCASE;
                      bLink ← bLink↑.nextLink
                    ENDLOOP;
                    newI ← IF n = vCur↑.posNode THEN I + newI
                                                ELSE I - newI;
                    denom ← MAX[ABS[I], ABS[newI]];
                    nodeOK ← IF denom <= Real.SmallestNormalizedNumber THEN TRUE
                             ELSE tol >= ABS[(newI - I)/denom];
                    IF firstTime THEN n↑.nHist.f0 ← 0.0
                    ELSE
                      n↑.nHist.f0 ← squish*n↑.nHist.f1 + (1.0-squish)*
                                    (newE-n↑.nHist.oldy)/dT +
                                    (IF vCur↑.posNode = n
                                       THEN vCur↑.negNode↑.nHist.f0
                                       ELSE vCur↑.posNode↑.nHist.f0);
                    vCur↑.vsCurrent ← newI;
                    IF print THEN
                      CWF.WF2["Old I = %9.2f New I = %9.2f", @I, @newI]
                  END
                ELSE
                  BEGIN
                    cond ← newE ← 0.0;
                    UNTIL bLink = NIL DO
                      b      ← bLink↑.branch;
                      plus   ← bLink↑.pos;
                      WITH b↑ SELECT FROM
                        resistor =>
                          BEGIN
                            cond ← cond + 1.0/comVal;
                            newE ← newE +
                                   bLink↑.otherNode↑.nHist.y/comVal
                          END;
                        inductor =>
                          newE ← newE + (IF plus THEN -iHist.y
                                         ELSE iHist.y)/comVal;
                        vSource =>
                          newE ← IF plus THEN newE - vsCurrent
                                         ELSE newE + vsCurrent;
                        iSource =>
                          newE ← IF plus THEN newE - comVal
                                         ELSE newE + comVal
                      ENDCASE;
                      bLink ← bLink↑.nextLink
                    ENDLOOP;
                    newE ← newE/cond;
                    denom ← MAX[ABS[E], ABS[newE]];
                    nodeOK ← IF denom <= Real.SmallestNormalizedNumber THEN TRUE
                             ELSE tol >= ABS[(newE - E)/denom]
                  END;
                n↑.nHist.y ← newE;
                IF print THEN
                  CWF.WF2["Old E = %9.2f New E = %9.2f.*n", @E, @newE]
              END;
            OK ← OK AND nodeOK;
            n↑.converged ← nodeOK;
            IF ~nodeOK THEN
              FOR bLink ← n↑.branches, bLink↑.nextLink UNTIL bLink=NIL DO
                bLink↑.otherNode↑.converged ← FALSE
              ENDLOOP
          ENDLOOP;
          nIter ← nIter + 1;
          IF nIter = maxIter THEN
            printMsg["t = %9.3f, iteration count exceeded", @t];
          IF OK THEN EXIT
        ENDLOOP;
        IF nIter >= maxIter + maxIter THEN
          SIGNAL solveError["GS did not converge"]
      END;

    printNodeEqn:  PROCEDURE =
      BEGIN
        n:  nodePtr;
        bLink:  branchLinkPtr;
        branch: branchPtr;
        plus:   BOOLEAN;

        FOR n ← intNodeList, n↑.nextIntNode UNTIL n = NIL DO
          CWF.WF3["*n%s -- %13.6f %13.6f*n", n↑.nodeName↑.name,
                  @n↑.nHist.y, @n↑.nHist.oldy];
          FOR bLink ← n↑.branches, bLink↑.nextLink UNTIL bLink = NIL DO
            branch ← bLink↑.branch;
            plus   ← bLink↑.pos;
            WITH b:  branch↑ SELECT FROM
              resistor =>
                CWF.WF3["(%13.6f - %13.6f)/%13.6f",
                        @bLink↑.otherNode↑.nHist.y,
                        @n↑.nHist.y,@b.comVal];
              capacitor =>
                CWF.WF2["%13.6fx%13.6f",
                        @b.comVal, @bLink↑.otherNode↑.nHist.f0];
              inductor =>
                IF plus THEN
                   CWF.WF2["-1x%13.6f/%13.6f",@b.iHist.y,@b.comVal]
                ELSE CWF.WF2["%13.6f/%13.6f", @b.iHist.y, @b.comVal];
              vSource =>
                IF plus THEN CWF.WF1["-1x%13.6f", @b.vsCurrent]
                ELSE CWF.WF1["%13.6f", @b.vsCurrent];
              iSource =>
                IF plus THEN CWF.WF1["-1x%13.6f", @b.comVal]
                ELSE CWF.WF1["%13.6f", @b.comVal]
            ENDCASE;
            CWF.WF3["   %s(%13.6f, %13.6f)*n",
                    bLink↑.otherNode↑.nodeName↑.name,
                    @bLink↑.otherNode↑.nHist.y,
                    @bLink↑.otherNode↑.nHist.f0]
          ENDLOOP
        ENDLOOP
      END;


    parmValue:  PROCEDURE[bool:  BOOLEAN]
                  RETURNS[r:  REAL, b:  BOOLEAN, c:  CARDINAL] =
      BEGIN
        IF bool THEN
          IF item = name THEN
            BEGIN
              IF LongEqualStrings[newString, "FALSE"] THEN b ← FALSE
              ELSE
                IF LongEqualStrings[newString, "TRUE"] THEN b ← TRUE
                ELSE error[719];
              next[]
            END
          ELSE error[704]
        ELSE
          BEGIN
            r ← getSignedNumber[];
            IF r >= 0.0 AND r < 65535.0 THEN c ← Real.FixC[r]
          END
      END;

    runParms:  PROCEDURE =
      BEGIN
        nameIndex:  CARDINAL;
        WHILE item = name DO
          FOR i:  CARDINAL IN [0..nParms) DO
            nameIndex ← i;
            IF newString.length # parmNames[nameIndex].length THEN LOOP;
            FOR j:  CARDINAL IN [0..newString.length) DO
              IF parmNames[i][j] # StringDefs.LowerCase[newString[j]] THEN EXIT;
            REPEAT
              FINISHED => EXIT;
            ENDLOOP;
          ENDLOOP;
          next[];
          IF item # leftArrow THEN error[703] ELSE next[];
          SELECT nameIndex FROM
             0 => [,, maxIter]      ← parmValue[FALSE];
             1 => [, printIter,]    ← parmValue[TRUE];
             2 => [initStep,,]      ← parmValue[FALSE];
             3 => [initStep,,]      ← parmValue[FALSE];
             4 => [tol,,]           ← parmValue[FALSE];
             5 => [printdT,,]       ← parmValue[FALSE];
             6 => [tMax,,]          ← parmValue[FALSE];
             7 => [, printStep,]    ← parmValue[TRUE];
             8 => [, doRungeKutta,] ← parmValue[TRUE];
             9 => [tMin,,]          ← parmValue[FALSE];
            10 => [, printAll,]     ← parmValue[TRUE];
            11 => [,, AMiter]       ← parmValue[FALSE];
            12 => [AMup,,]          ← parmValue[FALSE];
            13 => [intTol,,]         ← parmValue[FALSE];
            14 => [AMdown,,]        ← parmValue[FALSE];
            15 => [Vmin,,]          ← parmValue[FALSE];
            16 => [Vmax,,]          ← parmValue[FALSE];
            17 => [dvdtmax,,]       ← parmValue[FALSE];
            18 => [dvdtFac,,]       ← parmValue[FALSE];
            19 => [tBreak,,]        ← parmValue[FALSE];
            20 => [RKerr,,]         ← parmValue[FALSE];
            21 => [AMerr,,]         ← parmValue[FALSE];
            22 => [,, AMdelay]      ← parmValue[FALSE];
            23 => [floor,,]         ← parmValue[FALSE];
            24 => [minRatio,,]      ← parmValue[FALSE];
            25 => [RKfac,,]         ← parmValue[FALSE];
            26 => [retRatio,,]      ← parmValue[FALSE];
          ENDCASE => error[790];
          IF item = comma THEN next[]
        ENDLOOP
      END;

    predict:  PROCEDURE[h:  REAL] =
      BEGIN
        nodes:  nodePtr     ← intNodeList;
        inds:   inductorPtr ← inductorList;
        incr:   REAL;

        h ← h/24.0;
        UNTIL nodes = NIL DO
          IF ~nodes↑.marked THEN
            BEGIN
              OPEN nodes↑.nHist;
              incr ← h*(55.0*f1 - 59.0*f2 + 37.0*f3 - 9.0*f4);
              y ← oldy + incr
            END;
          nodes ← nodes↑.nextIntNode
        ENDLOOP;
        UNTIL inds = NIL DO
          OPEN inds↑.iHist;
          y ← oldy + h*(55.0*f1 - 59.0*f2 + 37.0*f3 - 9.0*f4);
          inds ← inds↑.nextInductor
        ENDLOOP;
      END;

    firstCorrector:  PROCEDURE[h:  REAL] =
      BEGIN
        nodes:  nodePtr     ← intNodeList;
        inds:   inductorPtr ← inductorList;
        f:      REAL;

        h ← h/24.0;
        UNTIL nodes = NIL DO
          IF ~nodes↑.marked THEN
            BEGIN
              OPEN nodes↑.nHist;
              y ← oldy + h*(9.0*f0 + 19.0*f1 - 5.0*f2 + f3);
              oldf ← f0
            END;
          nodes ← nodes↑.nextIntNode
        ENDLOOP;
        UNTIL inds = NIL DO
          OPEN inds↑.iHist;
          f ← inds↑.posNode↑.nHist.y - inds↑.negNode↑.nHist.y;
          y ← oldy + h*(9.0*f + 19.0*f1 - 5.0*f2 + f3);
          oldf ← f;
          inds ← inds↑.nextInductor
        ENDLOOP;
      END;

    iteratingCorrector:  PROCEDURE[h:  REAL]
                           RETURNS[newRatio:  REAL ← 4.0,
                                   OK:  BOOLEAN ← TRUE] =
      BEGIN
        nodes:  nodePtr     ← intNodeList;
        inds:   inductorPtr ← inductorList;
        f, deltay, newy, err, denom:  REAL;

        h ← 3.0*h/8.0;
        UNTIL nodes = NIL DO
          IF ~nodes↑.marked THEN
            BEGIN
              OPEN nodes↑.nHist;
              deltay ← h*(f0 - oldf);
              newy   ← y + deltay;
              denom  ← MAX[ABS[newy], ABS[y]];
              IF denom > floor THEN
                BEGIN
                  err ← AMerr*ABS[deltay/denom];
                  IF intTol < err THEN
                    BEGIN
                      OK ← FALSE;
                      newRatio ← MIN[newRatio, intTol/err];
                      worstNode ← nodes
                    END
                END;
              y    ← newy;
              oldf ← f0
            END;
          nodes ← nodes↑.nextIntNode
        ENDLOOP;
        UNTIL inds = NIL DO
          OPEN inds↑.iHist;
          f      ← inds↑.posNode↑.nHist.y - inds↑.negNode↑.nHist.y;
          deltay ← h*(f - oldf);
          newy   ← y + deltay;
          denom  ← MAX[ABS[newy], ABS[y]];
          IF denom # 0.0 THEN
            BEGIN
              err ← AMerr*ABS[deltay/denom];
              IF intTol < err THEN
                BEGIN
                  OK ← FALSE;
                  newRatio ← MIN[newRatio, intTol/err]
                END
            END;
          y    ← newy;
          oldf ← f;
          inds ← inds↑.nextInductor
        ENDLOOP;
      END;

    AdamsMoulton:  PROCEDURE[dT: REAL]
                     RETURNS[newRatio:  REAL, nGS:  CARDINAL, ok:  BOOLEAN] =
      BEGIN
        nAM:  CARDINAL;

        predict[dT];
        nGS ← GSiteration[dT, printAll];
        firstCorrector[dT];
        nGS ← GSiteration[dT, printAll] + nGS;
        FOR nAM ← 2, nAM + 1 UNTIL nAM = AMiter DO
          [] ← iteratingCorrector[dT];
          nGS ← GSiteration[dT, printAll] + nGS
        ENDLOOP;
        [newRatio, ok] ← iteratingCorrector[dT];
      END;

    RKupdate:  PROCEDURE[c, h:  REAL, last:  BOOLEAN]
                 RETURNS[newRatio:  REAL ← 1.0, ok:  BOOLEAN ← TRUE] =
      BEGIN
        nodes:  nodePtr     ← intNodeList;
        inds:   inductorPtr ← inductorList;
        k, incr, y, err, denom:  REAL;

        UNTIL nodes = NIL DO
          IF ~nodes↑.marked THEN
            BEGIN
              nodes↑.nHist.sumk ← c*nodes↑.nHist.f0 + nodes↑.nHist.sumk;
              IF last THEN
                BEGIN
                  incr  ← h*nodes↑.nHist.sumk;
                  y     ← nodes↑.nHist.oldy + incr;
                  denom ← MAX[ABS[y], ABS[nodes↑.nHist.y]];
                  IF denom > floor THEN
                    BEGIN
                      err ← RKerr*ABS[(y - nodes↑.nHist.y)/denom];
                      IF intTol < err THEN
                        BEGIN
                          ok ← FALSE;
                          newRatio ← MIN[newRatio, intTol/err]
                        END
                    END;
                  nodes↑.nHist.y ← y;
                  nodes↑.nHist.sumk ← 0.0
                END
              ELSE
                BEGIN
                  incr ← h*nodes↑.nHist.f0;
                  nodes↑.nHist.y ← nodes↑.nHist.oldy + incr
                END
            END;
          nodes ← nodes↑.nextIntNode
        ENDLOOP;
        UNTIL inds = NIL DO
          k ← inds↑.posNode↑.nHist.y - inds↑.negNode↑.nHist.y;
          inds↑.iHist.sumk ← c*k + inds↑.iHist.sumk;
          IF last THEN
            BEGIN
              inds↑.iHist.y ← inds↑.iHist.oldy + h*inds↑.iHist.sumk;
              inds↑.iHist.sumk ← 0.0
            END
          ELSE inds↑.iHist.y ← inds↑.iHist.oldy + h*k;
          inds ← inds↑.nextInductor
        ENDLOOP;
      END;

    RungeKutta:  PROCEDURE[dT: REAL]
                   RETURNS[newRatio:  REAL, nGS:  CARDINAL, ok:  BOOLEAN] =
      BEGIN
        time:  REAL;

        time ← t;
        t ← t - 0.5*dT;
        []  ← RKupdate[1.0, 0.5*dT, FALSE];
        nGS ← GSiteration[0.5*dT, printAll];
        []  ← RKupdate[2.0, 0.5*dT, FALSE];
        nGS ← GSiteration[0.5*dT, printAll] + nGS;
        t ← time;
        []  ← RKupdate[2.0, dT, FALSE];
        nGS ← GSiteration[dT, printAll] + nGS;
        [newRatio, ok] ← RKupdate[1.0, 0.1666667*dT, TRUE]
      END;

    changeStepSize:  PROCEDURE[ratio:  REAL, RK:  BOOLEAN] =
      BEGIN
        int:  CHARACTER;

        IF ratio < 1.0 OR countsPerStep < maxCountsPerStep THEN
          BEGIN
            numGoodSteps ← 1;
            IF worstNode # NIL THEN
              BEGIN
                worstNodeLog[curLog].node ← worstNode;
                worstNodeLog[curLog].t ← t;
                worstNodeLog[curLog].v ← worstNode↑.nHist.y;
                worstNodeLog[curLog].dvdt ← worstNode↑.nHist.f0
              END
            ELSE worstNodeLog[curLog] ← [NIL, 0.0, 0.0, 0.0];
            curLog ← (curLog + 1) MOD maxLog;

            IF ratio > Real.Float[maxCountsPerStep/countsPerStep] THEN
              countsPerStep ← maxCountsPerStep
            ELSE
            countsPerStep ← MAX[1, Real.RoundLI[countsPerStep*ratio]];
            int ← IF RK THEN 'R ELSE 'A;
            printMsg["t = %10.3f, step = %10lu%c",
                     @t, @countsPerStep, @int];
            IF countsPerStep = 1 THEN SIGNAL solveError["Step too small"]
          END
      END;

    saveState:  PROCEDURE =
      BEGIN
        nodes:  nodePtr ← nodeList;
        inds:  inductorPtr ← inductorList;
        volts:  vSourcePtr ← vSourceList;

        UNTIL nodes = NIL DO
          OPEN nodes↑.nHist;
          oldy ← y;
          f4 ← f3; f3 ← f2; f2 ← f1; f1 ← f0;
          IF y < Vmin THEN SIGNAL solveTest["Node below Vmin"]
          ELSE
            IF y > Vmax THEN SIGNAL solveTest["Node above Vmax"]
            ELSE
              IF ABS[f0] > dvdtmax THEN
                SIGNAL solveTest["Rate above max"];
          nodes ← nodes↑.nextNode
        ENDLOOP;
        UNTIL inds = NIL DO
          OPEN inds↑.iHist;
          oldy ← y;
          f4 ← f3; f3 ← f2; f2 ← f1;
          f1 ← inds↑.posNode↑.nHist.y - inds↑.negNode↑.nHist.y;
          inds ← inds↑.nextInductor
        ENDLOOP;
        UNTIL volts = NIL DO
          volts↑.oldCurrent ← volts↑.vsCurrent;
          volts ← volts↑.nextvSource
        ENDLOOP
      END;

    restoreState:  PROCEDURE =
      BEGIN
        nodes:  nodePtr ← nodeList;
        inds:  inductorPtr ← inductorList;
        volts:  vSourcePtr ← vSourceList;

        UNTIL nodes = NIL DO
          nodes↑.nHist.y ← nodes↑.nHist.oldy;
          nodes↑.nHist.f0 ← nodes↑.nHist.f1;
          nodes ← nodes↑.nextNode
        ENDLOOP;
        UNTIL inds = NIL DO
          inds↑.iHist.y ← inds↑.iHist.oldy;
          inds ← inds↑.nextInductor
        ENDLOOP;
        UNTIL volts = NIL DO
          volts↑.vsCurrent ← volts↑.oldCurrent;
          volts ← volts↑.nextvSource
        ENDLOOP
      END;

    integration:  PROCEDURE[RK:  BOOLEAN] =
      BEGIN
        nGS:  CARDINAL;
        out, ok:   BOOLEAN;
        ratio, oldTime, DT:  REAL;
        oldLowStepcount, oldHighStepCount:  LONG CARDINAL;

        saveState[];
        oldTime ← t;
        oldLowStepcount ← lowStepCount;
        oldHighStepCount ← highStepCount;
        DO
          lowStepCount ← lowStepCount + countsPerStep;
          IF lowStepCount > maxCountsPerStep THEN
            BEGIN
              highStepCount ← highStepCount + 1;
              lowStepCount ← lowStepCount - maxCountsPerStep;
              out ← TRUE
            END
          ELSE out ← printStep;
          t ← tMin + dT*lowStepCount + (dT*maxCountsPerStep)*highStepCount;
          DT ← dT*countsPerStep;
          BEGIN
            ENABLE Retreat =>
              BEGIN
                printSysWindow[cause];
                ok ← FALSE;
                ratio ← retRatio;
                GOTO FlushIt
              END;

            IF numGoodSteps < 4 THEN
              BEGIN
                [ratio, nGS, ok] ← RungeKutta[DT];
                IF ok THEN nGS ← GSiteration[DT, printAll,, TRUE] + nGS;
                ratio ← RF.SqRt[ratio]*RKfac;
                IF ok AND ~RK THEN numGoodSteps ← numGoodSteps + 1
              END
            ELSE
              BEGIN
                AMcount ← AMcount + 1;
                [ratio, nGS, ok] ← AdamsMoulton[DT];
                IF ok THEN
                  BEGIN
                    nGS ← GSiteration[DT, printAll,, TRUE] + nGS;
                    IF ratio > AMup AND AMcount >= AMdelay THEN
                      BEGIN
                        ratio ← RF.SqRt[RF.SqRt[ratio]];
                        changeStepSize[ratio, FALSE];
                        AMcount ← 0
                      END
                  END
                ELSE ratio ← RF.SqRt[ratio]
              END
          EXITS
            FlushIt => NULL
          END;
          IF ok THEN EXIT;
          restoreState[];
          t ← oldTime;
          highStepCount ← oldHighStepCount;
          lowStepCount ← oldLowStepcount;
          changeStepSize[MIN[ratio, minRatio], numGoodSteps < 4];
          AMcount ← 0
        ENDLOOP;
        advanceCursor[];
        IF out THEN printFromList[nGS, t, printIter];
        IF out THEN plotFromList[t];
        WHILE tBreak > 0.0 AND t >= tBreak DO oldTime ← t ENDLOOP
      END;


    zeroVars:  PROCEDURE =
      BEGIN
        n:     nodePtr ← nodeList;
        inds:  inductorPtr ← inductorList;
        vSs:   vSourcePtr ← vSourceList;

        gndNode↑.marked ← TRUE;
        UNTIL n = NIL DO
          n↑.nHist ← initialHistory;
          n↑.marked ← FALSE;
          n ← n↑.nextNode
        ENDLOOP;
        UNTIL inds = NIL DO
          inds↑.iHist ← initialHistory;
          inds ← inds↑.nextInductor
        ENDLOOP;
        UNTIL vSs = NIL DO
          vSs↑.vsCurrent ← vSs↑.oldCurrent ← 0.0;
          IF vSs↑.controller = NIL THEN
            IF vSs↑.posNode = gndNode THEN vSs↑.negNode↑.marked ← TRUE
            ELSE
              IF vSs↑.negNode = gndNode THEN vSs↑.posNode↑.marked ← TRUE;
          vSs ← vSs↑.nextvSource
        ENDLOOP
      END;

    getStepSize:  PROCEDURE RETURNS[stepOK:  BOOLEAN] =
      BEGIN
        vMax, dvdtMax, frac:  REAL ← 0.0;
        inds:  inductorPtr ← inductorList;
        nodes: nodePtr ← nodeList;

        lowStepCount ← 0;
        highStepCount ← 0;
        numberOfSteps ← Real.FixC[tMax/printdT];
        worstNode ← NIL;
        UNTIL nodes = NIL DO
          vMax ← MAX[ABS[nodes↑.nHist.y], vMax];
          IF ABS[nodes↑.nHist.f0] > dvdtMax THEN
            BEGIN
              dvdtMax ← ABS[nodes↑.nHist.f0];
              worstNode ← nodes
            END;
          nodes ← nodes↑.nextNode
        ENDLOOP;
        frac ← IF dvdtMax > 0.0 AND vMax > 0.0 THEN intTol*vMax/(dvdtMax*printdT)
               ELSE initStep;
        dT ← printdT/maxCountsPerStep;
        dvdtTrunc ← dvdtFac*intTol;
        countsPerStep ← Real.RoundLI[maxCountsPerStep*MIN[frac, initStep]];
        countsPerStep ← MAX[countsPerStep, 1];
        IF countsPerStep = 1 THEN
          BEGIN
            stepOK ← FALSE;
            printMsg["Initial step is too small!"]
          END
        ELSE
          BEGIN
            stepOK ← TRUE;
            printMsg["Initial step is %ld.", @countsPerStep]
          END
      END;

    setICs:  PUBLIC PROCEDURE =
      BEGIN
        n:  nodePtr;
        b:  branchPtr;
        v:  REAL;
        allNodes:  BOOLEAN;

        zeroVars[];
        IF item # leftB THEN error[700] ELSE next[];
        IF item = number THEN
          BEGIN
            tMin ← value;
            next[];
            IF item = comma THEN next[] ELSE error[702]
          END;
        DO
          allNodes ← item = star;
          IF allNodes THEN next[] ELSE [n, b] ← findNodeOrBranch[];
          IF item = leftArrow THEN next[] ELSE error[703];
          v ← getSignedNumber[];
          IF allNodes THEN
            FOR n ← nodeList, n↑.nextNode UNTIL n = NIL DO
              n↑.nHist.y ← v
            ENDLOOP
          ELSE
            IF n # NIL THEN n↑.nHist.y ← v
            ELSE
              IF b # NIL THEN
                WITH b↑ SELECT FROM
                  inductor => iHist.y ← v
                ENDCASE => error[721]
              ELSE error[720];
          IF item = comma THEN next[] ELSE EXIT
        ENDLOOP;
        IF item # rightB THEN error[701, TRUE] ELSE next[];
        icsSet ← TRUE
      END;

    runIt:  PUBLIC PROCEDURE RETURNS[REAL] =
      BEGIN
        n:  CARDINAL;

        IF item # leftB THEN error[700] ELSE next[];
        runParms[];
        IF item # rightB THEN error[701] ELSE next[];
        IF tMax <= tMin THEN error[731];
        IF ~AnyErrors[] THEN
          BEGIN
            ENABLE Failure => {error[errorNum]; GOTO quit};
            IF ~icsSet THEN zeroVars[];
            initPlot[tMin, tMin + tMax, TRUE];
            t ← tMin;
            IF printdT <= 0.0 THEN printdT ← tMax/dfltInts;
            dT ← printdT;
            n ← GSiteration[0.0, printAll, TRUE, TRUE];
            printFromList[n, t, printIter];
            plotFromList[t];
            IF ~getStepSize[] THEN RETURN[tMin];
            numGoodSteps ← 1;
            WHILE highStepCount <= numberOfSteps DO
              integration[doRungeKutta];
              IF canned THEN EXIT;
              IF checkPoint THEN
                BEGIN
                  checkPoint ← FALSE;
                  dumpAll[t]
                END
            ENDLOOP;
            icsSet ← FALSE;
            RETURN[t]
          EXITS
            quit => RETURN[t];
          END
        ELSE RETURN[tMin]
      END;

    canIt:  PUBLIC PROCEDURE =
      BEGIN
        canned ← TRUE
      END;

    checkIt:  PUBLIC PROCEDURE =
      BEGIN
        checkPoint ← TRUE
      END;

  END.