/* * Copyright (c) 2003 Matteo Frigo * Copyright (c) 2003 Massachusetts Institute of Technology * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "rdft.h" #include "dft.h" /* * Compute transforms with large prime factors using Rader's trick: * turn the factors into convolutions of size n - 1, which you then * perform via a pair of FFTs. This file contains only twiddle hc2hc * transforms, which are actually ordinary complex transforms in a * slightly funny order. */ typedef struct { solver super; rdft_kind kind; } S; typedef struct { plan_rdft super; plan *cldr, *cldr0; plan *cld; R *W; R *omega; int m, r, g, ginv; int os, ios; rdft_kind kind; } P; static rader_tl *twiddles = 0; /***************************************************************************/ /* Below, we extensively use the identity that fft(x*)* = ifft(x) in order to share data between forward and backward transforms and to obviate the necessity of having separate forward and backward plans. */ static void apply_aux(int r, plan_dft *cldr, const R *omega, R *buf, R *ro, R i0, R *io) { R r0; int k; /* compute DFT of buf, operating in-place */ cldr->apply((plan *) cldr, buf, buf+1, buf, buf+1); /* set output DC component: */ ro[0] = (r0 = ro[0]) + buf[0]; io[0] = i0 + buf[1]; /* now, multiply by omega: */ for (k = 0; k < r - 1; ++k) { R rB, iB, rW, iW; rW = omega[2*k]; iW = omega[2*k+1]; rB = buf[2*k]; iB = buf[2*k+1]; buf[2*k] = rW * rB - iW * iB; buf[2*k+1] = -(rW * iB + iW * rB); } /* this will add input[0] to all of the outputs after the ifft */ buf[0] += r0; buf[1] -= i0; /* inverse FFT: */ cldr->apply((plan *) cldr, buf, buf+1, buf, buf+1); } static void apply_dit(const plan *ego_, R *I, R *O) { const P *ego = (const P *) ego_; plan_dft *cldr; int os, ios; int j, k, gpower, g, ginv, r, m; R *buf, *rio, *ii, *io; const R *omega, *W; /* size-m child transforms: */ { plan_rdft *cld = (plan_rdft *) ego->cld; cld->apply((plan *) cld, I, O); } /* 0th twiddle transform is just size-r (prime) R2HC: */ { plan_rdft *cldr0 = (plan_rdft *) ego->cldr0; cldr0->apply((plan *) cldr0, O, O); } cldr = (plan_dft *) ego->cldr; r = ego->r; m = ego->m; g = ego->g; ginv = ego->ginv; omega = ego->omega; W = ego->W; os = ego->os; ios = ego->ios; gpower = 1; rio = O + os; ii = O + (m - 1) * os; io = O + (r * m - 1) * os; buf = (R *) MALLOC(sizeof(R) * (r - 1) * 2, BUFFERS); for (j = 2; j < m; j += 2, rio += os, ii -= os, io -= os, W += 2*(r-1)) { /* First, permute the input and multiply by W, storing in buf: */ A(gpower == 1); for (k = 0; k < r - 1; ++k, gpower = MULMOD(gpower, g, r)) { R rA, iA, rW, iW; rA = rio[gpower * ios]; iA = ii[gpower * ios]; rW = W[2*k]; iW = W[2*k+1]; buf[2*k] = rW * rA - iW * iA; buf[2*k+1] = rW * iA + iW * rA; } /* gpower == g^(r-1) mod r == 1 */; apply_aux(r, cldr, omega, buf, rio, ii[0], io); /* finally, do inverse permutation to unshuffle the output: */ A(gpower == 1); for (k = 0; k < r - 1; ++k, gpower = MULMOD(gpower, ginv, r)) { rio[gpower * ios] = buf[2*k]; io[-gpower * ios] = -buf[2*k+1]; } A(gpower == 1); /* second half of array must be fiddled to get real/imag parts in correct spots: */ for (k = (r+1)/2; k < r; ++k) { R t; t = rio[k * ios]; rio[k * ios] = -io[-k * ios]; io[-k * ios] = t; } } /* Avoid funny m/2-th iter by requiring m odd. This always happens anyway because all the factors of 2 get divided out first by codelets (Rader is UGLY for small factors). */ X(ifree)(buf); } static void apply_dif(const plan *ego_, R *I, R *O) { const P *ego = (const P *) ego_; plan_dft *cldr; int is, ios; int j, k, gpower, g, ginv, r, m; R *buf, *rio, *ii, *io; const R *omega, *W; /* 0th twiddle transform is just size-r (prime) HC2R: */ { plan_rdft *cldr0 = (plan_rdft *) ego->cldr0; cldr0->apply((plan *) cldr0, I, I); } cldr = (plan_dft *) ego->cldr; r = ego->r; m = ego->m; g = ego->g; ginv = ego->ginv; omega = ego->omega; W = ego->W + 2*(r-1); /* simplify reverse indexing of W */ is = ego->os; ios = ego->ios; gpower = 1; rio = I + is; io = I + (m - 1) * is; ii = I + (r * m - 1) * is; buf = (R *) MALLOC(sizeof(R) * (r - 1) * 2, BUFFERS); for (j = 2; j < m; j += 2, rio += is, ii -= is, io -= is, W += 2*(r-1)) { /* second half of array must be unfiddled to get real/imag parts from correct spots: */ for (k = (r+1)/2; k < r; ++k) { R t; t = rio[k * ios]; rio[k * ios] = ii[-k * ios]; ii[-k * ios] = -t; } /* First, permute the input, storing in buf: */ A(gpower == 1); for (k = 0; k < r - 1; ++k, gpower = MULMOD(gpower, g, r)) { buf[2*k] = rio[gpower * ios]; buf[2*k+1] = -ii[-gpower * ios]; } /* gpower == g^(r-1) mod r == 1 */; A(gpower == 1); apply_aux(r, cldr, omega, buf, rio, -ii[0], io); io[0] = -io[0]; /* finally, do inverse permutation to unshuffle the output, also multiplying by the inverse twiddle factors W*. The twiddle factors are accessed in reverse order W[-k], because here we exponentiating ginv and not g as in mktwiddle. */ { /* W[-0] = W[0] case must be handled specially */ R rA, iA, rW, iW; rA = buf[0]; iA = buf[1]; rW = W[-2*(r-1)]; iW = W[-2*(r-1) + 1]; rio[ios] = rA * rW + iA * iW; io[ios] = iA * rW - rA * iW; } gpower = ginv; for (k = 1; k < r - 1; ++k, gpower = MULMOD(gpower, ginv, r)) { R rA, iA, rW, iW; rA = buf[2*k]; iA = buf[2*k+1]; rW = W[-2*k]; iW = W[-2*k+1]; rio[gpower * ios] = rA * rW + iA * iW; io[gpower * ios] = iA * rW - rA * iW; } A(gpower == 1); } /* Avoid funny m/2-th iter by requiring m odd. This always happens anyway because all the factors of 2 get divided out first by codelets (Rader is UGLY for small factors). */ X(ifree)(buf); /* size-m child transforms: */ { plan_rdft *cld = (plan_rdft *) ego->cld; cld->apply((plan *) cld, I, O); } } static R *mktwiddle(int m, int r, int g) { int i, j, gpower; int n = r * m; R *W; if ((W = X(rader_tl_find)(m, r, g, twiddles))) return W; W = (R *)MALLOC(sizeof(R) * (r - 1) * ((m-1)/2) * 2, TWIDDLES); for (i = 1; i < (m+1)/2; ++i) { for (gpower = 1, j = 0; j < r - 1; ++j, gpower = MULMOD(gpower, g, r)) { int k = (i - 1) * (r - 1) + j; W[2*k] = X(cos2pi)(i * gpower, n); W[2*k+1] = FFT_SIGN * X(sin2pi)(i * gpower, n); } A(gpower == 1); } X(rader_tl_insert)(m, r, g, W, &twiddles); return W; } static void free_twiddle(R *twiddle) { X(rader_tl_delete)(twiddle, &twiddles); } /***************************************************************************/ static void awake(plan *ego_, int flg) { P *ego = (P *) ego_; AWAKE(ego->cldr0, flg); AWAKE(ego->cldr, flg); AWAKE(ego->cld, flg); if (flg) { if (!ego->omega) ego->omega = X(dft_rader_mkomega)(ego->cldr, ego->r, ego->ginv); if (!ego->W) ego->W = mktwiddle(ego->m, ego->r, ego->g); } else { X(dft_rader_free_omega)(&ego->omega); free_twiddle(ego->W); ego->W = 0; } } static void destroy(plan *ego_) { P *ego = (P *) ego_; X(plan_destroy_internal)(ego->cld); X(plan_destroy_internal)(ego->cldr); X(plan_destroy_internal)(ego->cldr0); } static void print(const plan *ego_, printer *p) { const P *ego = (const P *) ego_; p->print(p, "(rdft-rader-%s-%d%(%p%)%(%p%)%(%p%))", ego->kind == R2HC ? "r2hc-dit" : "hc2r-dif", ego->r, ego->cldr0, ego->cldr, ego->cld); } static int applicable0(const solver *ego_, const problem *p_) { if (RDFTP(p_)) { const S *ego = (const S *) ego_; const problem_rdft *p = (const problem_rdft *) p_; return (1 && p->sz->rnk == 1 && p->vecsz->rnk == 0 && p->sz->dims[0].n > 1 && p->sz->dims[0].n % 4 /* make sure n / r = m is odd */ && p->kind[0] == ego->kind && !X(is_prime)(p->sz->dims[0].n) /* avoid inf. loops planning cldr0 */ ); } return 0; } static int applicable(const solver *ego_, const problem *p_, const planner *plnr) { return (!NO_UGLYP(plnr) && applicable0(ego_, p_)); } static int mkP(P *pln, int r, R *O, int ios, rdft_kind kind, planner *plnr) { plan *cldr = (plan *) 0; plan *cldr0 = (plan *) 0; R *buf = (R *) 0; cldr0 = X(mkplan_d)(plnr, X(mkproblem_rdft_1_d)(X(mktensor_1d)(r, ios, ios), X(mktensor_1d)(1, 0, 0), O, O, kind)); if (!cldr0) goto nada; /* initial allocation for the purpose of planning */ buf = (R *) MALLOC(sizeof(R) * (r - 1) * 2, BUFFERS); cldr = X(mkplan_d)(plnr, X(mkproblem_dft_d)(X(mktensor_1d)(r - 1, 2, 2), X(mktensor_1d)(1, 0, 0), buf, buf + 1, buf, buf + 1)); if (!cldr) goto nada; X(ifree)(buf); pln->cldr = cldr; pln->cldr0 = cldr0; pln->omega = 0; pln->r = r; pln->g = X(find_generator)(r); pln->ginv = X(power_mod)(pln->g, r - 2, r); pln->kind = kind; A(MULMOD(pln->g, pln->ginv, r) == 1); X(ops_add)(&cldr->ops, &cldr->ops, &pln->super.super.ops); pln->super.super.ops.other += (r - 1) * (4 * 2 + 6) + 6; pln->super.super.ops.add += 2 * (r - 1) * 2 + 4; pln->super.super.ops.mul += 2 * (r - 1) * 4; return 1; nada: X(ifree0)(buf); X(plan_destroy_internal)(cldr); X(plan_destroy_internal)(cldr0); return 0; } static plan *mkplan_dit(const solver *ego, const problem *p_, planner *plnr) { const problem_rdft *p = (const problem_rdft *) p_; P *pln = 0; int n, is, os, r, m; plan *cld = (plan *) 0; static const plan_adt padt = { X(rdft_solve), awake, print, destroy }; if (!applicable(ego, p_, plnr)) goto nada; n = p->sz->dims[0].n; is = p->sz->dims[0].is; os = p->sz->dims[0].os; r = X(first_divisor)(n); m = n / r; cld = X(mkplan_d)(plnr, X(mkproblem_rdft_d)(X(mktensor_1d)(m, r * is, os), X(mktensor_1d)(r, is, m * os), p->I, p->O, p->kind)); if (!cld) goto nada; pln = MKPLAN_RDFT(P, &padt, apply_dit); if (!mkP(pln, r, p->O, os*m, p->kind[0], plnr)) goto nada; pln->ios = os*m; pln->os = os; pln->m = m; pln->cld = cld; pln->W = 0; X(ops_madd)((m - 1)/2, &pln->super.super.ops, &cld->ops, &pln->super.super.ops); return &(pln->super.super); nada: X(plan_destroy_internal)(cld); X(ifree0)(pln); return (plan *) 0; } static plan *mkplan_dif(const solver *ego, const problem *p_, planner *plnr) { const problem_rdft *p = (const problem_rdft *) p_; P *pln = 0; int n, is, os, r, m; plan *cld = (plan *) 0; static const plan_adt padt = { X(rdft_solve), awake, print, destroy }; if (!applicable(ego, p_, plnr)) goto nada; n = p->sz->dims[0].n; is = p->sz->dims[0].is; os = p->sz->dims[0].os; r = X(first_divisor)(n); m = n / r; cld = X(mkplan_d)(plnr, X(mkproblem_rdft_d)(X(mktensor_1d)(m, is, r * os), X(mktensor_1d)(r, m * is, os), p->I, p->O, p->kind)); if (!cld) goto nada; pln = MKPLAN_RDFT(P, &padt, apply_dif); if (!mkP(pln, r, p->I, is*m, p->kind[0], plnr)) goto nada; pln->ios = is*m; pln->os = is; pln->m = m; pln->cld = cld; pln->W = 0; X(ops_madd)((m - 1)/2, &pln->super.super.ops, &cld->ops, &pln->super.super.ops); return &(pln->super.super); nada: X(plan_destroy_internal)(cld); X(ifree0)(pln); return (plan *) 0; } /* constructors */ static solver *mksolver_dit(void) { static const solver_adt sadt = { mkplan_dit }; S *slv = MKSOLVER(S, &sadt); slv->kind = R2HC; return &(slv->super); } static solver *mksolver_dif(void) { static const solver_adt sadt = { mkplan_dif }; S *slv = MKSOLVER(S, &sadt); slv->kind = HC2R; return &(slv->super); } void X(rdft_rader_hc2hc_register)(planner *p) { REGISTER_SOLVER(p, mksolver_dit()); REGISTER_SOLVER(p, mksolver_dif()); }