This commit is contained in:
Meisam 2026-07-28 17:08:00 +02:00 committed by Holger Vogt
parent beaaed8cef
commit 9ac0fda90c
4 changed files with 536 additions and 2 deletions

View File

@ -18,16 +18,25 @@ no resampling or FFT-bin rounding). Each product is labelled by its 2-D harmonic
index (k1, k2), the defining QPSS output. index (k1, k2), the defining QPSS output.
Independent of the linear solver (it drives an ordinary transient). Independent of the linear solver (it drives an ordinary transient).
Enhancement-136: `qpss <expr> <f1> <f2> hb [K1] [K2]` selects a frequency-domain
two-tone Harmonic Balance engine instead of the transient path -- the TRUE
quasi-periodic steady state, which works for INCOMMENSURATE tones (no common beat
period) and retains its operating point for the small-signal `qpac` (E-137). The
engine (QPSShb, spicelib/analysis/dcpss.c) samples the devices on a 2-D phase grid,
2-D DFTs to the conversion matrix, and Newton-solves in the frequency domain.
**********/ **********/
#include "ngspice/ngspice.h" #include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h" #include "ngspice/cpdefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/ftedefs.h" #include "ngspice/ftedefs.h"
#include "ngspice/dvec.h" #include "ngspice/dvec.h"
#include "ngspice/wordlist.h" #include "ngspice/wordlist.h"
#include "ngspice/fteext.h" #include "ngspice/fteext.h"
#include "ngspice/cpextern.h" #include "ngspice/cpextern.h"
#include "circuits.h"
#include "com_qpss.h" #include "com_qpss.h"
/* Run one command synchronously through the command table (see com_optimize.c): /* Run one command synchronously through the command table (see com_optimize.c):
@ -102,6 +111,52 @@ com_qpss(wordlist *wl)
expr = wl->wl_word; expr = wl->wl_word;
f1 = qpssnum(wl->wl_next->wl_word); f1 = qpssnum(wl->wl_next->wl_word);
f2 = qpssnum(wl->wl_next->wl_next->wl_word); f2 = qpssnum(wl->wl_next->wl_next->wl_word);
/* Enhancement-136: `qpss <expr> <f1> <f2> hb [K1] [K2]` selects the
* frequency-domain two-tone Harmonic Balance engine -- the true,
* incommensurate-capable quasi-periodic steady state, which also retains
* the operating point for `qpac` -- instead of the E-133 transient path.
* The output is a per-node two-tone spectrum, so `expr` is not used here. */
{
wordlist *w;
for (w = wl->wl_next->wl_next->wl_next; w; w = w->wl_next)
if (strcasecmp(w->wl_word, "hb") == 0) {
CKTcircuit *ckt = ft_curckt->ci_ckt;
int K1 = 3, K2 = 3, verbose, err;
if (f1 <= 0.0 || f2 <= 0.0 || f1 == f2) {
fprintf(cp_err, "Error: qpss hb: need two distinct positive tone "
"frequencies.\n");
return;
}
if (w->wl_next) {
K1 = (int) qpssnum(w->wl_next->wl_word);
K2 = w->wl_next->wl_next
? (int) qpssnum(w->wl_next->wl_next->wl_word) : K1;
}
if (K1 < 1) K1 = 1;
if (K2 < 1) K2 = 1;
#ifdef KLU
if (ft_curckt->ci_defTask &&
(ckt->CKTmatrix == NULL || SMPmatSize(ckt->CKTmatrix) <= 0))
ckt->CKTkluMODE = ft_curckt->ci_defTask->TSKkluMODE;
#endif
if (ckt->CKTmatrix == NULL || SMPmatSize(ckt->CKTmatrix) <= 0) {
if ((err = CKTsetup(ckt)) != OK || (err = CKTtemp(ckt)) != OK) {
fprintf(cp_err, "Error: qpss hb: circuit setup failed.\n");
return;
}
}
verbose = cp_getvar("qpss_verbose", CP_BOOL, NULL, 0);
ft_curckt->ci_curTask = ft_curckt->ci_defTask;
ckt->CKTcurJob = ft_curckt->ci_defTask ? ft_curckt->ci_defTask->jobs : NULL;
err = QPSShb(ckt, f1, f2, K1, K2, 0, 0, 60, 1e-10, verbose ? 1 : 0);
if (err != OK)
fprintf(cp_err, "qpss hb: harmonic balance did not complete "
"(error %d).\n", err);
return;
}
}
if (wl->wl_next->wl_next->wl_next) { if (wl->wl_next->wl_next->wl_next) {
periods = (int) qpssnum(wl->wl_next->wl_next->wl_next->wl_word); periods = (int) qpssnum(wl->wl_next->wl_next->wl_next->wl_word);
if (wl->wl_next->wl_next->wl_next->wl_next) if (wl->wl_next->wl_next->wl_next->wl_next)

View File

@ -432,10 +432,10 @@ struct comm spcp_coms[] = {
{ 040, 040, 040, 040 }, E_DEFHMASK, 1, LOTS, { 040, 040, 040, 040 }, E_DEFHMASK, 1, LOTS,
NULL, NULL,
"-param name init lo hi ... -analysis <cmd> -minimize <expr> [-maxiter N] [-tol T] [-verbose] : Nelder-Mead parameter optimizer." }, "-param name init lo hi ... -analysis <cmd> -minimize <expr> [-maxiter N] [-tol T] [-verbose] : Nelder-Mead parameter optimizer." },
{ "qpss", com_qpss, TRUE, FALSE, /* Enhancement-133 */ { "qpss", com_qpss, TRUE, FALSE, /* Enhancement-133 / -136 */
{ 040, 040, 040, 040 }, E_DEFHMASK, 3, LOTS, { 040, 040, 040, 040 }, E_DEFHMASK, 3, LOTS,
NULL, NULL,
"expr f1 f2 [periods] [maxorder] : two-tone quasi-periodic steady-state spectrum (intermodulation)." }, "expr f1 f2 [periods] [maxorder] | expr f1 f2 hb [K1] [K2] : two-tone quasi-periodic steady state (transient / frequency-domain HB, incommensurate-capable)." },
{ "hb", com_hb, TRUE, FALSE, /* Enhancement-134 */ { "hb", com_hb, TRUE, FALSE, /* Enhancement-134 */
{ 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS,
NULL, NULL,

View File

@ -476,6 +476,7 @@ struct hbspectrum {
double *Vr, *Vi; /* [(2K+1)*N] two-sided Fourier coefficients, index (k+K)*N+i */ double *Vr, *Vi; /* [(2K+1)*N] two-sided Fourier coefficients, index (k+K)*N+i */
}; };
extern int HBanalyze(CKTcircuit *, double f0, int K, int P, int maxiter, double tol, int verbose, struct hbspectrum *out); /* E-134; E-209 out */ extern int HBanalyze(CKTcircuit *, double f0, int K, int P, int maxiter, double tol, int verbose, struct hbspectrum *out); /* E-134; E-209 out */
extern int QPSShb(CKTcircuit *, double f1, double f2, int K1, int K2, int P1, int P2, int maxiter, double tol, int verbose); /* E-136 */
#endif #endif

View File

@ -1642,6 +1642,484 @@ HBanalyze(CKTcircuit *ckt, double f0, int K, int Pin, int maxiter, double tol, i
} }
/* ======================================================================
* Enhancement-136: frequency-domain two-tone Harmonic Balance -- the TRUE
* (incommensurate-capable) quasi-periodic steady state, `qpss ... hb`.
*
* Each node voltage is a 2-D Fourier series
* v(t) = sum_{k1=-K1..K1, k2=-K2..K2} V_{k1,k2} e^{j(k1 w1 + k2 w2) t}.
* Devices are sampled on a 2-D PHASE grid (theta1,theta2) -- set the node
* voltages, load, read G/C -- which is time-independent, so INCOMMENSURATE
* tones just work (no common period); a 2-D DFT gives G_{d1,d2}, C_{d1,d2}.
* The source spectrum is captured by an almost-periodic Fourier transform
* (APFT): sample the v=0 source RHS at Nh real times and invert the
* Vandermonde. The 2-D conversion matrix H_{(n),(m)} = G_{n-m}+j*w_m*C_{n-m}
* is the direct analogue of pac_build_matrix; the Newton (with E-135 source
* stepping) reuses pss_csolve. The converged operating point + conversion
* data are retained in qpss_hb_saved for `qpac` (Enhancement-137).
* ====================================================================== */
struct qp_harm {
int N, K1, K2, Nh, Ntot, nnz;
int *rr, *cc; /* [nnz] 1-based Jacobian nonzero row/col */
int *h1, *h2; /* [Nh] (k1,k2) of each harmonic index */
int D2c, Dsz; /* diff-spectrum stride (4K2+1), size (4K1+1)(4K2+1) */
double *Gmr, *Gmi, *Cmr, *Cmi; /* [nnz*Dsz] 2-D difference spectra */
double f1, f2;
double *Vr, *Vi; /* [Ntot] retained operating point (for qpac); else NULL */
};
struct qp_harm *qpss_hb_saved = NULL; /* retained QPSS op-point for qpac (E-137) */
static int qp_didx(const struct qp_harm *h, int d1, int d2)
{ return (d1 + 2*h->K1) * h->D2c + (d2 + 2*h->K2); }
/* free the arrays a qp_harm owns (not the struct itself) */
void qp_free(struct qp_harm *hd)
{
if (!hd) return;
FREE(hd->rr); FREE(hd->cc); FREE(hd->h1); FREE(hd->h2);
FREE(hd->Gmr); FREE(hd->Gmi); FREE(hd->Cmr); FREE(hd->Cmi);
FREE(hd->Vr); FREE(hd->Vi);
}
/* assemble the dense Ntot x Ntot 2-D conversion matrix at input freq f_in */
void qp_build_matrix(struct qp_harm *hd, double f_in, double *Ar, double *Ai)
{
int Nh = hd->Nh, N = hd->N, Ntot = hd->Ntot, nnz = hd->nnz, ni, mi, e;
memset(Ar, 0, (size_t)Ntot * (size_t)Ntot * sizeof(double));
memset(Ai, 0, (size_t)Ntot * (size_t)Ntot * sizeof(double));
for (ni = 0; ni < Nh; ni++)
for (mi = 0; mi < Nh; mi++) {
int d1 = hd->h1[ni] - hd->h1[mi], d2 = hd->h2[ni] - hd->h2[mi];
double omega = 2.0 * M_PI * (f_in + hd->h1[mi]*hd->f1 + hd->h2[mi]*hd->f2);
int di = qp_didx(hd, d1, d2);
for (e = 0; e < nnz; e++) {
size_t hi = (size_t)e * (size_t)hd->Dsz + (size_t)di;
double gr = hd->Gmr[hi], gi = hd->Gmi[hi];
double cr = hd->Cmr[hi], ci = hd->Cmi[hi];
size_t row = (size_t)ni * (size_t)N + (size_t)(hd->rr[e] - 1);
size_t col = (size_t)mi * (size_t)N + (size_t)(hd->cc[e] - 1);
Ar[row * (size_t)Ntot + col] += gr - omega * ci;
Ai[row * (size_t)Ntot + col] += gi + omega * cr;
}
}
}
/* Sample the device Jacobian G(theta1,theta2), C(theta1,theta2) and resistive
* current I_R on the P1xP2 phase grid at prescribed node voltages, and 2-D DFT
* to the difference spectra + I_R harmonics. Fills hd; returns 0/1. Mirrors
* hb_extract (E-134) but on the 2-D phase grid. */
static int
qp_extract(CKTcircuit *ckt, const double *vsamp, int N, int P1, int P2,
int K1, int K2, struct qp_harm *hd, double *IRr, double *IRi)
{
int P = P1 * P2, i, r, c, e, s, s1, s2, nnz, d1, d2, hi;
int *rr, *cc;
double *Gt, *Ct, *IRt, *bsave;
memset(hd, 0, sizeof(*hd));
if (P <= 0 || N <= 0 || K1 < 1 || K2 < 1) return 1;
bsave = TMALLOC(double, N);
#ifdef KLU
if (ckt->CKTmatrix->CKTkluMODE) {
if (!ckt->CKTmatrix->SMPkluMatrix->KLUmatrixIsComplex) {
for (i = 0; i < DEVmaxnum; i++)
if (DEVices[i] && DEVices[i]->DEVbindCSCComplex && ckt->CKThead[i])
DEVices[i]->DEVbindCSCComplex(ckt->CKThead[i], ckt);
ckt->CKTmatrix->SMPkluMatrix->KLUmatrixIsComplex = KLUMatrixComplex;
}
} else
#endif
spSetComplex(ckt->CKTmatrix->SPmatrix);
/* establish structure at sample 0's bias */
for (i = 1; i <= N; i++) ckt->CKTrhsOld[i] = vsamp[i - 1];
ckt->CKTrhsOld[0] = 0.0;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEDCOP | MODEINITSMSIG;
CKTload(ckt);
ckt->CKTomega = 1.0;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEAC;
CKTacLoad(ckt);
nnz = 0;
for (r = 1; r <= N; r++) for (c = 1; c <= N; c++)
if (SMPfindElt(ckt->CKTmatrix, r, c, 0)) nnz++;
if (nnz <= 0) { FREE(bsave); return 1; }
rr = TMALLOC(int, nnz); cc = TMALLOC(int, nnz);
e = 0;
for (r = 1; r <= N; r++) for (c = 1; c <= N; c++)
if (SMPfindElt(ckt->CKTmatrix, r, c, 0)) { rr[e] = r; cc[e] = c; e++; }
Gt = TMALLOC(double, (size_t)nnz * (size_t)P);
Ct = TMALLOC(double, (size_t)nnz * (size_t)P);
IRt = TMALLOC(double, (size_t)N * (size_t)P);
for (s = 0; s < P; s++) {
double *Gv;
for (i = 1; i <= N; i++)
ckt->CKTrhsOld[i] = vsamp[(size_t)s * (size_t)N + (size_t)(i - 1)];
ckt->CKTrhsOld[0] = 0.0;
for (i = 0; i <= N; i++) ckt->CKTrhs[i] = 0.0;
/* settle limited junctions (E-134): repeated MODEINITFLOAT loads */
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEDCOP | MODEINITFLOAT;
{ int inner;
for (inner = 0; inner < 100; inner++) {
double bnorm = 0.0, dnorm = 0.0;
for (i = 0; i <= N; i++) ckt->CKTrhs[i] = 0.0;
CKTload(ckt);
for (i = 1; i <= N; i++) {
double db = ckt->CKTrhs[i] - bsave[i - 1];
dnorm += db * db; bnorm += ckt->CKTrhs[i] * ckt->CKTrhs[i];
bsave[i - 1] = ckt->CKTrhs[i];
}
if (inner > 0 && sqrt(dnorm) <= 1e-12 * (sqrt(bnorm) + 1e-30)) break;
} }
for (i = 0; i <= N; i++) ckt->CKTrhs[i] = 0.0;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEDCOP | MODEINITSMSIG;
CKTload(ckt);
ckt->CKTomega = 1.0;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEAC;
CKTacLoad(ckt);
Gv = IRt + (size_t)s * (size_t)N;
for (i = 0; i < N; i++) Gv[i] = 0.0;
for (e = 0; e < nnz; e++) {
double *el = (double *) SMPfindElt(ckt->CKTmatrix, rr[e], cc[e], 0);
double g = el ? el[0] : 0.0;
Gt[(size_t)e * (size_t)P + (size_t)s] = g;
Ct[(size_t)e * (size_t)P + (size_t)s] = el ? el[1] : 0.0;
Gv[rr[e] - 1] += g * vsamp[(size_t)s * (size_t)N + (size_t)(cc[e] - 1)];
}
for (i = 1; i <= N; i++) Gv[i - 1] -= bsave[i - 1]; /* I_R = G*v - b */
}
hd->N = N; hd->K1 = K1; hd->K2 = K2; hd->Nh = (2*K1+1) * (2*K2+1);
hd->Ntot = hd->Nh * N; hd->nnz = nnz; hd->rr = rr; hd->cc = cc;
hd->D2c = 4*K2 + 1; hd->Dsz = (4*K1+1) * (4*K2+1);
hd->h1 = TMALLOC(int, hd->Nh); hd->h2 = TMALLOC(int, hd->Nh);
{ int k1, k2; hi = 0;
for (k1 = -K1; k1 <= K1; k1++) for (k2 = -K2; k2 <= K2; k2++) {
hd->h1[hi] = k1; hd->h2[hi] = k2; hi++; } }
hd->Gmr = TMALLOC(double, (size_t)nnz * (size_t)hd->Dsz);
hd->Gmi = TMALLOC(double, (size_t)nnz * (size_t)hd->Dsz);
hd->Cmr = TMALLOC(double, (size_t)nnz * (size_t)hd->Dsz);
hd->Cmi = TMALLOC(double, (size_t)nnz * (size_t)hd->Dsz);
/* 2-D DFT of G(theta1,theta2), C(theta1,theta2) -> difference spectra */
for (e = 0; e < nnz; e++)
for (d1 = -2*K1; d1 <= 2*K1; d1++)
for (d2 = -2*K2; d2 <= 2*K2; d2++) {
double gr = 0, gi = 0, cr = 0, ci = 0;
for (s1 = 0; s1 < P1; s1++) for (s2 = 0; s2 < P2; s2++) {
int ss = s1 * P2 + s2;
double ang = 2.0*M_PI*((double)d1*s1/P1 + (double)d2*s2/P2);
double cs = cos(ang), sn = sin(ang);
double gv = Gt[(size_t)e * (size_t)P + (size_t)ss];
double cv = Ct[(size_t)e * (size_t)P + (size_t)ss];
gr += gv*cs; gi -= gv*sn; cr += cv*cs; ci -= cv*sn;
}
{ size_t idx = (size_t)e * (size_t)hd->Dsz + (size_t)qp_didx(hd, d1, d2);
hd->Gmr[idx] = gr/P; hd->Gmi[idx] = gi/P;
hd->Cmr[idx] = cr/P; hd->Cmi[idx] = ci/P; }
}
/* I_R harmonics over the full Nh set (row hi*N + node) */
for (r = 0; r < N; r++)
for (hi = 0; hi < hd->Nh; hi++) {
double xr = 0, xi = 0; int k1 = hd->h1[hi], k2 = hd->h2[hi];
for (s1 = 0; s1 < P1; s1++) for (s2 = 0; s2 < P2; s2++) {
int ss = s1 * P2 + s2;
double ang = 2.0*M_PI*((double)k1*s1/P1 + (double)k2*s2/P2);
double x = IRt[(size_t)ss * (size_t)N + (size_t)r];
xr += x*cos(ang); xi -= x*sin(ang);
}
IRr[(size_t)hi * (size_t)N + (size_t)r] = xr/P;
IRi[(size_t)hi * (size_t)N + (size_t)r] = xi/P;
}
FREE(Gt); FREE(Ct); FREE(IRt); FREE(bsave);
return 0;
}
/* reconstruct v(theta1,theta2) samples [P1*P2 * N] from the 2-D spectrum V */
static void
qp_synth(const double *Vr, const double *Vi, int N, int K1, int K2,
int P1, int P2, double *vsamp)
{
int s1, s2, i, k1, k2, hi;
for (s1 = 0; s1 < P1; s1++)
for (s2 = 0; s2 < P2; s2++) {
int s = s1 * P2 + s2;
for (i = 0; i < N; i++) {
double v = 0.0;
hi = 0;
for (k1 = -K1; k1 <= K1; k1++)
for (k2 = -K2; k2 <= K2; k2++) {
double ang = 2.0*M_PI*((double)k1*s1/P1 + (double)k2*s2/P2);
v += Vr[(size_t)hi*(size_t)N + (size_t)i] * cos(ang)
- Vi[(size_t)hi*(size_t)N + (size_t)i] * sin(ang);
hi++;
}
vsamp[(size_t)s * (size_t)N + (size_t)i] = v;
}
}
}
int
QPSShb(CKTcircuit *ckt, double f1, double f2, int K1, int K2, int P1, int P2,
int maxiter, double tol, int verbose)
{
int N = SMPmatSize(ckt->CKTmatrix);
int Nh = (2*K1+1) * (2*K2+1);
int Ntot = Nh * N, P, i, hi, rc = OK;
double *Vr, *Vi, *vsamp, *IRr, *IRi, *Isr, *Isi, *Fr, *Fi, *Jr, *Ji, *Kr, *Ki;
struct qp_harm hd;
if (N <= 0 || K1 < 1 || K2 < 1) { fprintf(stderr, "QPSS-HB: bad size.\n"); return E_PARMVAL; }
if (P1 <= 0) P1 = 4*K1 + 2;
if (P2 <= 0) P2 = 4*K2 + 2;
P = P1 * P2;
if (Ntot > 1600) {
fprintf(stderr, "QPSS-HB: system %d too large for the dense solver "
"(reduce K1/K2).\n", Ntot);
return E_PARMVAL;
}
/* APFT/conversion need all harmonic frequencies k1*f1+k2*f2 distinct */
{ int a, b, k1, k2, hh = 0; double fs = f1 + f2;
int *t1 = TMALLOC(int, Nh), *t2 = TMALLOC(int, Nh);
for (k1 = -K1; k1 <= K1; k1++) for (k2 = -K2; k2 <= K2; k2++) { t1[hh]=k1; t2[hh]=k2; hh++; }
for (a = 0; a < Nh; a++) for (b = a+1; b < Nh; b++)
if (fabs((t1[a]-t1[b])*f1 + (t2[a]-t2[b])*f2) < 1e-9 * fs) {
fprintf(stderr, "QPSS-HB: tones f1=%g f2=%g are too commensurate at order "
"K1=%d K2=%d -- harmonics (%d,%d) and (%d,%d) alias to the same "
"frequency. Reduce the order or use the transient qpss.\n",
f1, f2, K1, K2, t1[a], t2[a], t1[b], t2[b]);
FREE(t1); FREE(t2); return E_PARMVAL;
}
FREE(t1); FREE(t2);
}
Vr = TMALLOC(double, Ntot); Vi = TMALLOC(double, Ntot);
IRr = TMALLOC(double, Ntot); IRi = TMALLOC(double, Ntot);
Isr = TMALLOC(double, Ntot); Isi = TMALLOC(double, Ntot);
Fr = TMALLOC(double, Ntot); Fi = TMALLOC(double, Ntot);
Kr = TMALLOC(double, Ntot); Ki = TMALLOC(double, Ntot);
Jr = TMALLOC(double, (size_t)Ntot * (size_t)Ntot);
Ji = TMALLOC(double, (size_t)Ntot * (size_t)Ntot);
vsamp = TMALLOC(double, (size_t)N * (size_t)P);
/* --- source spectrum Is via an OVERSAMPLED least-squares almost-periodic
* Fourier transform: sample the v=0 source RHS at Nt >> Nh real times
* t_j = j*dt and solve the normal equations (Gamma^H Gamma) Is = Gamma^H b,
* with Gamma_{j,h} = exp(j 2pi (k1 f1 + k2 f2) t_j). Oversampling makes
* Gamma^H Gamma well conditioned (~ Nt*I by near-orthogonality of the
* harmonics over the equidistributed phases) where a *square* Vandermonde
* would be catastrophically ill-conditioned beyond a handful of harmonics. */
{
int j, k1, k2, a, b, Nt = 6*Nh < 96 ? 96 : 6*Nh;
double lammax = K1*f1 + K2*f2, dt = 1.0 / (2.1 * lammax);
double *lam = TMALLOC(double, Nh);
double *Gr = TMALLOC(double, (size_t)Nt*(size_t)Nh);
double *Gi = TMALLOC(double, (size_t)Nt*(size_t)Nh);
double *Mr = TMALLOC(double, (size_t)Nh*(size_t)Nh);
double *Mi = TMALLOC(double, (size_t)Nh*(size_t)Nh);
double *cr = TMALLOC(double, (size_t)Nh*(size_t)Nh);
double *ci = TMALLOC(double, (size_t)Nh*(size_t)Nh);
double *br = TMALLOC(double, Nh), *bi = TMALLOC(double, Nh);
double *bsr = TMALLOC(double, (size_t)Nt*(size_t)N);
a = 0;
for (k1 = -K1; k1 <= K1; k1++) for (k2 = -K2; k2 <= K2; k2++) { lam[a] = k1*f1 + k2*f2; a++; }
for (j = 0; j < Nt; j++) {
for (i = 0; i <= N; i++) { ckt->CKTrhsOld[i] = 0.0; ckt->CKTrhs[i] = 0.0; }
ckt->CKTtime = (double)j * dt;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODETRAN | MODEINITTRAN;
CKTload(ckt);
for (i = 0; i < N; i++) bsr[(size_t)j*(size_t)N + (size_t)i] = ckt->CKTrhs[i+1];
}
for (j = 0; j < Nt; j++)
for (a = 0; a < Nh; a++) {
double ang = 2.0*M_PI*lam[a]*(double)j*dt;
Gr[(size_t)j*(size_t)Nh + (size_t)a] = cos(ang);
Gi[(size_t)j*(size_t)Nh + (size_t)a] = sin(ang);
}
/* M = Gamma^H Gamma : M_{a,b} = sum_j conj(G_{j,a}) G_{j,b} */
for (a = 0; a < Nh; a++)
for (b = 0; b < Nh; b++) {
double sr = 0, si = 0;
for (j = 0; j < Nt; j++) {
double gar = Gr[(size_t)j*(size_t)Nh+(size_t)a], gai = Gi[(size_t)j*(size_t)Nh+(size_t)a];
double gbr = Gr[(size_t)j*(size_t)Nh+(size_t)b], gbi = Gi[(size_t)j*(size_t)Nh+(size_t)b];
sr += gar*gbr + gai*gbi; /* conj(ga)*gb */
si += gar*gbi - gai*gbr;
}
Mr[(size_t)a*(size_t)Nh+(size_t)b] = sr;
Mi[(size_t)a*(size_t)Nh+(size_t)b] = si;
}
for (i = 0; i < N; i++) {
for (a = 0; a < Nh; a++) { /* rhs = Gamma^H b (b real) */
double sr = 0, si = 0;
for (j = 0; j < Nt; j++) {
double bj = bsr[(size_t)j*(size_t)N + (size_t)i];
sr += Gr[(size_t)j*(size_t)Nh+(size_t)a] * bj;
si += -Gi[(size_t)j*(size_t)Nh+(size_t)a] * bj;
}
br[a] = sr; bi[a] = si;
}
memcpy(cr, Mr, (size_t)Nh*(size_t)Nh*sizeof(double));
memcpy(ci, Mi, (size_t)Nh*(size_t)Nh*sizeof(double));
if (pss_csolve(Nh, cr, ci, br, bi))
for (a = 0; a < Nh; a++) { br[a] = 0.0; bi[a] = 0.0; }
for (a = 0; a < Nh; a++) {
Isr[(size_t)a*(size_t)N + (size_t)i] = br[a];
Isi[(size_t)a*(size_t)N + (size_t)i] = bi[a];
}
}
FREE(lam); FREE(Gr); FREE(Gi); FREE(Mr); FREE(Mi); FREE(cr); FREE(ci); FREE(br); FREE(bi); FREE(bsr);
}
for (i = 0; i < Ntot; i++) { Vr[i] = 0.0; Vi[i] = 0.0; }
/* --- Newton with E-135 source-stepping continuation --- */
{
double lambda = 0.0, dlambda = 1.0, fnorm = 0.0;
double *Vsr = TMALLOC(double, Ntot), *Vsi = TMALLOC(double, Ntot);
int iter, nlevels = 0, nnewton = 0, hard_err = 0;
memcpy(Vsr, Vr, (size_t)Ntot*sizeof(double));
memcpy(Vsi, Vi, (size_t)Ntot*sizeof(double));
for (;;) {
double target = lambda + dlambda;
int conv = 0;
if (target > 1.0) target = 1.0;
for (iter = 0; iter < maxiter; iter++) {
qp_synth(Vr, Vi, N, K1, K2, P1, P2, vsamp);
if (qp_extract(ckt, vsamp, N, P1, P2, K1, K2, &hd, IRr, IRi)) {
fprintf(stderr, "QPSS-HB: device extraction failed.\n");
rc = E_PARMVAL; hard_err = 1; break;
}
hd.f1 = f1; hd.f2 = f2;
qp_build_matrix(&hd, 0.0, Jr, Ji);
/* reactive current I_C = (J - Jg)*V (jwC part of J on V) */
{
struct qp_harm hg = hd;
double *Jgr = TMALLOC(double, (size_t)Ntot*(size_t)Ntot);
double *Jgi = TMALLOC(double, (size_t)Ntot*(size_t)Ntot);
hg.Cmr = TMALLOC(double, (size_t)hd.nnz*(size_t)hd.Dsz);
hg.Cmi = TMALLOC(double, (size_t)hd.nnz*(size_t)hd.Dsz);
qp_build_matrix(&hg, 0.0, Jgr, Jgi);
FREE(hg.Cmr); FREE(hg.Cmi);
for (i = 0; i < Ntot; i++) {
double xr = 0, xi = 0; int kk;
for (kk = 0; kk < Ntot; kk++) {
double ar = Jr[(size_t)i*(size_t)Ntot+(size_t)kk] - Jgr[(size_t)i*(size_t)Ntot+(size_t)kk];
double ai = Ji[(size_t)i*(size_t)Ntot+(size_t)kk] - Jgi[(size_t)i*(size_t)Ntot+(size_t)kk];
xr += ar*Vr[kk] - ai*Vi[kk];
xi += ar*Vi[kk] + ai*Vr[kk];
}
Kr[i] = xr; Ki[i] = xi;
}
FREE(Jgr); FREE(Jgi);
}
qp_free(&hd);
fnorm = 0.0;
for (i = 0; i < Ntot; i++) {
Fr[i] = IRr[i] + Kr[i] - target*Isr[i];
Fi[i] = IRi[i] + Ki[i] - target*Isi[i];
fnorm += Fr[i]*Fr[i] + Fi[i]*Fi[i];
}
fnorm = sqrt(fnorm);
nnewton++;
if (verbose)
fprintf(stderr, "QPSS-HB lambda=%.4f iter %2d: |F| = %.6e\n", target, iter, fnorm);
if (isnan(fnorm) || fnorm > 1e300) break;
for (i = 0; i < Ntot; i++) { Fr[i] = -Fr[i]; Fi[i] = -Fi[i]; }
if (pss_csolve(Ntot, Jr, Ji, Fr, Fi)) break;
for (i = 0; i < Ntot; i++) { Vr[i] += Fr[i]; Vi[i] += Fi[i]; }
if (fnorm < tol) { conv = 1; break; }
}
if (hard_err) break;
if (conv) {
lambda = target; nlevels++;
memcpy(Vsr, Vr, (size_t)Ntot*sizeof(double));
memcpy(Vsi, Vi, (size_t)Ntot*sizeof(double));
if (lambda >= 1.0 - 1e-9) break;
dlambda *= 1.7;
if (lambda + dlambda > 1.0) dlambda = 1.0 - lambda;
} else {
memcpy(Vr, Vsr, (size_t)Ntot*sizeof(double));
memcpy(Vi, Vsi, (size_t)Ntot*sizeof(double));
dlambda *= 0.5;
if (dlambda < 1e-5) {
fprintf(stderr, "QPSS-HB: source stepping stalled at lambda=%.4g "
"(|F|=%.3e).\n", lambda, fnorm);
rc = E_ITERLIM; break;
}
}
}
if (rc == OK)
fprintf(stdout, "QPSS-HB: converged in %d iterations, %d continuation step%s "
"(|F| = %.3e).\n", nnewton, nlevels, nlevels == 1 ? "" : "s", fnorm);
FREE(Vsr); FREE(Vsi);
}
/* --- output the two-tone spectrum + retain the operating point for qpac --- */
if (rc == OK) {
int numNames, error, k1, k2, ord;
IFuid *nameList = NULL;
error = CKTnames(ckt, &numNames, &nameList);
fprintf(stdout,
"\nQPSS-HB: two-tone steady state (f1 = %g Hz, f2 = %g Hz, "
"K1 = %d, K2 = %d)\n"
" node (k1,k2) frequency [Hz] |V| phase [deg]\n",
f1, f2, K1, K2);
for (i = 0; i < N; i++) {
const char *nm = (!error && i < numNames) ? (const char *) nameList[i] : "?";
for (ord = 0; ord <= K1 + K2; ord++)
for (k1 = -K1; k1 <= K1; k1++)
for (k2 = -K2; k2 <= K2; k2++) {
double f, sc, vr, vi;
if (abs(k1) + abs(k2) != ord) continue;
f = k1*f1 + k2*f2;
if (f < 0.0) continue; /* report f >= 0 (conj pairs) */
hi = (k1 + K1) * (2*K2 + 1) + (k2 + K2);
sc = (f < 1e-9*(f1+f2)) ? 1.0 : 2.0;
vr = sc * Vr[(size_t)hi*(size_t)N + (size_t)i];
vi = sc * Vi[(size_t)hi*(size_t)N + (size_t)i];
fprintf(stdout, " %-8s (%2d,%2d) %16.6e %14.6e %10.3f\n",
nm, k1, k2, f, hypot(vr, vi),
(f < 1e-9*(f1+f2)) ? 0.0 : atan2(vi, vr) * 180.0/M_PI);
}
}
if (nameList) tfree(nameList);
/* retain: re-extract at the converged V for a clean conversion structure */
qp_synth(Vr, Vi, N, K1, K2, P1, P2, vsamp);
{
struct qp_harm *sv = TMALLOC(struct qp_harm, 1);
if (qp_extract(ckt, vsamp, N, P1, P2, K1, K2, sv, IRr, IRi) == 0) {
sv->f1 = f1; sv->f2 = f2;
sv->Vr = TMALLOC(double, Ntot); sv->Vi = TMALLOC(double, Ntot);
memcpy(sv->Vr, Vr, (size_t)Ntot*sizeof(double));
memcpy(sv->Vi, Vi, (size_t)Ntot*sizeof(double));
if (qpss_hb_saved) { qp_free(qpss_hb_saved); FREE(qpss_hb_saved); }
qpss_hb_saved = sv;
} else {
FREE(sv);
}
}
}
FREE(Vr); FREE(Vi); FREE(IRr); FREE(IRi); FREE(Isr); FREE(Isi);
FREE(Fr); FREE(Fi); FREE(Jr); FREE(Ji); FREE(Kr); FREE(Ki); FREE(vsamp);
return rc;
}
int int
DCpss(CKTcircuit *ckt, DCpss(CKTcircuit *ckt,
int restart) /* forced restart flag */ int restart) /* forced restart flag */