/**********
Copyright 1990 Regents of the University of California.  All rights reserved.
Author: 1985 Thomas L. Quarles
Modified: September 2003 Paolo Nenzi
**********/

#include "ngspice/ngspice.h"
#include "ngspice/cktdefs.h"
#include "capdefs.h"
#include "ngspice/trandefs.h"
#include "ngspice/sperror.h"
#include "ngspice/suffix.h"

int
CAPload(GENmodel *inModel, CKTcircuit *ckt)
/* actually load the current capacitance value into the
 * sparse matrix previously provided
 */
{
    CAPmodel *model = (CAPmodel*)inModel;
    CAPinstance *here;
    int cond1;
    double vcap;
    double geq;
    double ceq;
    int error;
    double m;
    double capeff;  /* differential capacitance dQ/dV */
    double qval;    /* charge Q(V) */

    /* check if capacitors are in the circuit or are open circuited */
    if(ckt->CKTmode & (MODETRAN|MODEAC|MODETRANOP) ) {
        /* evaluate device independent analysis conditions */
        cond1=
            ( ( (ckt->CKTmode & MODEDC) &&
                (ckt->CKTmode & MODEINITJCT) )
              || ( ( ckt->CKTmode & MODEUIC) &&
                   ( ckt->CKTmode & MODEINITTRAN) ) ) ;
        /*  loop through all the capacitor models */
        for( ; model != NULL; model = CAPnextModel(model)) {

            /* loop through all the instances of the model */
            for (here = CAPinstances(model); here != NULL ;
                    here=CAPnextInstance(here)) {

                m = here->CAPm;

                if(cond1) {
                    vcap = here->CAPinitCond;
                } else {
                    vcap = *(ckt->CKTrhsOld+here->CAPposNode) -
                           *(ckt->CKTrhsOld+here->CAPnegNode) ;
                }
                /* Charge-conserving voltage-dependent capacitance.
                 * With C(V) = CAPcapac*(1 + vc1*V + vc2*V^2), the stored charge
                 * is the integral Q(V) = CAPcapac*(V + vc1*V^2/2 + vc2*V^3/3) and
                 * the differential capacitance (Jacobian) is dQ/dV = C(V).
                 * This is the native, charge-conserving alternative to realizing
                 * a voltage-dependent cap with a behavioral differentiator. */
                capeff = here->CAPcapac;
                qval   = here->CAPcapac * vcap;
                if (here->CAPvc1Given || here->CAPvc2Given ||
                    model->CAPvc1Given || model->CAPvc2Given) {
                    /* instance vc1/vc2 override model vc1/vc2 (default 0),
                       mirroring how tc1/tc2 are resolved in CAPtemp() */
                    double vc1 = here->CAPvc1Given ? here->CAPvc1 :
                                 (model->CAPvc1Given ? model->CAPvoltCoeff1 : 0.0);
                    double vc2 = here->CAPvc2Given ? here->CAPvc2 :
                                 (model->CAPvc2Given ? model->CAPvoltCoeff2 : 0.0);
                    qval   = here->CAPcapac *
                             (vcap + vc1*vcap*vcap/2.0 + vc2*vcap*vcap*vcap/3.0);
                    capeff = here->CAPcapac * (1.0 + vc1*vcap + vc2*vcap*vcap);
                }
                if (here->CAPdangling) {
                    /* Eldo-style topology reduction: this cap hangs on a
                     * floating (degree-1) node.  Remove it from the system in
                     * EVERY mode -- including the DC operating point: pin the
                     * floating node(s) with a unit diagonal so the matrix stays
                     * nonsingular, and contribute no charge or current.  In
                     * transient this eliminates the spurious LTE pressure that
                     * drives "Timestep too small"; in the DC op it stops the
                     * solver from reporting this known, about-to-be-pruned node
                     * as a "singular matrix" -- a false alarm that would mask
                     * genuine singularities.  (Mirrors the unconditional
                     * dangling handling in resload.c; set in CKTtopologyReduce().) */
                    if (here->CAPdangling & 1) *(here->CAPposPosPtr) += 1.0;
                    if (here->CAPdangling & 2) *(here->CAPnegNegPtr) += 1.0;
                    *(ckt->CKTstate0+here->CAPqcap) = 0.0;
                    continue;
                }
                if(ckt->CKTmode & (MODETRAN | MODEAC)) {
#ifndef PREDICTOR
                    if(ckt->CKTmode & MODEINITPRED) {
                        *(ckt->CKTstate0+here->CAPqcap) =
                            *(ckt->CKTstate1+here->CAPqcap);
                    } else { /* only const caps - no poly's */
#endif /* PREDICTOR */
                        *(ckt->CKTstate0+here->CAPqcap) = qval;
                        if((ckt->CKTmode & MODEINITTRAN)) {
                            *(ckt->CKTstate1+here->CAPqcap) =
                                *(ckt->CKTstate0+here->CAPqcap);
                        }
#ifndef PREDICTOR
                    }
#endif /* PREDICTOR */
                    error = NIintegrate(ckt,&geq,&ceq,capeff,
                                        here->CAPqcap);
                    if(error) return(error);
                    /* Companion constant for a general nonlinear charge Q(V):
                     * the stamped current is geq*V + ceq with geq = ag0*dQ/dV,
                     * so the correct constant is ceq = I - geq*V (I = dQ/dt =
                     * state0[CAPccap]).  NIintegrate returns I - ag0*Q instead,
                     * which assumes Q = C*V; for a vc1/vc2 cap that leaks a
                     * spurious current ~ag0*(dQ/dV*V - Q) that grows with |V|
                     * and ~1/dt.  Recompute it (identical value when linear). */
                    if (here->CAPvc1Given || here->CAPvc2Given ||
                        model->CAPvc1Given || model->CAPvc2Given)
                        ceq = *(ckt->CKTstate0 + here->CAPccap) - geq * vcap;
                    if(ckt->CKTmode & MODEINITTRAN) {
                        *(ckt->CKTstate1+here->CAPccap) =
                            *(ckt->CKTstate0+here->CAPccap);
                    }
                    /* Tiny conductance in parallel with the capacitor so that a
                     * node connected only through capacitors (e.g. a dangling
                     * compensation cap) still has a DC reference and cannot drift
                     * into a singular/ill-conditioned operating point.  This
                     * restores the 1e15 ohm "avoid floating nodes" resistor that
                     * the old behavioral C=f(v) expansion placed across the cap;
                     * 1e-15 S is negligible for caps on driven nodes. */
                    {
                        double gpar = 1e-15;
                        *(here->CAPposPosPtr) += m * geq + gpar;
                        *(here->CAPnegNegPtr) += m * geq + gpar;
                        *(here->CAPposNegPtr) -= m * geq + gpar;
                        *(here->CAPnegPosPtr) -= m * geq + gpar;
                    }
                    *(ckt->CKTrhs+here->CAPposNode) -= m * ceq;
                    *(ckt->CKTrhs+here->CAPnegNode) += m * ceq;
                } else
                    *(ckt->CKTstate0+here->CAPqcap) = qval;
            }
        }
    } else {
        /* Pure DC operating point (MODEDCOP / DC sweep): capacitors are open
         * circuits and the loop above is skipped entirely.  But a cap on a
         * dangling (degree-1) node is the ONE thing that still needs the
         * matrix: without a stamp that node's row is all-zero and the solver
         * reports a spurious "singular matrix: check node <x>" -- for a node
         * topology reduction has already decided to prune.  Pin it here too
         * (unit diagonal, V=0) so the DC op is clean and any remaining
         * "singular matrix" warning is a GENUINE one.  Mirrors resload.c,
         * which pins dangling resistors in every mode. */
        for( ; model != NULL; model = CAPnextModel(model))
            for (here = CAPinstances(model); here != NULL;
                    here = CAPnextInstance(here)) {
                if (!here->CAPdangling)
                    continue;
                if (here->CAPdangling & 1) *(here->CAPposPosPtr) += 1.0;
                if (here->CAPdangling & 2) *(here->CAPnegNegPtr) += 1.0;
            }
    }
    return(OK);
}

