ckt: dangling-passive topology reduction
Add CKTtopologyReduce() (cktsetup.c) to remove degree-1 dangling capacitors/resistors -- e.g. dead-end opamp compensation caps -- that otherwise cause spurious 'Timestep too small' aborts. Node degree is counted generically over all device types; the floating node is pinned with a unit diagonal. Disable with 'set no_topo_reduce'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b8ae73d4a2
commit
9575899a95
|
|
@ -13,8 +13,14 @@ Author: 1985 Thomas L. Quarles
|
||||||
#include "ngspice/smpdefs.h"
|
#include "ngspice/smpdefs.h"
|
||||||
#include "ngspice/cktdefs.h"
|
#include "ngspice/cktdefs.h"
|
||||||
#include "ngspice/devdefs.h"
|
#include "ngspice/devdefs.h"
|
||||||
|
#include "ngspice/gendefs.h"
|
||||||
#include "ngspice/sperror.h"
|
#include "ngspice/sperror.h"
|
||||||
#include "ngspice/fteext.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
|
#ifdef XSPICE
|
||||||
#include "ngspice/enh.h"
|
#include "ngspice/enh.h"
|
||||||
|
|
@ -30,6 +36,131 @@ Author: 1985 Thomas L. Quarles
|
||||||
return(E_NOMEM);\
|
return(E_NOMEM);\
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Topology reduction for removing dangling capacitors and resistors.
|
||||||
|
*
|
||||||
|
* 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 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). 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] || !DEVices[i]->DEVpublic.terms)
|
||||||
|
continue;
|
||||||
|
nterm = *(DEVices[i]->DEVpublic.terms);
|
||||||
|
if (DEVices[i]->DEVpublic.name) {
|
||||||
|
if (!strcmp(DEVices[i]->DEVpublic.name, "Capacitor")) captype = i;
|
||||||
|
else if (!strcmp(DEVices[i]->DEVpublic.name, "Resistor")) restype = i;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
FREE(degree);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
CKTsetup(CKTcircuit *ckt)
|
CKTsetup(CKTcircuit *ckt)
|
||||||
{
|
{
|
||||||
|
|
@ -116,6 +247,13 @@ CKTsetup(CKTcircuit *ckt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Topology reduction for removing dangling capacitors and resistors:
|
||||||
|
* 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);
|
||||||
|
|
||||||
#ifdef XSPICE
|
#ifdef XSPICE
|
||||||
/* gtri - begin - Setup for adding rshunt option resistors */
|
/* gtri - begin - Setup for adding rshunt option resistors */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ typedef struct sCAPinstance {
|
||||||
unsigned CAPbv_maxGiven : 1; /* flags indicates maximum voltage is given */
|
unsigned CAPbv_maxGiven : 1; /* flags indicates maximum voltage is given */
|
||||||
int CAPsenParmNo; /* parameter # for sensitivity use;
|
int CAPsenParmNo; /* parameter # for sensitivity use;
|
||||||
set equal to 0 if not a design parameter*/
|
set equal to 0 if not a design parameter*/
|
||||||
|
int CAPdangling; /* topology reduction: 0 = normal device;
|
||||||
|
bit0 set => pos node is a dangling (degree-1) floating node,
|
||||||
|
bit1 set => neg node is a dangling floating node. A dangling
|
||||||
|
cap is removed from the system: it contributes no
|
||||||
|
charge/current and its floating node(s) are pinned with a unit
|
||||||
|
diagonal so the matrix stays nonsingular. Set in CKTtopologyReduce(). */
|
||||||
|
|
||||||
#ifdef KLU
|
#ifdef KLU
|
||||||
BindElement *CAPposPosBinding ;
|
BindElement *CAPposPosBinding ;
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,19 @@ CAPload(GENmodel *inModel, CKTcircuit *ckt)
|
||||||
*(ckt->CKTrhsOld+here->CAPnegNode) ;
|
*(ckt->CKTrhsOld+here->CAPnegNode) ;
|
||||||
}
|
}
|
||||||
if(ckt->CKTmode & (MODETRAN | MODEAC)) {
|
if(ckt->CKTmode & (MODETRAN | MODEAC)) {
|
||||||
|
if (here->CAPdangling) {
|
||||||
|
/* Topology reduction: this cap hangs on a
|
||||||
|
* floating (degree-1) node. Remove it from the system:
|
||||||
|
* pin the floating node(s) with a unit diagonal so the
|
||||||
|
* matrix stays nonsingular, and contribute no charge or
|
||||||
|
* current. This eliminates the spurious LTE pressure
|
||||||
|
* that otherwise drives "Timestep too small" at the
|
||||||
|
* dangling node (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;
|
||||||
|
}
|
||||||
#ifndef PREDICTOR
|
#ifndef PREDICTOR
|
||||||
if(ckt->CKTmode & MODEINITPRED) {
|
if(ckt->CKTmode & MODEINITPRED) {
|
||||||
*(ckt->CKTstate0+here->CAPqcap) =
|
*(ckt->CKTstate0+here->CAPqcap) =
|
||||||
|
|
@ -71,10 +84,20 @@ CAPload(GENmodel *inModel, CKTcircuit *ckt)
|
||||||
*(ckt->CKTstate1+here->CAPccap) =
|
*(ckt->CKTstate1+here->CAPccap) =
|
||||||
*(ckt->CKTstate0+here->CAPccap);
|
*(ckt->CKTstate0+here->CAPccap);
|
||||||
}
|
}
|
||||||
*(here->CAPposPosPtr) += m * geq;
|
/* Tiny conductance in parallel with the capacitor so that a
|
||||||
*(here->CAPnegNegPtr) += m * geq;
|
* node connected only through capacitors (e.g. a dangling
|
||||||
*(here->CAPposNegPtr) -= m * geq;
|
* compensation cap) still has a DC reference and cannot drift
|
||||||
*(here->CAPnegPosPtr) -= m * geq;
|
* 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->CAPposNode) -= m * ceq;
|
||||||
*(ckt->CKTrhs+here->CAPnegNode) += m * ceq;
|
*(ckt->CKTrhs+here->CAPnegNode) += m * ceq;
|
||||||
} else
|
} else
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,10 @@ typedef struct sRESinstance {
|
||||||
unsigned RESbv_maxGiven : 1; /* flags indicates maximum voltage is given */
|
unsigned RESbv_maxGiven : 1; /* flags indicates maximum voltage is given */
|
||||||
int RESsenParmNo; /* parameter # for sensitivity use;
|
int RESsenParmNo; /* parameter # for sensitivity use;
|
||||||
* set equal to 0 if not a design parameter*/
|
* set equal to 0 if not a design parameter*/
|
||||||
|
int RESdangling; /* topology reduction: 0 = normal device;
|
||||||
|
bit0 => pos node dangling, bit1 => neg node dangling. A dangling
|
||||||
|
resistor is removed from the system and its floating node(s)
|
||||||
|
pinned with a unit diagonal. Set in CKTtopologyReduce(). */
|
||||||
|
|
||||||
#ifndef NONOISE
|
#ifndef NONOISE
|
||||||
double RESnVar[NSTATVARS][RESNSRCS];
|
double RESnVar[NSTATVARS][RESNSRCS];
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,16 @@ RESload(GENmodel *inModel, CKTcircuit *ckt)
|
||||||
for (here = RESinstances(model); here != NULL ;
|
for (here = RESinstances(model); here != NULL ;
|
||||||
here = RESnextInstance(here)) {
|
here = RESnextInstance(here)) {
|
||||||
|
|
||||||
|
if (here->RESdangling) {
|
||||||
|
/* Topology reduction: dangling (degree-1) resistor.
|
||||||
|
* Remove it from the system, pin the floating node(s) with a
|
||||||
|
* unit diagonal (set in CKTtopologyReduce()). */
|
||||||
|
if (here->RESdangling & 1) *(here->RESposPosPtr) += 1.0;
|
||||||
|
if (here->RESdangling & 2) *(here->RESnegNegPtr) += 1.0;
|
||||||
|
here->REScurrent = 0.0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
here->REScurrent = (*(ckt->CKTrhsOld+here->RESposNode) -
|
here->REScurrent = (*(ckt->CKTrhsOld+here->RESposNode) -
|
||||||
*(ckt->CKTrhsOld+here->RESnegNode)) * here->RESconduct;
|
*(ckt->CKTrhsOld+here->RESnegNode)) * here->RESconduct;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue