/**********
ngspice-jit: pseudo-transient DC continuation (Eldo-style pstran/dptran)
**********/

/* CKTpstranOP(ckt, firstmode, continuemode, iterlim)
 *
 * Last-resort operating-point method, tried after Newton, gmin stepping,
 * source stepping and optran have failed.  An artificial capacitor Cp is
 * attached from every node to ground and the resulting system is
 * integrated in a pseudo time with backward Euler and no truncation-error
 * control: only the stationary point matters, not the path.  The BE
 * companion of the pseudo capacitor is a conductance g = Cp/dt on the
 * matrix diagonal (applied through the existing CKTdiagGmin augmentation)
 * plus a current g*v_accepted[n] on the RHS (applied in NIiter(), gated by
 * CKTpstranG/CKTpstranHist).  The pseudo timestep grows aggressively on
 * success; as dt -> infinity the artificial stamps vanish, so the
 * stationary point is the exact DC solution of the original circuit.
 *
 * The damped variant (.option dptran) additionally clamps the Newton
 * voltage update per iteration (CKTpstranDampV, also handled in NIiter()),
 * trading iterations for robustness on circuits with positive feedback.
 *
 * Structured after dynamic_gmin() in cktop.c.  Invariant preserved: no
 * exit path leaves CKTdiagGmin != CKTgshunt, CKTpstranG != 0 or
 * CKTpstranHist != NULL.
 *
 * return value:
 * 0 -> method converged
 * 1 -> method failed
 */

#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "ngspice/devdefs.h"
#include "ngspice/sperror.h"
#include "ngspice/fteext.h"

#ifdef XSPICE
#include "ngspice/enh.h"
#endif

#define PSTRAN_DAMP_V   0.3     /* dptran per-iteration voltage clamp [V] */
#define PSTRAN_GROW     2.0     /* dt growth after a success */
#define PSTRAN_GROW_HI 10.0     /* dt growth after several successes */
#define PSTRAN_SHRINK   2.0     /* dt reduction after a failure; keep it no
                                   larger than PSTRAN_GROW, or any region
                                   with less than ~3/4 step acceptance
                                   random-walks dt downward and burns the
                                   step budget without crossing */
#define PSTRAN_MINITER  100     /* Newton budget per pseudo step */

int
CKTpstranOP(CKTcircuit *ckt, long int firstmode,
            long int continuemode, int iterlim)
{
    double dt, dtmax, Cp, maxdelta;
    double *hist, *OldCKTstate0;
    int converged, steps, streak, NumNodes, i;
    CKTnode *n;

    SPfrontEnd->IFerrorf(ERR_INFO,
        "Starting pseudo-transient continuation (%s)",
        ckt->CKTdptran ? "dptran" : "pstran");

    NumNodes = 0;
    for (n = ckt->CKTnodes; n; n = n->next)
        NumNodes++;

    hist = TMALLOC(double, NumNodes + 1);
    OldCKTstate0 = TMALLOC(double, ckt->CKTnumStates + 1);

    /* start the pseudo transient from a cold circuit, like the other
       steppers do; the failed attempts before us may have left wild
       values behind */
    for (i = 0; i <= NumNodes; i++)
        hist[i] = 0.0;
    for (n = ckt->CKTnodes; n; n = n->next)
        ckt->CKTrhsOld[n->number] = 0.0;
    for (i = 0; i < ckt->CKTnumStates; i++)
        ckt->CKTstate0[i] = 0.0;
    memcpy(OldCKTstate0, ckt->CKTstate0,
           (size_t) ckt->CKTnumStates * sizeof(double));

    Cp = ckt->CKTpstranCp;
    dt = ckt->CKTpstranDtInit;
    if (Cp <= 0.0 || dt <= 0.0) {
        SPfrontEnd->IFerrorf(ERR_WARNING,
            "pstran: pstrancp and pstrandtinit must be positive");
        FREE(hist);
        FREE(OldCKTstate0);
        return 1;
    }

    /* dt at which the pseudo conductance has decayed to gmin (or below
       the shunt the user asked for anyway) */
    dtmax = ckt->CKTpstranDtMax;
    if (dtmax <= 0.0)
        dtmax = Cp / MAX(ckt->CKTgmin, ckt->CKTgshunt);
    if (dtmax < dt)
        dtmax = dt;

    ckt->CKTmode = firstmode;
    ckt->CKTpstranHist = hist;
    if (ckt->CKTdptran)
        ckt->CKTpstranDampV = PSTRAN_DAMP_V;

    converged = 1;
    streak = 0;

    for (steps = 0; steps < ckt->CKTpstranMaxSteps; steps++) {

        ckt->CKTpstranG = Cp / dt;
        ckt->CKTdiagGmin = ckt->CKTgshunt + ckt->CKTpstranG;

        if (ft_ngdebug)
            fprintf(stderr, "pstran: dt = %12.4e g = %12.4e\n",
                    dt, ckt->CKTpstranG);

        ckt->CKTnoncon = 1;
        converged = NIiter(ckt, MAX(ckt->CKTdcTrcvMaxIter, PSTRAN_MINITER));

        if (converged == 0) {
            ckt->CKTmode = continuemode;

            maxdelta = 0.0;
            for (n = ckt->CKTnodes; n; n = n->next)
                if (n->type == SP_VOLTAGE) {
                    double diff = fabs(ckt->CKTrhsOld[n->number] -
                                       hist[n->number]);
                    double tol = ckt->CKTreltol *
                        fabs(ckt->CKTrhsOld[n->number]) + ckt->CKTvoltTol;
                    if (diff - tol > maxdelta)
                        maxdelta = diff - tol;
                }

            for (n = ckt->CKTnodes; n; n = n->next)
                hist[n->number] = ckt->CKTrhsOld[n->number];
            memcpy(OldCKTstate0, ckt->CKTstate0,
                   (size_t) ckt->CKTnumStates * sizeof(double));

            /* stationary at a vanished pseudo stamp: we are done */
            if (dt >= dtmax && maxdelta <= 0.0)
                break;

            streak++;
            dt *= (streak >= 3) ? PSTRAN_GROW_HI : PSTRAN_GROW;
            if (dt > dtmax)
                dt = dtmax;
        } else {
            streak = 0;
            dt /= PSTRAN_SHRINK;
            if (dt < ckt->CKTpstranDtInit * 1e-9) {
                if (ft_ngdebug)
                    fprintf(stderr, "pstran: pseudo timestep collapsed\n");
                break;      /* failed */
            }
            /* roll back to the last accepted pseudo-time solution */
            for (n = ckt->CKTnodes; n; n = n->next)
                ckt->CKTrhsOld[n->number] = hist[n->number];
            memcpy(ckt->CKTstate0, OldCKTstate0,
                   (size_t) ckt->CKTnumStates * sizeof(double));
        }
    }

    /* remove the pseudo stamps unconditionally */
    ckt->CKTpstranG = 0.0;
    ckt->CKTpstranHist = NULL;
    ckt->CKTpstranDampV = 0.0;
    ckt->CKTdiagGmin = ckt->CKTgshunt;

    if (converged == 0) {
#ifdef XSPICE
        /* gtri - wbk - add convergence problem reporting flags */
        ckt->enh->conv_debug.last_NIiter_call = MIF_TRUE;
#endif
        /* one clean Newton without the pseudo stamp; from the settled
           point it must converge in a few iterations */
        converged = NIiter(ckt, iterlim);
    }

    FREE(hist);
    FREE(OldCKTstate0);

    if (converged != 0) {
        SPfrontEnd->IFerrorf(ERR_WARNING,
            "Pseudo-transient continuation failed");
    } else {
        SPfrontEnd->IFerrorf(ERR_INFO,
            "Pseudo-transient continuation completed");
#ifdef XSPICE
        /* gtri - wbk - add convergence problem reporting flags */
        ckt->enh->conv_debug.last_NIiter_call = MIF_FALSE;
#endif
    }

    return converged;
}
