This commit is contained in:
Meisam 2026-07-28 17:16:27 +02:00 committed by Holger Vogt
parent 76aa690e13
commit a40fe00924
8 changed files with 248 additions and 0 deletions

View File

@ -57,6 +57,8 @@ libfte_la_SOURCES = \
com_qpss.h \
com_qpac.c \
com_qpac.h \
com_qpnoise.c \
com_qpnoise.h \
com_hb.c \
com_hb.h \
com_checkpoint.c \

View File

@ -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_qpnoise(wordlist *wl); /* Enhancement-138 */
void com_hb(wordlist *wl); /* Enhancement-134 */
void com_savestate(wordlist *wl); /* Enhancement-131 */
void com_loadstate(wordlist *wl); /* Enhancement-131 */

View File

@ -0,0 +1,82 @@
/**********
Enhancement-138: two-tone small-signal QPnoise (quasi-periodic noise) --
`qpnoise <output_node> <f_in>`.
Around the QPSS operating point retained by a prior `qpss <expr> <f1> <f2> hb`, folds
every device's noise through the ADJOINT of the 2-D conversion matrix over all sidebands
to the output at f_in -- the two-tone analogue of pnoise. A mixer/PA's device noise at
each sideband f_in + k1*f1 + k2*f2 is converted (folded) to the output. The engine is
QPnoiseAnalyze() (spicelib/analysis/dcpss.c); this command resolves the output node and
runs it.
**********/
#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h"
#include "ngspice/cktdefs.h"
#include "ngspice/ftedefs.h"
#include "ngspice/fteext.h"
#include "ngspice/wordlist.h"
#include "ngspice/cpextern.h"
#include "ngspice/ifsim.h"
#include "com_qpnoise.h"
static double qpnnum(const char *w)
{
char *s = (char *) w;
double v = 0.0;
if (ft_numparse(&s, FALSE, &v) < 0)
v = atof(w);
return v;
}
/* resolve a node name to its 1-based CKT node number via the name list, else 0 */
static int qpn_node(CKTcircuit *ckt, const char *name)
{
int numNames = 0, i, num = 0;
IFuid *nameList = NULL;
if (CKTnames(ckt, &numNames, &nameList) != OK || !nameList)
return 0;
for (i = 0; i < numNames; i++)
if (nameList[i] && strcmp((const char *) nameList[i], name) == 0) {
num = i + 1;
break;
}
tfree(nameList);
return num;
}
void
com_qpnoise(wordlist *wl)
{
CKTcircuit *ckt;
double f_in;
int outNode, verbose, err;
if (!ft_curckt || !ft_curckt->ci_ckt) {
fprintf(cp_err, "Error: qpnoise: there is no circuit loaded.\n");
return;
}
ckt = ft_curckt->ci_ckt;
if (!wl || !wl->wl_next) {
fprintf(cp_err, "Usage: qpnoise <output_node> <f_in> "
"(run `qpss <expr> <f1> <f2> hb` first)\n");
return;
}
outNode = qpn_node(ckt, wl->wl_word);
if (outNode <= 0) {
fprintf(cp_err, "Error: qpnoise: unknown output node '%s'.\n", wl->wl_word);
return;
}
f_in = qpnnum(wl->wl_next->wl_word);
if (f_in <= 0.0) {
fprintf(cp_err, "Error: qpnoise: need f_in > 0.\n");
return;
}
verbose = cp_getvar("qpnoise_verbose", CP_BOOL, NULL, 0);
err = QPnoiseAnalyze(ckt, outNode, f_in, verbose ? 1 : 0);
if (err != OK)
fprintf(cp_err, "qpnoise: quasi-periodic noise did not complete (error %d).\n", err);
}

View File

@ -0,0 +1,7 @@
#ifndef ngspice_COM_QPNOISE_H
#define ngspice_COM_QPNOISE_H
/* Enhancement-138: two-tone small-signal QPnoise (quasi-periodic noise). */
void com_qpnoise(wordlist *wl);
#endif

View File

@ -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." },
{ "qpnoise", com_qpnoise, TRUE, FALSE, /* Enhancement-138 */
{ 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS,
NULL,
"output_node f_in : two-tone QPnoise -- output/input noise density at f_in, folding device noise over all sidebands around the `qpss ... hb` operating point." },
{ "hb", com_hb, TRUE, FALSE, /* Enhancement-134 */
{ 040, 040, 040, 040 }, E_DEFHMASK, 2, LOTS,
NULL,

View File

@ -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 QPnoiseAnalyze(CKTcircuit *, int outNode, double f_in, int verbose); /* E-138 */
#endif

View File

@ -2210,6 +2210,155 @@ QPACanalyze(CKTcircuit *ckt, double f_in, int verbose)
}
/* ======================================================================
* Enhancement-138: two-tone small-signal QPnoise (quasi-periodic noise).
* Around the QPSS operating point retained by `qpss ... hb`, fold every
* device's noise through the ADJOINT of the 2-D conversion matrix over
* all sidebands to the output at f_in -- the two-tone analogue of pnoise
* (E-124). Each device's noise routine computes S*|dTransimp|^2 reading
* the transimpedance from CKTrhs/CKTirhs, so loading the sideband-(k1,k2)
* adjoint transfer into CKTrhs and summing over all harmonics folds the
* noise exactly (mixer/PA folded noise). With no pump the conversion
* matrix is block-diagonal, so only sideband (0,0) contributes and the
* result reduces to ordinary .noise.
* ====================================================================== */
/* Solve the ADJOINT 2-D conversion system H^T Psi = e_{out,(0,0)}. Psi then
* holds, for every (node j, harmonic hi), the transfer from a unit injection at
* (j,hi) to the output at sideband (0,0). Reuses qp_build_matrix (forward H) and
* transposes in place. Returns 0 on success, 1 if singular. */
static int
qp_solve_adjoint(struct qp_harm *hd, double f_in, int outNode, double *Psr, double *Psi)
{
int Ntot = hd->Ntot, N = hd->N, i, j, i00, rc;
double *Ar, *Ai;
Ar = TMALLOC(double, (size_t)Ntot * (size_t)Ntot);
Ai = TMALLOC(double, (size_t)Ntot * (size_t)Ntot);
qp_build_matrix(hd, f_in, Ar, Ai);
for (i = 0; i < Ntot; i++) /* transpose in place: H -> H^T */
for (j = i + 1; j < Ntot; j++) {
size_t ij = (size_t)i*(size_t)Ntot + (size_t)j;
size_t ji = (size_t)j*(size_t)Ntot + (size_t)i;
double t;
t = Ar[ij]; Ar[ij] = Ar[ji]; Ar[ji] = t;
t = Ai[ij]; Ai[ij] = Ai[ji]; Ai[ji] = t;
}
memset(Psr, 0, (size_t)Ntot * sizeof(double));
memset(Psi, 0, (size_t)Ntot * sizeof(double));
i00 = hd->K1 * (2*hd->K2 + 1) + hd->K2;
Psr[(size_t)i00 * (size_t)N + (size_t)(outNode - 1)] = 1.0;
rc = pss_csolve(Ntot, Ar, Ai, Psr, Psi);
FREE(Ar); FREE(Ai);
return rc;
}
int
QPnoiseAnalyze(CKTcircuit *ckt, int outNode, double f_in, int verbose)
{
struct qp_harm *hd = qpss_hb_saved;
int N, Nh, Ntot, i, j, hi, i00;
double *Psr, *Psi, *Xr, *Xi;
double onoise = 0.0, inoise, gain2 = 1.0;
NOISEAN nj;
Ndata data;
JOB *oldJob;
if (!hd) {
fprintf(stderr, "qpnoise: no QPSS operating point -- run `qpss <expr> <f1> <f2> hb` first.\n");
return E_NOTFOUND;
}
N = hd->N; Nh = hd->Nh; Ntot = hd->Ntot;
if (outNode <= 0 || outNode > N) {
fprintf(stderr, "qpnoise: bad output node.\n");
return E_PARMVAL;
}
i00 = hd->K1 * (2*hd->K2 + 1) + hd->K2;
/* bias the devices at the QPSS operating point (phase (0,0) sample: v = sum of
* all harmonic coefficients) so each noise PSD is at the periodic bias. */
for (j = 1; j <= N; j++) {
double v = 0.0;
for (hi = 0; hi < Nh; hi++) v += hd->Vr[(size_t)hi*(size_t)N + (size_t)(j-1)];
ckt->CKTrhsOld[j] = v;
}
ckt->CKTrhsOld[0] = 0.0;
ckt->CKTmode = (ckt->CKTmode & MODEUIC) | MODEDCOP | MODEINITSMSIG;
CKTload(ckt);
/* minimal NOISEAN context (device noise routines cast CKTcurJob to NOISEAN*) */
memset(&nj, 0, sizeof(nj));
nj.NstartFreq = f_in;
nj.NstopFreq = f_in;
nj.NnumSteps = 1;
nj.NstpType = 0;
nj.NStpsSm = 0;
nj.JOBname = "qpnoise";
memset(&data, 0, sizeof(data));
data.prtSummary = FALSE;
oldJob = ckt->CKTcurJob;
ckt->CKTcurJob = (JOB *) &nj;
for (i = 0; i < DEVmaxnum; i++)
if (DEVices[i] && DEVices[i]->DEVnoise && ckt->CKThead[i]) {
double dummy = 0.0;
DEVices[i]->DEVnoise(N_DENS, N_OPEN, ckt->CKThead[i], ckt, &data, &dummy);
}
Psr = TMALLOC(double, Ntot); Psi = TMALLOC(double, Ntot);
Xr = TMALLOC(double, Ntot); Xi = TMALLOC(double, Ntot);
/* adjoint transfer from every (node, harmonic) to the output at (0,0), then
* fold each device's noise density over all sidebands. */
data.freq = f_in; data.delFreq = 0.0; data.prtSummary = FALSE;
if (qp_solve_adjoint(hd, f_in, outNode, Psr, Psi) == 0) {
for (hi = 0; hi < Nh; hi++) {
double dens = 0.0;
size_t blk = (size_t)hi * (size_t)N;
for (j = 1; j <= N; j++) {
ckt->CKTrhs[j] = Psr[blk + (size_t)(j-1)];
ckt->CKTirhs[j] = Psi[blk + (size_t)(j-1)];
}
ckt->CKTrhs[0] = 0.0; ckt->CKTirhs[0] = 0.0;
for (i = 0; i < DEVmaxnum; i++)
if (DEVices[i] && DEVices[i]->DEVnoise && ckt->CKThead[i])
DEVices[i]->DEVnoise(N_DENS, N_CALC, ckt->CKThead[i], ckt, &data, &dens);
onoise += dens; /* sum device noise over sidebands */
}
}
/* input-referred: divide by the source->output conversion gain at (0,0) */
if (hd->has_src) {
double *Ar = TMALLOC(double, (size_t)Ntot*(size_t)Ntot);
double *Ai = TMALLOC(double, (size_t)Ntot*(size_t)Ntot);
qp_build_matrix(hd, f_in, Ar, Ai);
memset(Xr, 0, (size_t)Ntot*sizeof(double));
memset(Xi, 0, (size_t)Ntot*sizeof(double));
for (i = 0; i < N; i++) {
Xr[(size_t)i00*(size_t)N + (size_t)i] = hd->B0r[i];
Xi[(size_t)i00*(size_t)N + (size_t)i] = hd->B0i[i];
}
if (pss_csolve(Ntot, Ar, Ai, Xr, Xi) == 0) {
size_t oidx = (size_t)i00*(size_t)N + (size_t)(outNode-1);
gain2 = Xr[oidx]*Xr[oidx] + Xi[oidx]*Xi[oidx];
}
FREE(Ar); FREE(Ai);
}
inoise = onoise / MAX(gain2, N_MINGAIN);
ckt->CKTcurJob = oldJob;
(void) verbose;
fprintf(stdout,
"\nQPnoise: two-tone output noise at f_in = %g Hz (folding %d sidebands, "
"f1 = %g, f2 = %g)\n"
" onoise density = %.6e V^2/Hz (%.6e V/sqrt(Hz))\n"
" inoise density = %.6e (gain^2 = %.6e)\n",
f_in, Nh, hd->f1, hd->f2, onoise, sqrt(onoise), inoise, gain2);
FREE(Psr); FREE(Psi); FREE(Xr); FREE(Xi);
return OK;
}
int
DCpss(CKTcircuit *ckt,
int restart) /* forced restart flag */

View File

@ -896,6 +896,7 @@
<ClInclude Include="..\src\frontend\com_plot.h" />
<ClInclude Include="..\src\frontend\com_pyplot.h" />
<ClInclude Include="..\src\frontend\com_qpac.h" />
<ClInclude Include="..\src\frontend\com_qpnoise.h" />
<ClInclude Include="..\src\frontend\com_qpss.h" />
<ClInclude Include="..\src\frontend\com_rehash.h" />
<ClInclude Include="..\src\frontend\com_set.h" />
@ -1515,6 +1516,7 @@
<ClCompile Include="..\src\frontend\com_plot.c" />
<ClCompile Include="..\src\frontend\com_pyplot.c" />
<ClCompile Include="..\src\frontend\com_qpac.c" />
<ClCompile Include="..\src\frontend\com_qpnoise.c" />
<ClCompile Include="..\src\frontend\com_qpss.c" />
<ClCompile Include="..\src\frontend\com_rehash.c" />
<ClCompile Include="..\src\frontend\com_set.c" />