You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

194 lines
5.9 KiB

#include "ngspice/ngspice.h"
#include "ngspice/cpdefs.h"
#include "interpolate.h"
#include "polyeval.h"
#include "polyfit.h"
/* Returns the strchr of the last element that was calculated. oval is
* the value of the old scale at the end of the interval that is being
* interpolated from, and sign is 1 if the old scale was increasing,
* and -1 if it was decreasing. */
static int
putinterval(double *poly, int degree, double *nvec,
int last, double *nscale, int nlen, double oval, int sign)
{
int end, i;
/* See how far we have to go. */
for (end = last + 1; end < nlen; end++)
if (nscale[end] * sign > oval * sign)
break;
end--;
for (i = last + 1; i <= end; i++)
nvec[i] = ft_peval(nscale[i], poly, degree);
return (end);
}
/* Interpolate data from oscale to nscale. data is assumed to be olen long,
* ndata will be nlen long. Returns FALSE if the scales are too strange
* to deal with. Note that we are guaranteed that either both scales are
* increasing or both are decreasing.
*/
#define EDGE_FACTOR 1e-3
bool
ft_interpolate(double *data, double *ndata, double *oscale, int olen,
double *nscale, int nlen, int degree)
{
double *result, *scratch, *xdata, *ydata, diff;
int sign, lastone, i, l, middle, tdegree;
if ((olen < 2) || (nlen < 2)) {
fprintf(cp_err, "Error: lengths too small to interpolate.\n");
return (FALSE);
}
if ((degree < 1) || (degree > olen)) {
fprintf(cp_err, "Error: degree is %d, can't interpolate.\n",
degree);
return (FALSE);
}
for (i = 0; i < olen - 1; ++i) {
if (oscale[i + 1] < oscale[i]) {
sign = -1;
break;
} else if (oscale[i + 1] > oscale[i]) {
sign = 1;
break;
}
}
if (i >= olen) {
fprintf(cp_err, "Error: bad scale, can't interpolate.\n");
return FALSE;
}
scratch = TMALLOC(double, (degree + 1) * (degree + 2));
result = TMALLOC(double, degree + 1);
xdata = TMALLOC(double, degree + 1);
ydata = TMALLOC(double, degree + 1);
/* Initial load of the values to be analysed by ft_polyfit(),
* skipping irrelevant points and checking for and fudging vertical edges.
*/
i = l = 0;
middle = (degree + 1) / 2;
if (sign > 0) {
while (l < olen - degree && oscale[l + middle] < nscale[0])
++l;
} else {
while (l < olen - degree && oscale[l + middle] > nscale[0])
++l;
}
ydata[0] = data[l];
xdata[0] = oscale[l];
do {
if (oscale[l + 1] == oscale[l]) {
if (i == 0) {
ydata[0] = data[++l]; // Ignore first point.
} else {
/* Push the previous x value back, making edge a slope. */
diff = xdata[i] - xdata[i - 1];
xdata[i] -= sign * diff * EDGE_FACTOR;
}
}
xdata[++i] = oscale[++l];
ydata[i] = data[l];
} while (i < degree && l < olen - 1);
if (i < degree) {
fprintf(cp_err, "Error: too few points to calculate polynomial\n");
return FALSE;
}
i = 0;
tdegree = degree;
while (!ft_polyfit(xdata + i, ydata + i, result, tdegree, scratch)) {
/* If it doesn't work this time, bump the interpolation
* degree down by one.
*/
if (--tdegree == 0) {
fprintf(cp_err, "ft_interpolate: Internal Error.\n");
return (FALSE);
}
if (tdegree % 2)
++i; // Drop left point.
}
/* Add this part of the curve. What we do is evaluate the polynomial
* at those points between the last one and the one that is greatest,
* without being greater than the leftmost old scale point, or least
* if the scale is decreasing at the end of the interval we are looking
* at.
*/
lastone = putinterval(result, tdegree, ndata, -1,
nscale, nlen, xdata[middle], sign);
/* Now plot the rest, piece by piece. l is the
* last element under consideration.
*/
for (++l; l < olen && lastone < nlen - 1; l++) {
double out;
/* Shift the old stuff by one and get another value. */
out = xdata[0];
for (i = 0; i < degree; i++) {
xdata[i] = xdata[i + 1];
ydata[i] = ydata[i + 1];
}
ydata[i] = data[l];
xdata[i] = oscale[l];
/* Check for vertical edge. */
if (oscale[l] == xdata[i - 1]) {
if (degree == 1)
diff = xdata[0] - out;
else
diff = xdata[i - 1] - xdata[i - 2];
xdata[i - 1] -= sign * diff * EDGE_FACTOR;
}
/* Skip input points until the next output point is framed. */
if (l < olen - degree) {
if (sign > 0 && xdata[middle] < nscale[lastone + 1])
continue;
else if (sign < 0 && xdata[middle] > nscale[lastone + 1])
continue;
}
i = 0;
tdegree = degree;
while (!ft_polyfit(xdata + i, ydata + i, result, tdegree, scratch)) {
/* If it doesn't work this time, bump the interpolation
* degree down by one.
*/
if (--tdegree == 0) {
fprintf(cp_err, "ft_interpolate: Internal Error.\n");
return (FALSE);
}
if (!((degree - tdegree) & 1))
++i; // Drop left point after right.
}
lastone = putinterval(result, tdegree, ndata, lastone,
nscale, nlen, xdata[middle], sign);
}
lastone = putinterval(result, degree, ndata, lastone,
nscale, nlen, oscale[olen - 1], sign);
if (lastone < nlen - 1) /* ??? */
ndata[nlen - 1] = data[olen - 1];
tfree(scratch);
tfree(xdata);
tfree(ydata);
tfree(result);
return (TRUE);
}