346 lines
10 KiB
C
346 lines
10 KiB
C
/**********
|
|
Copyright 1990 Regents of the University of California. All rights reserved.
|
|
Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group
|
|
**********/
|
|
|
|
/*
|
|
* Curve plotting routines and general (non-graphics) plotting things.
|
|
*/
|
|
|
|
#include "ngspice/ngspice.h"
|
|
#include "ngspice/cpdefs.h"
|
|
#include "ngspice/ftedefs.h"
|
|
#include "ngspice/dvec.h"
|
|
#include "ngspice/graph.h"
|
|
#include "graf.h"
|
|
#include "ngspice/ftedbgra.h"
|
|
|
|
#include "plotcurv.h"
|
|
|
|
|
|
static void plotinterval(struct dvec *v, double lo, double hi, register double *coeffs,
|
|
int degree, bool rotated);
|
|
|
|
|
|
/* Plot the vector v, with scale xs. If we are doing curve-fitting, then
|
|
* do some tricky stuff.
|
|
*/
|
|
|
|
void
|
|
ft_graf(struct dvec *v, struct dvec *xs, bool nostart)
|
|
{
|
|
int degree, gridsize, length;
|
|
register int i, j, l;
|
|
double *scratch, *result, *gridbuf, *mm;
|
|
register double *xdata, *ydata;
|
|
bool rot, increasing = FALSE;
|
|
double dx = 0.0, dy = 0.0, lx = 0.0, ly = 0.0;
|
|
int dir;
|
|
|
|
/* if already started, use saved degree */
|
|
if (nostart) {
|
|
degree = currentgraph->degree;
|
|
} else {
|
|
if (!cp_getvar("polydegree", CP_NUM, °ree, 0))
|
|
degree = 1;
|
|
currentgraph->degree = degree;
|
|
}
|
|
|
|
if (degree > v->v_length)
|
|
degree = v->v_length;
|
|
|
|
if (degree < 1) {
|
|
fprintf(cp_err, "Error: polydegree is %d, can't plot...\n",
|
|
degree);
|
|
return;
|
|
}
|
|
|
|
if (!cp_getvar("gridsize", CP_NUM, &gridsize, 0))
|
|
gridsize = 0;
|
|
|
|
if ((gridsize < 0) || (gridsize > 10000)) {
|
|
fprintf(cp_err, "Error: bad grid size %d\n", gridsize);
|
|
return;
|
|
}
|
|
|
|
if (gridsize && xs) {
|
|
if (isreal(xs)) {
|
|
increasing = (xs->v_realdata[0] < xs->v_realdata[1]);
|
|
for (i = 0; i < xs->v_length - 1; i++)
|
|
if (increasing != (xs->v_realdata[i] < xs->v_realdata[i + 1])) {
|
|
fprintf(cp_err,
|
|
"Warning: scale not monotonic, gridsize not relevant.\n");
|
|
gridsize = 0;
|
|
break;
|
|
}
|
|
} else {
|
|
increasing = (realpart(xs->v_compdata[0]) < realpart(xs->v_compdata[1]));
|
|
for (i = 0; i < xs->v_length - 1; i++)
|
|
if (increasing != (realpart(xs->v_compdata[i]) < realpart(xs->v_compdata[i + 1]))) {
|
|
fprintf(cp_err,
|
|
"Warning: scale not monotonic, gridsize not relevant.\n");
|
|
gridsize = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!nostart)
|
|
gr_start(v);
|
|
|
|
/* Do the one value case */
|
|
|
|
if (!xs) {
|
|
for (i = 0; i < v->v_length; i++) {
|
|
|
|
/* We should do the one - point case too!
|
|
* Important for pole-zero for example
|
|
*/
|
|
if (v->v_length == 1) {
|
|
j = 0;
|
|
} else {
|
|
j = i-1;
|
|
if (i == 0)
|
|
continue;
|
|
}
|
|
|
|
if (isreal(v)) {
|
|
/* This isn't good but we may as well do
|
|
* something useful.
|
|
*/
|
|
gr_point(v, v->v_realdata[i],
|
|
0.0, /* v->v_realdata[i], */
|
|
v->v_realdata[j],
|
|
0.0, /* v->v_realdata[j], */
|
|
(j == i ? 1 : i));
|
|
} else {
|
|
gr_point(v, realpart(v->v_compdata[i]),
|
|
imagpart(v->v_compdata[i]),
|
|
realpart(v->v_compdata[j]),
|
|
imagpart(v->v_compdata[j]), (j == i ? 1 : i));
|
|
}
|
|
}
|
|
gr_end(v);
|
|
return;
|
|
}
|
|
|
|
xs->v_flags |= VF_PERMANENT;
|
|
|
|
/* First check the simple case, where we don't have to do any
|
|
* interpolation.
|
|
*/
|
|
if ((degree == 1) && (gridsize == 0)) {
|
|
bool mono = (currentgraph->plottype == PLOT_MONOLIN);
|
|
dir = 0;
|
|
for (i = 0, j = v->v_length; i < j; i++) {
|
|
dx = isreal(xs) ? xs->v_realdata[i] :
|
|
realpart(xs->v_compdata[i]);
|
|
dy = isreal(v) ? v->v_realdata[i] :
|
|
realpart(v->v_compdata[i]);
|
|
if ((i == 0 || (dir > 0 ? lx > dx : dir < 0 ? lx < dx : 0)) &&
|
|
(mono || (xs->v_plot && xs->v_plot->pl_scale == xs)))
|
|
{
|
|
gr_point(v, dx, dy, lx, ly, 0);
|
|
} else {
|
|
gr_point(v, dx, dy, lx, ly, i);
|
|
if (!dir)
|
|
dir = lx > dx ? -1 : lx < dx ? 1 : 0;
|
|
}
|
|
lx = dx;
|
|
ly = dy;
|
|
}
|
|
if (v->v_length == 1)
|
|
gr_point(v, dx, dy, lx, ly, 1);
|
|
gr_end(v);
|
|
return;
|
|
}
|
|
|
|
if (gridsize < degree + 1)
|
|
gridsize = 0;
|
|
|
|
if (gridsize) {
|
|
/* This is done quite differently from what we do below... */
|
|
gridbuf = TMALLOC(double, gridsize);
|
|
result = TMALLOC(double, gridsize);
|
|
if (isreal(v)) {
|
|
ydata = v->v_realdata;
|
|
} else {
|
|
ydata = TMALLOC(double, v->v_length);
|
|
for (i = 0; i < v->v_length; i++)
|
|
ydata[i] = realpart(v->v_compdata[i]);
|
|
}
|
|
|
|
if (isreal(xs)) {
|
|
xdata = xs->v_realdata;
|
|
} else {
|
|
xdata = TMALLOC(double, xs->v_length);
|
|
for (i = 0; i < xs->v_length; i++)
|
|
xdata[i] = realpart(xs->v_compdata[i]);
|
|
}
|
|
|
|
mm = ft_minmax(xs, TRUE);
|
|
dx = (mm[1] - mm[0]) / gridsize;
|
|
if (increasing)
|
|
for (i = 0, dy = mm[0]; i < gridsize; i++, dy += dx)
|
|
gridbuf[i] = dy;
|
|
else
|
|
for (i = 0, dy = mm[1]; i < gridsize; i++, dy -= dx)
|
|
gridbuf[i] = dy;
|
|
if (!ft_interpolate(ydata, result, xdata, v->v_length, gridbuf,
|
|
gridsize, degree)) {
|
|
fprintf(cp_err, "Error: can't put %s on gridsize %d\n",
|
|
v->v_name, gridsize);
|
|
return;
|
|
}
|
|
/* Now this is a problem. There's no way that we can
|
|
* figure out where to put the tic marks to correspond with
|
|
* the actual data...
|
|
*/
|
|
for (i = 0; i < gridsize; i++)
|
|
gr_point(v, gridbuf[i], result[i],
|
|
gridbuf[i ? (i - 1) : i], result[i ? (i - 1) : i], -1);
|
|
gr_end(v);
|
|
tfree(gridbuf);
|
|
tfree(result);
|
|
if (!isreal(v))
|
|
tfree(ydata);
|
|
if (!isreal(xs))
|
|
tfree(xdata);
|
|
return;
|
|
}
|
|
|
|
/* We need to do curve fitting now. First get some scratch
|
|
* space
|
|
*/
|
|
scratch = TMALLOC(double, (degree + 1) * (degree + 2));
|
|
result = TMALLOC(double, degree + 1);
|
|
xdata = TMALLOC(double, degree + 1);
|
|
ydata = TMALLOC(double, degree + 1);
|
|
|
|
|
|
/* Plot the first degree segments... */
|
|
if (isreal(v))
|
|
memcpy(ydata, v->v_realdata, (size_t)(degree + 1) * sizeof(double));
|
|
else
|
|
for (i = 0; i <= degree; i++)
|
|
ydata[i] = realpart(v->v_compdata[i]);
|
|
|
|
if (isreal(xs))
|
|
memcpy(xdata, xs->v_realdata, (size_t)(degree + 1) * sizeof(double));
|
|
else
|
|
for (i = 0; i <= degree; i++)
|
|
xdata[i] = realpart(xs->v_compdata[i]);
|
|
|
|
rot = FALSE;
|
|
while (!ft_polyfit(xdata, ydata, result, degree, scratch)) {
|
|
/* Rotate the coordinate system 90 degrees and try again.
|
|
* If it doesn't work this time, bump the interpolation
|
|
* degree down by one...
|
|
*/
|
|
if (ft_polyfit(ydata, xdata, result, degree, scratch)) {
|
|
rot = TRUE;
|
|
break;
|
|
}
|
|
if (--degree == 0) {
|
|
fprintf(cp_err, "plotcurve: Internal Error: ack...\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Plot this part of the curve... */
|
|
for (i = 0; i < degree; i++)
|
|
if (rot)
|
|
plotinterval(v, ydata[i], ydata[i + 1], result, degree, TRUE);
|
|
else
|
|
plotinterval(v, xdata[i], xdata[i + 1], result, degree, FALSE);
|
|
|
|
/* Now plot the rest, piece by piece... l is the
|
|
* last element under consideration.
|
|
*/
|
|
length = v->v_length;
|
|
for (l = degree + 1; l < length; l++) {
|
|
|
|
/* Shift the old stuff by one and get another value. */
|
|
for (i = 0; i < degree; i++) {
|
|
xdata[i] = xdata[i + 1];
|
|
ydata[i] = ydata[i + 1];
|
|
}
|
|
|
|
if (isreal(v))
|
|
ydata[i] = v->v_realdata[l];
|
|
else
|
|
ydata[i] = realpart(v->v_compdata[l]);
|
|
|
|
if (isreal(xs))
|
|
xdata[i] = xs->v_realdata[l];
|
|
else
|
|
xdata[i] = realpart(xs->v_compdata[l]);
|
|
|
|
rot = FALSE;
|
|
while (!ft_polyfit(xdata, ydata, result, degree, scratch)) {
|
|
if (ft_polyfit(ydata, xdata, result, degree, scratch)) {
|
|
rot = TRUE;
|
|
break;
|
|
}
|
|
if (--degree == 0) {
|
|
fprintf(cp_err,
|
|
"plotcurve: Internal Error: ack...\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (rot)
|
|
plotinterval(v, ydata[degree - 1], ydata[degree],
|
|
result, degree, TRUE);
|
|
else
|
|
plotinterval(v, xdata[degree - 1], xdata[degree],
|
|
result, degree, FALSE);
|
|
}
|
|
|
|
tfree(scratch);
|
|
tfree(xdata);
|
|
tfree(ydata);
|
|
tfree(result);
|
|
|
|
gr_end(v);
|
|
}
|
|
|
|
|
|
#define GRANULARITY 10
|
|
|
|
static void
|
|
plotinterval(struct dvec *v, double lo, double hi, register double *coeffs, int degree, bool rotated)
|
|
{
|
|
double incr, dx, dy, lx, ly;
|
|
register int i;
|
|
int steps;
|
|
|
|
/*
|
|
fprintf(cp_err, "plotinterval(%s, %G, %G, [ ", v->v_name, lo, hi);
|
|
for (i = 0; i <= degree; i++)
|
|
fprintf(cp_err, "%G ", coeffs[i]);
|
|
fprintf(cp_err, "], %d, %s)\n\r", degree, rotated ? "TRUE" : "FALSE");
|
|
*/
|
|
|
|
/* This is a problem -- how do we know what granularity to use? If
|
|
* the guy cares about this he will use gridsize.
|
|
*/
|
|
if (!cp_getvar("polysteps", CP_NUM, &steps, 0))
|
|
steps = GRANULARITY;
|
|
|
|
incr = (hi - lo) / (double) (steps + 1);
|
|
dx = lo + incr;
|
|
lx = lo;
|
|
ly = ft_peval(lo, coeffs, degree);
|
|
for (i = 0; i <= steps; i++, dx += incr) {
|
|
dy = ft_peval(dx, coeffs, degree);
|
|
if (rotated)
|
|
gr_point(v, dy, dx, ly, lx, -1);
|
|
else
|
|
gr_point(v, dx, dy, lx, ly, -1);
|
|
lx = dx;
|
|
ly = dy;
|
|
/* fprintf(cp_err, "plot (%G, %G)\n\r", dx, dy); */
|
|
}
|
|
}
|