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
5e27a8f6de
commit
aac1952ebe
|
|
@ -13,8 +13,14 @@ Author: 1985 Thomas L. Quarles
|
|||
#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"
|
||||
|
|
@ -30,6 +36,131 @@ Author: 1985 Thomas L. Quarles
|
|||
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
|
||||
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
|
||||
/* 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 */
|
||||
int CAPsenParmNo; /* parameter # for sensitivity use;
|
||||
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
|
||||
BindElement *CAPposPosBinding ;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,19 @@ CAPload(GENmodel *inModel, CKTcircuit *ckt)
|
|||
*(ckt->CKTrhsOld+here->CAPnegNode) ;
|
||||
}
|
||||
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
|
||||
if(ckt->CKTmode & MODEINITPRED) {
|
||||
*(ckt->CKTstate0+here->CAPqcap) =
|
||||
|
|
@ -71,10 +84,20 @@ CAPload(GENmodel *inModel, CKTcircuit *ckt)
|
|||
*(ckt->CKTstate1+here->CAPccap) =
|
||||
*(ckt->CKTstate0+here->CAPccap);
|
||||
}
|
||||
*(here->CAPposPosPtr) += m * geq;
|
||||
*(here->CAPnegNegPtr) += m * geq;
|
||||
*(here->CAPposNegPtr) -= m * geq;
|
||||
*(here->CAPnegPosPtr) -= m * geq;
|
||||
/* 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
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ typedef struct sRESinstance {
|
|||
unsigned RESbv_maxGiven : 1; /* flags indicates maximum voltage is given */
|
||||
int RESsenParmNo; /* parameter # for sensitivity use;
|
||||
* 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
|
||||
double RESnVar[NSTATVARS][RESNSRCS];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,16 @@ RESload(GENmodel *inModel, CKTcircuit *ckt)
|
|||
for (here = RESinstances(model); here != NULL ;
|
||||
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) -
|
||||
*(ckt->CKTrhsOld+here->RESnegNode)) * here->RESconduct;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue