File indexing completed on 2024-05-12 03:47:50

0001 /*
0002     File                 : nsl_conv.c
0003     Project              : LabPlot
0004     Description          : NSL discrete convolution functions
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2018 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "nsl_conv.h"
0011 #include "nsl_common.h"
0012 #include <gsl/gsl_cblas.h>
0013 #include <gsl/gsl_fft_halfcomplex.h>
0014 #ifdef HAVE_FFTW3
0015 #include <fftw3.h>
0016 #endif
0017 #include "backend/nsl/nsl_stats.h"
0018 
0019 const char* nsl_conv_direction_name[] = {i18n("Forward (Convolution)"), i18n("Backward (Deconvolution)")};
0020 const char* nsl_conv_type_name[] = {i18n("Linear (Zero-padded)"), i18n("Circular")};
0021 const char* nsl_conv_method_name[] = {i18n("Auto"), i18n("Direct"), i18n("FFT")};
0022 const char* nsl_conv_norm_name[] = {i18n("None"), i18n("Sum"), i18n("Euclidean")};
0023 const char* nsl_conv_wrap_name[] = {i18n("None"), i18n("Maximum"), i18n("Center (Acausal)")};
0024 const char* nsl_conv_kernel_name[] = {i18n("Sliding Average"),
0025                                       i18n("Triangular Smooth"),
0026                                       i18n("Pseudo-Gaussian Smooth"),
0027                                       i18n("First Derivative"),
0028                                       i18n("Smooth First Derivative"),
0029                                       i18n("Second Derivative"),
0030                                       i18n("Third Derivative"),
0031                                       i18n("Fourth Derivative"),
0032                                       i18n("Gaussian"),
0033                                       i18n("Lorentzian")};
0034 
0035 int nsl_conv_standard_kernel(double k[], size_t n, nsl_conv_kernel_type type) {
0036     size_t i;
0037 
0038     int validn = 1;
0039     switch (type) {
0040     case nsl_conv_kernel_avg:
0041         for (i = 0; i < n; i++)
0042             k[i] = 1.;
0043         break;
0044     case nsl_conv_kernel_smooth_triangle:
0045         for (i = 0; i < n / 2; i++)
0046             k[i] = i + 1.;
0047         for (i = n / 2; i < n; i++)
0048             k[i] = (double)(n - i);
0049         break;
0050     case nsl_conv_kernel_smooth_gaussian:
0051         switch (n) {
0052         case 5:
0053             k[0] = 1;
0054             k[1] = 4;
0055             k[2] = 6;
0056             k[3] = 4;
0057             k[4] = 1;
0058             break;
0059         case 7:
0060             k[0] = 1;
0061             k[1] = 4;
0062             k[2] = 8;
0063             k[3] = 10;
0064             k[4] = 8;
0065             k[5] = 4;
0066             k[6] = 1;
0067             break;
0068         case 9:
0069             k[0] = 1;
0070             k[1] = 4;
0071             k[2] = 9;
0072             k[3] = 14;
0073             k[4] = 17;
0074             k[5] = 14;
0075             k[6] = 9;
0076             k[7] = 4;
0077             k[8] = 1;
0078             break;
0079         default:
0080             validn = 0;
0081         }
0082         break;
0083     case nsl_conv_kernel_first_derivative:
0084         if (n == 2) {
0085             k[0] = -1;
0086             k[1] = 1;
0087         } else
0088             validn = 0;
0089         break;
0090     case nsl_conv_kernel_smooth_first_derivative:
0091         if (n % 2) {
0092             for (i = 0; i < n; i++)
0093                 k[i] = (int)(i - n / 2);
0094         } else
0095             validn = 0;
0096         break;
0097     case nsl_conv_kernel_second_derivative:
0098         if (n == 3) {
0099             k[0] = 1;
0100             k[1] = -2;
0101             k[2] = 1;
0102         } else
0103             validn = 0;
0104         break;
0105     case nsl_conv_kernel_third_derivative:
0106         if (n == 4) {
0107             k[0] = 1;
0108             k[1] = -3;
0109             k[2] = 3;
0110             k[3] = -1;
0111         } else
0112             validn = 0;
0113         break;
0114     case nsl_conv_kernel_fourth_derivative:
0115         if (n == 5) {
0116             k[0] = 1;
0117             k[1] = -4;
0118             k[2] = 6;
0119             k[3] = -4;
0120             k[4] = 1;
0121         } else
0122             validn = 0;
0123         break;
0124     case nsl_conv_kernel_gaussian: {
0125         double s = n / 5.; /* relative width */
0126         for (i = 0; i < n; i++) {
0127             double x = i - (n - 1.) / 2.;
0128             k[i] = M_SQRT1_2 / M_SQRTPI / s * exp(-x * x / 2. / s / s);
0129         }
0130         break;
0131     }
0132     case nsl_conv_kernel_lorentzian: {
0133         double s = n / 5.; /* relative width */
0134         for (i = 0; i < n; i++) {
0135             double x = i - (n - 1.) / 2.;
0136             k[i] = s / M_PI / (s * s + x * x);
0137         }
0138         break;
0139     }
0140     }
0141 
0142     if (!validn) {
0143         printf("ERROR: kernel size %lu not supported for kernel %s\n", (unsigned long)n, nsl_conv_kernel_name[type]);
0144         return -1;
0145     }
0146 
0147     /* debug */
0148     printf("[");
0149     for (i = 0; i < n; i++)
0150         printf("%g ", k[i]);
0151     puts("]");
0152 
0153     return 0;
0154 }
0155 
0156 int nsl_conv_convolution_direction(double s[],
0157                                    size_t n,
0158                                    double r[],
0159                                    size_t m,
0160                                    nsl_conv_direction_type dir,
0161                                    nsl_conv_type_type type,
0162                                    nsl_conv_method_type method,
0163                                    nsl_conv_norm_type normalize,
0164                                    nsl_conv_wrap_type wrap,
0165                                    double out[]) {
0166     if (dir == nsl_conv_direction_forward)
0167         return nsl_conv_convolution(s, n, r, m, type, method, normalize, wrap, out);
0168     else
0169         return nsl_conv_deconvolution(s, n, r, m, type, normalize, wrap, out);
0170 }
0171 
0172 int nsl_conv_convolution(double s[],
0173                          size_t n,
0174                          double r[],
0175                          size_t m,
0176                          nsl_conv_type_type type,
0177                          nsl_conv_method_type method,
0178                          nsl_conv_norm_type normalize,
0179                          nsl_conv_wrap_type wrap,
0180                          double out[]) {
0181     if (method == nsl_conv_method_direct || (method == nsl_conv_method_auto && GSL_MAX_INT(n, m) <= NSL_CONV_METHOD_BORDER)) {
0182         if (type == nsl_conv_type_linear)
0183             return nsl_conv_linear_direct(s, n, r, m, normalize, wrap, out);
0184         else if (type == nsl_conv_type_circular)
0185             return nsl_conv_circular_direct(s, n, r, m, normalize, wrap, out);
0186     } else {
0187         return nsl_conv_fft_type(s, n, r, m, nsl_conv_direction_forward, type, normalize, wrap, out);
0188     }
0189 
0190     return 0;
0191 }
0192 
0193 int nsl_conv_deconvolution(double s[],
0194                            size_t n,
0195                            double r[],
0196                            size_t m,
0197                            nsl_conv_type_type type,
0198                            nsl_conv_norm_type normalize,
0199                            nsl_conv_wrap_type wrap,
0200                            double out[]) {
0201     /* only supported by FFT method */
0202     return nsl_conv_fft_type(s, n, r, m, nsl_conv_direction_backward, type, normalize, wrap, out);
0203 }
0204 
0205 int nsl_conv_linear_direct(const double s[], size_t n, double r[], size_t m, nsl_conv_norm_type normalize, nsl_conv_wrap_type wrap, double out[]) {
0206     size_t i, j, size = n + m - 1, wi = 0;
0207     double norm = 1;
0208     if (normalize == nsl_conv_norm_euclidean) {
0209         if ((norm = cblas_dnrm2((int)m, r, 1)) == 0)
0210             norm = 1.;
0211     } else if (normalize == nsl_conv_norm_sum) {
0212         if ((norm = cblas_dasum((int)m, r, 1)) == 0)
0213             norm = 1.;
0214     }
0215 
0216     if (wrap == nsl_conv_wrap_max)
0217         nsl_stats_maximum(r, m, &wi);
0218     else if (wrap == nsl_conv_wrap_center)
0219         wi = m / 2;
0220 
0221     for (j = 0; j < size; j++) {
0222         int index; // can be negative
0223         double res = 0;
0224         for (i = 0; i < n; i++) {
0225             index = (int)(j - i);
0226             if (index >= 0 && index < (int)m)
0227                 res += s[i] * r[index] / norm;
0228         }
0229         index = (int)(j - wi);
0230         if (index < 0)
0231             index += (int)size;
0232         out[index] = res;
0233     }
0234 
0235     return 0;
0236 }
0237 
0238 int nsl_conv_circular_direct(const double s[], size_t n, double r[], size_t m, nsl_conv_norm_type normalize, nsl_conv_wrap_type wrap, double out[]) {
0239     size_t i, j, size = GSL_MAX(n, m), wi = 0;
0240     double norm = 1;
0241     if (normalize == nsl_conv_norm_euclidean) {
0242         if ((norm = cblas_dnrm2((int)m, r, 1)) == 0)
0243             norm = 1.;
0244     } else if (normalize == nsl_conv_norm_sum) {
0245         if ((norm = cblas_dasum((int)m, r, 1)) == 0)
0246             norm = 1.;
0247     }
0248 
0249     if (wrap == nsl_conv_wrap_max)
0250         nsl_stats_maximum(r, m, &wi);
0251     else if (wrap == nsl_conv_wrap_center)
0252         wi = m / 2;
0253 
0254     for (j = 0; j < size; j++) {
0255         int index; // can be negative
0256         double res = 0;
0257         for (i = 0; i < n; i++) {
0258             index = (int)(j - i);
0259             if (index < 0)
0260                 index += (int)size;
0261             if (index < (int)m)
0262                 res += s[i] * r[index] / norm;
0263         }
0264         index = (int)(j - wi);
0265         if (index < 0)
0266             index += (int)size;
0267         out[index] = res;
0268     }
0269 
0270     return 0;
0271 }
0272 
0273 int nsl_conv_fft_type(const double s[],
0274                       size_t n,
0275                       double r[],
0276                       size_t m,
0277                       nsl_conv_direction_type dir,
0278                       nsl_conv_type_type type,
0279                       nsl_conv_norm_type normalize,
0280                       nsl_conv_wrap_type wrap,
0281                       double out[]) {
0282     size_t i, size, wi = 0;
0283     if (type == nsl_conv_type_linear)
0284         size = n + m - 1;
0285     else // circular
0286         size = GSL_MAX(n, m);
0287 
0288     double norm = 1.;
0289     if (normalize == nsl_conv_norm_euclidean) {
0290         if ((norm = cblas_dnrm2((int)m, r, 1)) == 0)
0291             norm = 1.;
0292     } else if (normalize == nsl_conv_norm_sum) {
0293         if ((norm = cblas_dasum((int)m, r, 1)) == 0)
0294             norm = 1.;
0295     }
0296 
0297     if (wrap == nsl_conv_wrap_max)
0298         nsl_stats_maximum(r, m, &wi);
0299     else if (wrap == nsl_conv_wrap_center)
0300         wi = m / 2;
0301 
0302 #ifdef HAVE_FFTW3
0303     // already zero-pad here for FFT method and FFTW r2c
0304     size_t oldsize = size;
0305     size = 2 * (size / 2 + 1);
0306 #endif
0307 
0308     // zero-padded arrays
0309     double* stmp = (double*)malloc(size * sizeof(double));
0310     if (stmp == NULL) {
0311         printf("nsl_conv_fft_type(): ERROR allocating memory for 'stmp'!\n");
0312         return -1;
0313     }
0314 
0315     double* rtmp = (double*)malloc(size * sizeof(double));
0316     if (rtmp == NULL) {
0317         free(stmp);
0318         printf("nsl_corr_fft_type(): ERROR allocating memory for 'rtmp'!\n");
0319         return -1;
0320     }
0321 
0322     for (i = 0; i < n; i++)
0323         stmp[i] = s[i];
0324     for (i = n; i < size; i++)
0325         stmp[i] = 0;
0326     for (i = 0; i < m; i++) {
0327         rtmp[i] = r[i] / norm; /* norm response */
0328     }
0329     for (i = m; i < size; i++)
0330         rtmp[i] = 0;
0331 
0332     int status;
0333 #ifdef HAVE_FFTW3 // already wraps output
0334     status = nsl_conv_fft_FFTW(stmp, rtmp, oldsize, dir, wi, out);
0335 #else // GSL
0336     status = nsl_conv_fft_GSL(stmp, rtmp, size, dir, out);
0337     for (i = 0; i < size; i++) { // wrap output
0338         size_t index = (i + wi) % size;
0339         stmp[i] = out[index];
0340     }
0341     memcpy(out, stmp, size * sizeof(double));
0342 #endif
0343 
0344     free(stmp);
0345     free(rtmp);
0346 
0347     return status;
0348 }
0349 
0350 #ifdef HAVE_FFTW3
0351 int nsl_conv_fft_FFTW(double s[], double r[], size_t n, nsl_conv_direction_type dir, size_t wi, double out[]) {
0352     size_t i;
0353     const size_t size = 2 * (n / 2 + 1);
0354     double* in = (double*)malloc(size * sizeof(double));
0355     fftw_plan rpf = fftw_plan_dft_r2c_1d((int)n, in, (fftw_complex*)in, FFTW_ESTIMATE);
0356 
0357     fftw_execute_dft_r2c(rpf, s, (fftw_complex*)s);
0358     fftw_execute_dft_r2c(rpf, r, (fftw_complex*)r);
0359     fftw_destroy_plan(rpf);
0360     free(in);
0361 
0362     // multiply/divide
0363     if (dir == nsl_conv_direction_forward) {
0364         for (i = 0; i < size; i += 2) {
0365             double re = s[i] * r[i] - s[i + 1] * r[i + 1];
0366             double im = s[i] * r[i + 1] + s[i + 1] * r[i];
0367 
0368             s[i] = re;
0369             s[i + 1] = im;
0370         }
0371     } else {
0372         for (i = 0; i < size; i += 2) {
0373             double norm = r[i] * r[i] + r[i + 1] * r[i + 1];
0374             if (norm < DBL_MIN)
0375                 norm = 1.;
0376             double re = (s[i] * r[i] + s[i + 1] * r[i + 1]) / norm;
0377             double im = (s[i + 1] * r[i] - s[i] * r[i + 1]) / norm;
0378 
0379             s[i] = re;
0380             s[i + 1] = im;
0381         }
0382     }
0383 
0384     // back transform
0385     double* o = (double*)malloc(size * sizeof(double));
0386     fftw_plan rpb = fftw_plan_dft_c2r_1d((int)n, (fftw_complex*)o, o, FFTW_ESTIMATE);
0387 
0388     fftw_execute_dft_c2r(rpb, (fftw_complex*)s, s);
0389     fftw_destroy_plan(rpb);
0390 
0391     for (i = 0; i < n; i++) {
0392         size_t index = (i + wi) % n;
0393         out[i] = s[index] / n;
0394     }
0395     free(o);
0396 
0397     return 0;
0398 }
0399 #endif
0400 
0401 int nsl_conv_fft_GSL(double s[], double r[], size_t n, nsl_conv_direction_type dir, double out[]) {
0402     gsl_fft_real_workspace* work = gsl_fft_real_workspace_alloc(n);
0403     gsl_fft_real_wavetable* real = gsl_fft_real_wavetable_alloc(n);
0404 
0405     /* FFT s and r */
0406     gsl_fft_real_transform(s, 1, n, real, work);
0407     gsl_fft_real_transform(r, 1, n, real, work);
0408     gsl_fft_real_wavetable_free(real);
0409 
0410     size_t i;
0411     /* calculate halfcomplex product/quotient depending on direction */
0412     if (dir == nsl_conv_direction_forward) {
0413         out[0] = s[0] * r[0];
0414         for (i = 1; i < n; i++) {
0415             if (i % 2) { /* Re */
0416                 out[i] = s[i] * r[i];
0417                 if (i < n - 1) /* when n is even last value is real */
0418                     out[i] -= s[i + 1] * r[i + 1];
0419             } else /* Im */
0420                 out[i] = s[i - 1] * r[i] + s[i] * r[i - 1];
0421         }
0422     } else {
0423         out[0] = s[0] / r[0];
0424         for (i = 1; i < n; i++) {
0425             if (i % 2) { /* Re */
0426                 if (i == n - 1) /* when n is even last value is real */
0427                     out[i] = s[i] / r[i];
0428                 else {
0429                     double norm = r[i] * r[i] + r[i + 1] * r[i + 1];
0430                     if (norm < DBL_MIN)
0431                         norm = 1.;
0432                     out[i] = (s[i] * r[i] + s[i + 1] * r[i + 1]) / norm;
0433                 }
0434             } else { /* Im */
0435                 double norm = r[i - 1] * r[i - 1] + r[i] * r[i];
0436                 if (norm < DBL_MIN)
0437                     norm = 1.;
0438                 out[i] = (s[i] * r[i - 1] - s[i - 1] * r[i]) / norm;
0439             }
0440         }
0441     }
0442 
0443     /* back transform */
0444     gsl_fft_halfcomplex_wavetable* hc = gsl_fft_halfcomplex_wavetable_alloc(n);
0445     gsl_fft_halfcomplex_inverse(out, 1, n, hc, work);
0446     gsl_fft_halfcomplex_wavetable_free(hc);
0447     gsl_fft_real_workspace_free(work);
0448 
0449     return 0;
0450 }