/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles
**********/

    /* CKTsetup(ckt)
     * this is a driver program to iterate through all the various
     * setup functions provided for the circuit elements in the
     * given circuit
     */

#include "ngspice/ngspice.h"
#include "ngspice/smpdefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/devdefs.h"
#include "ngspice/gendefs.h"
#include "ngspice/sperror.h"
#include "ngspice/fteext.h"
#include "ngspice/cpextern.h"

/* device headers needed by CKTtopologyReduce() to mark dangling passives */
#include "../devices/cap/capdefs.h"
#include "../devices/res/resdefs.h"

#ifdef XSPICE
#include "ngspice/enh.h"
/* XSPICE code models store their terminals in the MIF conn/port tree, not in
 * the generic GENnode() array, and report DEVpublic.terms == 0.  CKTtopologyReduce()
 * walks that tree explicitly so code-model nodes are counted. */
#include "ngspice/mifdefs.h"
#endif

#ifdef USE_OMP
#include <omp.h>
#include "ngspice/cpextern.h"
#endif

#define CKALLOC(var,size,type) \
    if(size && ((var = TMALLOC(type, size)) == NULL)){\
            return(E_NOMEM);\
}

/* Eldo-style topology reduction ("KEEPDANGLING off").
 *
 * A node whose only connection is a single passive terminal (a degree-1
 * "dangling" node, e.g. the dead end of an opamp compensation cap) carries no
 * steady current/charge, but its row becomes ill-conditioned as the timestep
 * shrinks and shows up as a spurious "Timestep too small" abort.  Commercial
 * fast simulators (Eldo) simply remove such elements from the matrix.  This
 * pass does the same: it finds dangling passive leaves and marks the owning
 * capacitor/resistor so that, at load time, the device contributes nothing and
 * its floating node is pinned with a unit diagonal (kept nonsingular).
 *
 * Node degree is counted GENERICALLY over every device type through the
 * GENnode() terminal array, so no device family can be missed (which would
 * wrongly prune a live node).  XSPICE code models are the one exception: they
 * report DEVpublic.terms == 0 and keep their node numbers in the MIF conn/port
 * tree rather than in GENnode(), so they are walked separately below -- without
 * that, a node touched only by a code model would look floating and its passive
 * would be wrongly pruned.  Only capacitors and resistors are ever removed.
 * Disable with `set no_topo_reduce` in .spiceinit. */
static void
CKTtopologyReduce(CKTcircuit *ckt)
{
    int i, t, nterm, maxnode, removed_total = 0, reported = 0;
    int captype = -1, restype = -1;
    int *degree;
    GENmodel *gmod;
    GENinstance *ginst;

    if (cp_getvar("no_topo_reduce", CP_BOOL, NULL, 0))
        return;

    maxnode = ckt->CKTmaxEqNum;
    if (maxnode < 1)
        return;

    degree = TMALLOC(int, maxnode + 1);
    if (!degree)
        return;
    for (i = 0; i <= maxnode; i++)
        degree[i] = 0;

    /* complete, type-agnostic node degree over all device terminals */
    for (i = 0; i < DEVmaxnum; i++) {
        if (!DEVices[i] || !ckt->CKThead[i])
            continue;
        if (DEVices[i]->DEVpublic.name) {
            if (!strcmp(DEVices[i]->DEVpublic.name, "Capacitor")) captype = i;
            else if (!strcmp(DEVices[i]->DEVpublic.name, "Resistor")) restype = i;
        }

#ifdef XSPICE
        /* XSPICE code models: DEVpublic.terms is 0 and GENnode() does not hold
         * their nodes, so the generic walk below would count nothing for them.
         * Walk the MIF conn/port tree and count every analog port's pos/neg node
         * instead.  Event-driven (digital) and vsource-current ports leave these
         * at 0 (calloc'd) and are skipped by the nd > 0 test.
         *
         * A code-model instance IS a MIFinstance, so it is identified by its
         * instance size -- a plain compile-time integer (sizeof(MIFinstance))
         * that is identical across the .cm modules.  A function-pointer test
         * (DEVsetup == MIFsetup) is NOT reliable here: code models linked into
         * separate .cm shared objects carry their own copy of MIFsetup, so the
         * pointers differ from this translation unit's. */
        if (DEVices[i]->DEVinstSize &&
            *(DEVices[i]->DEVinstSize) == (int) sizeof(MIFinstance)) {
            for (gmod = ckt->CKThead[i]; gmod; gmod = gmod->GENnextModel)
                for (ginst = gmod->GENinstances; ginst; ginst = ginst->GENnextInstance) {
                    MIFinstance *mif = (MIFinstance *) ginst;
                    int c, p;
                    for (c = 0; c < mif->num_conn; c++) {
                        Mif_Conn_Data_t *conn = mif->conn[c];
                        if (!conn || conn->is_null)
                            continue;
                        for (p = 0; p < conn->size; p++) {
                            Mif_Port_Data_t *port = conn->port[p];
                            int nd;
                            if (!port || port->is_null)
                                continue;
                            nd = port->smp_data.pos_node;
                            if (nd > 0 && nd <= maxnode) degree[nd]++;
                            nd = port->smp_data.neg_node;
                            if (nd > 0 && nd <= maxnode) degree[nd]++;
                        }
                    }
                }
            continue;   /* code model handled; skip the generic terminal walk */
        }
#endif

        if (!DEVices[i]->DEVpublic.terms)
            continue;
        nterm = *(DEVices[i]->DEVpublic.terms);
        for (gmod = ckt->CKThead[i]; gmod; gmod = gmod->GENnextModel)
            for (ginst = gmod->GENinstances; ginst; ginst = ginst->GENnextInstance) {
                int *nodes = GENnode(ginst);
                for (t = 0; t < nterm; t++) {
                    int nd = nodes[t];
                    if (nd > 0 && nd <= maxnode)
                        degree[nd]++;
                }
            }
    }

    if (captype < 0 && restype < 0) {
        FREE(degree);
        return;
    }

    /* Leaf-prune to a fixpoint: removing a dangling passive can drop its other
     * node to degree 1, exposing the next leaf of a dangling chain. */
    for (;;) {
        int removed_this_pass = 0;

        if (captype >= 0) {
            CAPmodel *cm;
            for (cm = (CAPmodel *)ckt->CKThead[captype]; cm; cm = CAPnextModel(cm))
                for (CAPinstance *ci = CAPinstances(cm); ci; ci = CAPnextInstance(ci)) {
                    int pn = ci->CAPposNode, nn = ci->CAPnegNode, mode = 0;
                    if (ci->CAPdangling)
                        continue;
                    if (pn > 0 && degree[pn] == 1) mode |= 1;
                    if (nn > 0 && degree[nn] == 1) mode |= 2;
                    if (!mode)
                        continue;
                    ci->CAPdangling = mode;
                    if (mode & 1) degree[pn] = 0; else if (pn > 0) degree[pn]--;
                    if (mode & 2) degree[nn] = 0; else if (nn > 0) degree[nn]--;
                    removed_this_pass++;
                    if (reported++ < 40)
                        fprintf(stdout,
                            "Topology reduction: removed dangling capacitor %s "
                            "(floating node %s)\n", ci->CAPname,
                            (char *)CKTnodName(ckt, (mode & 2) ? nn : pn));
                }
        }

        if (restype >= 0) {
            RESmodel *rm;
            for (rm = (RESmodel *)ckt->CKThead[restype]; rm; rm = RESnextModel(rm))
                for (RESinstance *ri = RESinstances(rm); ri; ri = RESnextInstance(ri)) {
                    int pn = ri->RESposNode, nn = ri->RESnegNode, mode = 0;
                    if (ri->RESdangling)
                        continue;
                    if (pn > 0 && degree[pn] == 1) mode |= 1;
                    if (nn > 0 && degree[nn] == 1) mode |= 2;
                    if (!mode)
                        continue;
                    ri->RESdangling = mode;
                    if (mode & 1) degree[pn] = 0; else if (pn > 0) degree[pn]--;
                    if (mode & 2) degree[nn] = 0; else if (nn > 0) degree[nn]--;
                    removed_this_pass++;
                    if (reported++ < 40)
                        fprintf(stdout,
                            "Topology reduction: removed dangling resistor %s "
                            "(floating node %s)\n", ri->RESname,
                            (char *)CKTnodName(ckt, (mode & 2) ? nn : pn));
                }
        }

        removed_total += removed_this_pass;
        if (!removed_this_pass)
            break;
    }

    if (removed_total)
        fprintf(stdout, "Topology reduction: %d dangling passive(s) removed "
                "from the matrix.\n", removed_total);

    /* Floating nodes.  Whatever survives the passive prune with fewer than two
     * device terminals still on it cannot carry current: a dangling MOS gate,
     * a MOS left with only its bulk connected, a diode with no path to an
     * active device.  Extracted (PEX) netlists are full of them -- Eldo prunes
     * them at elaboration ("NODE ... has less than two connections", "MOS ...
     * removed because only its bulk is connected") and without that the DC
     * matrix is rank-deficient, which no amount of source- or gmin-stepping can
     * repair: the KLU pivot merely relocates which node gets blamed.
     *
     * We do not delete the device (that would need per-family surgery); we give
     * the node a DC reference through CKTfloatGmin.  Since no current can reach
     * the node, the tie is electrically inert -- and because it is applied ONLY
     * here and not to every node like .option rshunt, it cannot load a real
     * high-impedance node.
     *
     * degree[] is the post-prune count: a pinned passive leaf was already set
     * to 0 above and is handled by cap/resload, so only degree == 1 is left for
     * us.  (Device-internal nodes -- MOS drain/source primes -- are not in
     * GENnode() and so never appear here.) */
    if (ckt->CKTfloatGmin > 0.0 && ckt->CKTmatrix) {
        int nfloat = 0;
        IFuid *nodeName;
        CKTnode *nd;

        /* Node number -> name, built in ONE pass.
         *
         * NEVER call CKTnodName() from a loop over nodes: it is a LINEAR SCAN of
         * the node list (cktnodn.c), so a lookup per candidate is O(nodes^2).
         * The candidate set here is not small -- see the '#' note below, it is
         * every MOS internal node, ~157k on the PEX chip -- and each lookup then
         * chases ~1.4M `next' pointers, missing cache on every hop.  That is
         * ~1e11 dependent loads: it ran for HOURS inside CKTsetup, before a
         * single Newton iteration, and looked for all the world like a hang.
         * One pass over CKTnodes costs O(nodes) and makes the test O(1). */
        nodeName = TMALLOC(IFuid, (size_t) maxnode + 1);
        if (nodeName)
            for (nd = ckt->CKTnodes; nd; nd = nd->next)
                if (nd->number > 0 && nd->number <= maxnode)
                    nodeName[nd->number] = nd->name;

        /* Device-INTERNAL nodes must not be considered.  DEVpublic.terms is not
         * always the count of external terminals: BSIM4, for instance, declares
         * five ("Drain","Gate","Source","Bulk","Charge"), so the generic walk
         * above gives every MOS's internal node a degree of 1 and it would look
         * floating -- 157031 false positives on the PEX chip.  Such nodes are
         * created by the device itself (CKTmkVolt) and always carry its internal
         * stamps, so they are never floating.  ngspice names them "<inst>#..."
         * ('#' cannot occur in a parsed node name), which is the one property
         * that holds across every device family. */
        for (i = 1; i <= maxnode; i++)
            if (degree[i] == 1) {
                const char *nm = nodeName ? (const char *) nodeName[i] : NULL;
                if (nm && strchr(nm, '#'))
                    degree[i] = 2;      /* internal: not a candidate */
                else
                    nfloat++;
            }

        if (nfloat > 0) {
            int k = 0, shown = 0;
            bool verbose = cp_getvar("topo_verbose", CP_BOOL, NULL, 0);
            FREE(ckt->CKTfloatDiag);
            ckt->CKTfloatDiag = TMALLOC(double *, nfloat);
            if (ckt->CKTfloatDiag) {
                FILE *fdbg = verbose ? fopen("float_nodes.txt", "w") : NULL;
                for (i = 1; i <= maxnode; i++)
                    if (degree[i] == 1) {
                        const char *nm =
                            nodeName ? (const char *) nodeName[i] : "?";
                        ckt->CKTfloatDiag[k++] =
                            SMPmakeElt(ckt->CKTmatrix, i, i);
                        if (fdbg)
                            fprintf(fdbg, "%s\n", nm);
                        if (verbose && shown++ < 20)
                            fprintf(stdout, "  floating node: %s\n", nm);
                    }
                if (fdbg) fclose(fdbg);
                ckt->CKTnumFloatNodes = k;
                /* Quiet by default: this fires on ordinary decks (a dangling
                 * gate is common) and the regression suite compares stdout
                 * verbatim.  `set topo_verbose` to see it. */
                if (verbose)
                    fprintf(stdout,
                            "Topology reduction: %d floating node(s) tied to "
                            "ground through %g S (.option floatgmin).\n",
                            k, ckt->CKTfloatGmin);
            }
        }

        FREE(nodeName);
    }

    FREE(degree);
}

int
CKTsetup(CKTcircuit *ckt)
{
    int i;
    int error;
#ifdef USE_OMP
    int nthreads = 2;
#endif
#ifdef XSPICE
 /* gtri - begin - Setup for adding rshunt option resistors */
    CKTnode *node;
    int     num_nodes;
 /* gtri - end - Setup for adding rshunt option resistors */

#ifdef KLU
    BindElement BindNode, *matched, *BindStruct ;
    size_t nz ;
#endif
#endif

    SMPmatrix *matrix;

    if (!ckt->CKThead) {
        fprintf(stderr, "Error: No model list found, device setup not possible!\n");
        if (ft_stricterror)
            controlled_exit(EXIT_BAD);
        return E_PANIC;
    }
    if (!DEVices) {
        fprintf(stderr, "Error: No device list found, device setup not possible!\n");
        if (ft_stricterror)
            controlled_exit(EXIT_BAD);
        return E_PANIC;
    }

    ckt->CKTnumStates=0;

#ifdef WANT_SENSE2
    if(ckt->CKTsenInfo){
        error = CKTsenSetup(ckt);
        if (error)
            return(error);
    }
#endif

    if (ckt->CKTisSetup)
        return E_NOCHANGE;

    error = NIinit(ckt);
    if (error) 
        return(error);

    ckt->CKTisSetup = 1;

    matrix = ckt->CKTmatrix;

#ifdef USE_OMP
    if (!cp_getvar("num_threads", CP_NUM, &nthreads, 0))
        nthreads = 2;

    omp_set_num_threads(nthreads);
/*    if (nthreads == 1)
      printf("OpenMP: %d thread is requested in ngspice\n", nthreads);
    else
      printf("OpenMP: %d threads are requested in ngspice\n", nthreads);*/
#endif

#ifdef HAS_PROGREP
    SetAnalyse("Device Setup", 0);
#endif

    /* preserve CKTlastNode before invoking DEVsetup()
     * so we can check for incomplete CKTdltNNum() invocations
     * during DEVunsetup() causing an erronous circuit matrix
     *   when reinvoking CKTsetup()
     */
    ckt->prev_CKTlastNode = ckt->CKTlastNode;

    for (i=0;i<DEVmaxnum;i++) {
        if ( DEVices[i] && DEVices[i]->DEVsetup && ckt->CKThead[i] ) {
            error = DEVices[i]->DEVsetup (matrix, ckt->CKThead[i], ckt,
                    &ckt->CKTnumStates);
            if(error) return(error);
        }
    }

    /* Eldo-style topology reduction: mark dangling (degree-1) passive leaves
     * for removal.  Done after all device setups (so the node degrees are
     * complete) but before the matrix is converted/bound for the linear
     * solver -- it only changes load-time stamping, not the matrix structure. */
    CKTtopologyReduce(ckt);

    /* pstran/dptran (cktpstran.c): make sure every equation owns a diagonal
     * entry, so the pseudo-capacitor conductance (applied through the
     * CKTdiagGmin augmentation) reaches every row.  KLU keeps exactly the
     * stamped pattern and skips absent diagonals -- typically vsrc/B-source
     * branch rows -- which would leave the matching RHS history term
     * unbalanced there: an inconsistent stamp whose error grows as the
     * pseudo timestep shrinks.  Classic SPARSE creates these diagonals
     * itself during reordering; doing it here makes both solvers behave
     * the same.  Zero-valued entries, only when the option is enabled. */
    if (ckt->CKTpstran || ckt->CKTdptran) {
        CKTnode *pnode;
        for (pnode = ckt->CKTnodes; pnode; pnode = pnode->next)
            if (pnode->number > 0)
                SMPmakeElt(matrix, pnode->number, pnode->number);
    }

#ifdef XSPICE
  /* gtri - begin - Setup for adding rshunt option resistors */

    if(ckt->enh->rshunt_data.enabled) {

        /* Count number of voltage nodes in circuit */
        for(num_nodes = 0, node = ckt->CKTnodes; node; node = node->next)
            if((node->type == SP_VOLTAGE) && (node->number != 0))
                num_nodes++;

        /* Allocate space for the matrix diagonal data */
        if(num_nodes > 0) {
            FREE(ckt->enh->rshunt_data.diag);
            ckt->enh->rshunt_data.diag =
                 TMALLOC(double *, num_nodes);
        }

        /* Set the number of nodes in the rshunt data */
        ckt->enh->rshunt_data.num_nodes = num_nodes;

        /* Get/create matrix diagonal entry following what RESsetup does */
        for(i = 0, node = ckt->CKTnodes; node; node = node->next) {
            if((node->type == SP_VOLTAGE) && (node->number != 0)) {
                ckt->enh->rshunt_data.diag[i] =
                      SMPmakeElt(matrix,node->number,node->number);
                i++;
            }
        }
    }

    /* gtri - end - Setup for adding rshunt option resistors */
#endif

#ifdef KLU
    if (ckt->CKTmatrix->CKTkluMODE)
    {
        fprintf (stdout, "Using KLU as Direct Linear Solver\n") ;

        /* Convert the COO Storage to CSC for KLU and Fill the Binding Table */
        SMPconvertCOOtoCSC (matrix) ;

        /* Assign the KLU Pointers */
        for (i = 0 ; i < DEVmaxnum ; i++)
            if (DEVices [i] && DEVices [i]->DEVbindCSC && ckt->CKThead [i])
                DEVices [i]->DEVbindCSC (ckt->CKThead [i], ckt) ;

#ifdef XSPICE
        if (ckt->enh->rshunt_data.num_nodes > 0) {
            BindStruct = ckt->CKTmatrix->SMPkluMatrix->KLUmatrixBindStructCOO ;
            nz = (size_t)ckt->CKTmatrix->SMPkluMatrix->KLUmatrixLinkedListNZ ;
            for(i = 0, node = ckt->CKTnodes; node; node = node->next) {
                if((node->type == SP_VOLTAGE) && (node->number != 0)) {
                    BindNode.COO = ckt->enh->rshunt_data.diag [i] ;
                    BindNode.CSC = NULL ;
                    BindNode.CSC_Complex = NULL ;
                    matched = (BindElement *) bsearch (&BindNode, BindStruct, nz, sizeof (BindElement), BindCompare) ;
                    if (!matched) {
                        fprintf (stderr, "Error: Ptr %p not found in BindStruct Table\n", ckt->enh->rshunt_data.diag [i]) ;
                        ckt->enh->rshunt_data.diag[i] = NULL;
                    }
                    else
                        ckt->enh->rshunt_data.diag [i] = matched->CSC ;
                    i++;
                }
            }
        }
#endif

        /* Re-bind the floating-node ties (.option floatgmin) exactly as the
         * devices (DEVbindCSC, above) and rshunt are re-bound.
         *
         * SMPmakeElt() handed us pointers into the COO linked list.
         * SMPconvertCOOtoCSC() has just copied every value into a FRESH CSC
         * array, so those pointers are now stale.  Skipping this step does not
         * merely lose the tie -- CKTload() then does `*ptr += gmin' into freed
         * memory, corrupting the heap until KLU dies inside klu_factor().
         * (That is a real bug I shipped: floatgmin appeared to work under
         * SPARSE, which keeps the pointers valid, and silently did nothing --
         * then crashed -- under KLU, which is the solver every large deck uses.) */
        if (ckt->CKTnumFloatNodes > 0 && ckt->CKTfloatDiag) {
            BindStruct = ckt->CKTmatrix->SMPkluMatrix->KLUmatrixBindStructCOO ;
            nz = (size_t)ckt->CKTmatrix->SMPkluMatrix->KLUmatrixLinkedListNZ ;
            for (i = 0 ; i < ckt->CKTnumFloatNodes ; i++) {
                BindNode.COO = ckt->CKTfloatDiag [i] ;
                BindNode.CSC = NULL ;
                BindNode.CSC_Complex = NULL ;
                matched = (BindElement *) bsearch (&BindNode, BindStruct, nz,
                                                   sizeof (BindElement), BindCompare) ;
                if (!matched) {
                    fprintf (stderr, "Error: floatgmin ptr %p not found in "
                             "BindStruct Table\n", (void *) ckt->CKTfloatDiag [i]) ;
                    ckt->CKTfloatDiag [i] = NULL ;
                } else {
                    ckt->CKTfloatDiag [i] = matched->CSC ;
                }
            }
        }

    } else {
        fprintf (stdout, "Using SPARSE 1.3 as Direct Linear Solver\n") ;
    }
#endif

    for(i=0;i<=MAX(2,ckt->CKTmaxOrder)+1;i++) { /* dctran needs 3 states as minimum */
        CKALLOC(ckt->CKTstates[i],ckt->CKTnumStates,double);
    }
#ifdef WANT_SENSE2
    if(ckt->CKTsenInfo){
        /* to allocate memory to sensitivity structures if
         * it is not done before */

        error = NIsenReinit(ckt);
        if(error) return(error);
    }
#endif
    if(ckt->CKTniState & NIUNINITIALIZED) {
        error = NIreinit(ckt);
        if(error) return(error);
    }

    return(OK);
}

int
CKTunsetup(CKTcircuit *ckt)
{
    int i, error, e2;
    CKTnode *node;

    error = OK;
    if (!ckt->CKTisSetup)
        return OK;

    for(i=0;i<=ckt->CKTmaxOrder+1;i++) {
        tfree(ckt->CKTstates[i]);
    }

    /* added by HT 050802*/
    for(node=ckt->CKTnodes;node;node=node->next){
        if(node->icGiven || node->nsGiven) {
            node->ptr=NULL;
        }
    }

    for (i=0;i<DEVmaxnum;i++) {
        if ( DEVices[i] && DEVices[i]->DEVunsetup && ckt->CKThead[i] ) {
            e2 = DEVices[i]->DEVunsetup (ckt->CKThead[i], ckt);
            if (!error && e2)
                error = e2;
        }
    }

    if (ckt->prev_CKTlastNode != ckt->CKTlastNode) {
        fprintf(stderr, "Internal Error: incomplete CKTunsetup(), this will cause serious problems, please report this issue !\n");
        controlled_exit(EXIT_FAILURE);
    }
    ckt->prev_CKTlastNode = NULL;

    ckt->CKTisSetup = 0;
    if(error) return(error);

    NIdestroy(ckt);
    /*
    if (ckt->CKTmatrix)
        SMPdestroy(ckt->CKTmatrix);
    ckt->CKTmatrix = NULL;
    */

    return OK;
}
