From 5970997cc5a3f13675c072f13a474f4381598c5f Mon Sep 17 00:00:00 2001 From: Meisam Date: Tue, 28 Jul 2026 17:25:26 +0200 Subject: [PATCH] pa-141 --- src/frontend/Makefile.am | 2 ++ src/frontend/com_commands.h | 1 + src/frontend/commands.c | 4 +++ src/include/ngspice/cktdefs.h | 1 + src/spicelib/analysis/dcpss.c | 68 +++++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/src/frontend/Makefile.am b/src/frontend/Makefile.am index 46a47f5f6..ea12a0ef0 100644 --- a/src/frontend/Makefile.am +++ b/src/frontend/Makefile.am @@ -57,6 +57,8 @@ libfte_la_SOURCES = \ com_qpss.h \ com_qpac.c \ com_qpac.h \ + com_qpxf.c \ + com_qpxf.h \ com_qpnoise.c \ com_qpnoise.h \ com_hbosc.c \ diff --git a/src/frontend/com_commands.h b/src/frontend/com_commands.h index bb0f3cfb3..fc6b6b006 100644 --- a/src/frontend/com_commands.h +++ b/src/frontend/com_commands.h @@ -9,6 +9,7 @@ void com_alterparam(wordlist *wl); void com_optimize(wordlist *wl); /* Enhancement-130 */ void com_qpss(wordlist *wl); /* Enhancement-133 */ void com_qpac(wordlist *wl); /* Enhancement-137 */ +void com_qpxf(wordlist *wl); /* Enhancement-141 */ void com_qpnoise(wordlist *wl); /* Enhancement-138 */ void com_hbosc(wordlist *wl); /* Enhancement-140 */ void com_phasenoise(wordlist *wl); /* Enhancement-140 */ diff --git a/src/frontend/commands.c b/src/frontend/commands.c index 565fa7892..7529960e7 100644 --- a/src/frontend/commands.c +++ b/src/frontend/commands.c @@ -440,6 +440,10 @@ struct comm spcp_coms[] = { { 040, 040, 040, 040 }, E_DEFHMASK, 1, LOTS, NULL, "f_in : two-tone small-signal QPAC -- response at sidebands f_in+k1f1+k2f2 around the `qpss ... hb` operating point." }, + { "qpxf", com_qpxf, TRUE, FALSE, /* Enhancement-141 */ + { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, + NULL, + "output_node f_in : two-tone QPXF -- transfer from each input sideband f_in+k1f1+k2f2 to the output, around the `qpss ... hb` operating point." }, { "qpnoise", com_qpnoise, TRUE, FALSE, /* Enhancement-138 */ { 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS, NULL, diff --git a/src/include/ngspice/cktdefs.h b/src/include/ngspice/cktdefs.h index e865fcfec..023550e13 100644 --- a/src/include/ngspice/cktdefs.h +++ b/src/include/ngspice/cktdefs.h @@ -478,6 +478,7 @@ struct hbspectrum { 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 */ extern int QPACanalyze(CKTcircuit *, double f_in, int verbose); /* E-137 */ +extern int QPXFanalyze(CKTcircuit *, int outNode, double f_in, int verbose); /* E-141 */ extern int QPnoiseAnalyze(CKTcircuit *, int outNode, double f_in, int cyclo, int verbose); /* E-138 / -139 */ extern int HBOSCanalyze(CKTcircuit *, int oscNode, int K, int P, double f0seed, double ampseed, int maxiter, double tol, int verbose); /* E-140 */ extern int PhaseNoiseAnalyze(CKTcircuit *, double fstart, double fstop, int npts, int verbose); /* E-140 */ diff --git a/src/spicelib/analysis/dcpss.c b/src/spicelib/analysis/dcpss.c index 953177eab..1bbe3a995 100644 --- a/src/spicelib/analysis/dcpss.c +++ b/src/spicelib/analysis/dcpss.c @@ -2425,6 +2425,74 @@ QPnoiseAnalyze(CKTcircuit *ckt, int outNode, double f_in, int cyclo, int verbose } +/* ====================================================================== + * Enhancement-141: two-tone small-signal QPXF -- the quasi-periodic transfer + * function, the ADJOINT of QPAC (Enhancement-137). One adjoint solve of the + * 2-D conversion matrix, H^T Psi = e_{out,(0,0)}, gives the transimpedance + * Psi_{(k1,k2),j} from an injection at (node j, sideband (k1,k2)) to the + * output at sideband (0,0). Dotting each sideband block of Psi with the + * netlist AC-source pattern B0 gives the transfer from that input sideband to + * the output. By reciprocity the (0,0) transfer equals the QPAC response at + * the output node -- a cross-check that pins the adjoint solve (cf. PXF/PAC, + * Enhancement-125). Reuses qp_solve_adjoint (built for QPnoise, E-138). + * ====================================================================== */ +int +QPXFanalyze(CKTcircuit *ckt, int outNode, double f_in, int verbose) +{ + struct qp_harm *hd = qpss_hb_saved; + int N, Ntot, j, hi, numNames, error, k1, k2, ord; + IFuid *nameList = NULL; + double *Psr, *Psi; + + if (!hd) { + fprintf(stderr, "qpxf: no QPSS operating point -- run `qpss hb` first.\n"); + return E_NOTFOUND; + } + N = hd->N; Ntot = hd->Ntot; + if (outNode <= 0 || outNode > N) { fprintf(stderr, "qpxf: bad output node.\n"); return E_PARMVAL; } + (void) verbose; + Psr = TMALLOC(double, Ntot); Psi = TMALLOC(double, Ntot); + + if (qp_solve_adjoint(hd, f_in, outNode, Psr, Psi)) { + fprintf(stderr, "qpxf: singular conversion matrix.\n"); + FREE(Psr); FREE(Psi); return E_SINGULAR; + } + + error = CKTnames(ckt, &numNames, &nameList); + fprintf(stdout, + "\nQPXF: two-tone transfer function to node %s (f_in = %g Hz, f1 = %g Hz, f2 = %g Hz)\n" + " (k1,k2) input sideband f_in+k1f1+k2f2 [Hz] |H| phase [deg]\n", + (!error && (outNode-1) < numNames) ? (const char *) nameList[outNode-1] : "?", + f_in, hd->f1, hd->f2); + for (ord = 0; ord <= hd->K1 + hd->K2; ord++) + for (k1 = -hd->K1; k1 <= hd->K1; k1++) + for (k2 = -hd->K2; k2 <= hd->K2; k2++) { + double fsb, hr = 0.0, hii = 0.0; + if (abs(k1) + abs(k2) != ord) continue; + hi = (k1 + hd->K1) * (2*hd->K2 + 1) + (k2 + hd->K2); + /* transfer from input at this sideband to the output: Psi_block . B0 + * (AC-source pattern), or Psi at the injection node if no AC source */ + if (hd->has_src) { + for (j = 0; j < N; j++) { + double pr = Psr[(size_t)hi*(size_t)N + (size_t)j]; + double pi = Psi[(size_t)hi*(size_t)N + (size_t)j]; + hr += pr*hd->B0r[j] - pi*hd->B0i[j]; + hii += pr*hd->B0i[j] + pi*hd->B0r[j]; + } + } else { + hr = Psr[(size_t)hi*(size_t)N + 0]; + hii = Psi[(size_t)hi*(size_t)N + 0]; + } + fsb = f_in + k1*hd->f1 + k2*hd->f2; + fprintf(stdout, " (%2d,%2d) %18.6e %14.6e %10.3f\n", + k1, k2, fsb, hypot(hr, hii), atan2(hii, hr) * 180.0/M_PI); + } + if (nameList) tfree(nameList); + FREE(Psr); FREE(Psi); + return OK; +} + + /* ====================================================================== * Enhancement-140: oscillator phase noise. *