#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); }