(************************************************************************* Copyright (c) 2009, Sergey Bochkanov (ALGLIB project). >>> SOURCE LICENSE >>> 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 (www.fsf.org); 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. A copy of the GNU General Public License is available at http://www.fsf.org/licensing/licenses >>> END OF LICENSE >>> *************************************************************************) unit u_conv; interface uses Math, Sysutils, u_ap, u_ftbase, u_fft; procedure ConvC1D(const A : TComplex1DArray; M : Integer; const B : TComplex1DArray; N : Integer; var R : TComplex1DArray); procedure ConvC1DInv(const A : TComplex1DArray; M : Integer; const B : TComplex1DArray; N : Integer; var R : TComplex1DArray); procedure ConvC1DCircular(const S : TComplex1DArray; M : Integer; const R : TComplex1DArray; N : Integer; var C : TComplex1DArray); procedure ConvC1DCircularInv(const A : TComplex1DArray; M : Integer; const B : TComplex1DArray; N : Integer; var R : TComplex1DArray); procedure ConvR1D(const A : TReal1DArray; M : Integer; const B : TReal1DArray; N : Integer; var R : TReal1DArray); procedure ConvR1DInv(const A : TReal1DArray; M : Integer; const B : TReal1DArray; N : Integer; var R : TReal1DArray); procedure ConvR1DCircular(const S : TReal1DArray; M : Integer; const R : TReal1DArray; N : Integer; var C : TReal1DArray); procedure ConvR1DCircularInv(const A : TReal1DArray; M : Integer; const B : TReal1DArray; N : Integer; var R : TReal1DArray); procedure ConvC1DX(const A : TComplex1DArray; M : Integer; const B : TComplex1DArray; N : Integer; Circular : Boolean; Alg : Integer; Q : Integer; var R : TComplex1DArray); procedure ConvR1DX(const A : TReal1DArray; M : Integer; const B : TReal1DArray; N : Integer; Circular : Boolean; Alg : Integer; Q : Integer; var R : TReal1DArray); implementation (************************************************************************* 1-dimensional complex convolution. For given A/B returns conv(A,B) (non-circular). Subroutine can automatically choose between three implementations: straightforward O(M*N) formula for very small N (or M), overlap-add algorithm for cases where max(M,N) is significantly larger than min(M,N), but O(M*N) algorithm is too slow, and general FFT-based formula for cases where two previois algorithms are too slow. Algorithm has max(M,N)*log(max(M,N)) complexity for any M/N. INPUT PARAMETERS A - array[0..M-1] - complex function to be transformed M - problem size B - array[0..N-1] - complex function to be transformed N - problem size OUTPUT PARAMETERS R - convolution: A*B. array[0..N+M-2]. NOTE: It is assumed that A is zero at T<0, B is zero too. If one or both functions have non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************) procedure ConvC1D(const A : TComplex1DArray; M : Integer; const B : TComplex1DArray; N : Integer; var R : TComplex1DArray); begin Assert((N>0) and (M>0), 'ConvC1D: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer that B. // if M0) and (M>0) and (N<=M), 'ConvC1DInv: incorrect N or M!'); P := FTBaseFindSmooth(M); FTBaseGenerateComplexFFTPlan(P, Plan); SetLength(Buf, 2*P); I:=0; while I<=M-1 do begin Buf[2*I+0] := A[I].X; Buf[2*I+1] := A[I].Y; Inc(I); end; I:=M; while I<=P-1 do begin Buf[2*I+0] := 0; Buf[2*I+1] := 0; Inc(I); end; SetLength(Buf2, 2*P); I:=0; while I<=N-1 do begin Buf2[2*I+0] := B[I].X; Buf2[2*I+1] := B[I].Y; Inc(I); end; I:=N; while I<=P-1 do begin Buf2[2*I+0] := 0; Buf2[2*I+1] := 0; Inc(I); end; FTBaseExecutePlan(Buf, 0, P, Plan); FTBaseExecutePlan(Buf2, 0, P, Plan); I:=0; while I<=P-1 do begin C1.X := Buf[2*I+0]; C1.Y := Buf[2*I+1]; C2.X := Buf2[2*I+0]; C2.Y := Buf2[2*I+1]; C3 := C_Div(C1,C2); Buf[2*I+0] := C3.X; Buf[2*I+1] := -C3.Y; Inc(I); end; FTBaseExecutePlan(Buf, 0, P, Plan); T := AP_Double(1)/P; SetLength(R, M-N+1); I:=0; while I<=M-N do begin R[I].X := +T*Buf[2*I+0]; R[I].Y := -T*Buf[2*I+1]; Inc(I); end; end; (************************************************************************* 1-dimensional circular complex convolution. For given S/R returns conv(S,R) (circular). Algorithm has linearithmic complexity for any M/N. IMPORTANT: normal convolution is commutative, i.e. it is symmetric - conv(A,B)=conv(B,A). Cyclic convolution IS NOT. One function - S - is a signal, periodic function, and another - R - is a response, non-periodic function with limited length. INPUT PARAMETERS S - array[0..M-1] - complex periodic signal M - problem size B - array[0..N-1] - complex non-periodic response N - problem size OUTPUT PARAMETERS R - convolution: A*B. array[0..M-1]. NOTE: It is assumed that A is zero at T<0, B is zero too. If one or both functions have non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************) procedure ConvC1DCircular(const S : TComplex1DArray; M : Integer; const R : TComplex1DArray; N : Integer; var C : TComplex1DArray); var Buf : TComplex1DArray; I1 : Integer; I2 : Integer; J2 : Integer; i_ : Integer; i1_ : Integer; begin Assert((N>0) and (M>0), 'ConvC1DCircular: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if M0) and (M>0), 'ConvC1DCircularInv: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if M0) and (M>0), 'ConvR1D: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer that B. // if M0) and (M>0) and (N<=M), 'ConvR1DInv: incorrect N or M!'); P := FTBaseFindSmoothEven(M); SetLength(Buf, P); APVMove(@Buf[0], 0, M-1, @A[0], 0, M-1); I:=M; while I<=P-1 do begin Buf[I] := 0; Inc(I); end; SetLength(Buf2, P); APVMove(@Buf2[0], 0, N-1, @B[0], 0, N-1); I:=N; while I<=P-1 do begin Buf2[I] := 0; Inc(I); end; SetLength(Buf3, P); FTBaseGenerateComplexFFTPlan(P div 2, Plan); FFTR1DInternalEven(Buf, P, Buf3, Plan); FFTR1DInternalEven(Buf2, P, Buf3, Plan); Buf[0] := Buf[0]/Buf2[0]; Buf[1] := Buf[1]/Buf2[1]; I:=1; while I<=P div 2-1 do begin C1.X := Buf[2*I+0]; C1.Y := Buf[2*I+1]; C2.X := Buf2[2*I+0]; C2.Y := Buf2[2*I+1]; C3 := C_Div(C1,C2); Buf[2*I+0] := C3.X; Buf[2*I+1] := C3.Y; Inc(I); end; FFTR1DInvInternalEven(Buf, P, Buf3, Plan); SetLength(R, M-N+1); APVMove(@R[0], 0, M-N, @Buf[0], 0, M-N); end; (************************************************************************* 1-dimensional circular real convolution. Analogous to ConvC1DCircular(), see ConvC1DCircular() comments for more details. INPUT PARAMETERS S - array[0..M-1] - real signal M - problem size B - array[0..N-1] - real response N - problem size OUTPUT PARAMETERS R - convolution: A*B. array[0..M-1]. NOTE: It is assumed that A is zero at T<0, B is zero too. If one or both functions have non-zero values at negative T's, you can still use this subroutine - just shift its result correspondingly. -- ALGLIB -- Copyright 21.07.2009 by Bochkanov Sergey *************************************************************************) procedure ConvR1DCircular(const S : TReal1DArray; M : Integer; const R : TReal1DArray; N : Integer; var C : TReal1DArray); var Buf : TReal1DArray; I1 : Integer; I2 : Integer; J2 : Integer; begin Assert((N>0) and (M>0), 'ConvC1DCircular: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if M0) and (M>0), 'ConvR1DCircularInv: incorrect N or M!'); // // normalize task: make M>=N, // so A will be longer (at least - not shorter) that B. // if M0) and (M>0), 'ConvC1DX: incorrect N or M!'); Assert(N<=M, 'ConvC1DX: N0) and (M>0), 'ConvC1DX: incorrect N or M!'); Assert(N<=M, 'ConvC1DX: N2 - we should call small case code otherwise // if Alg=1 then begin Assert(M+N-1>2, 'ConvR1DX: internal error!'); if Circular and FTBaseIsSmooth(M) and (M mod 2=0) then begin // // special code for circular convolution with smooth even M // SetLength(Buf, M); APVMove(@Buf[0], 0, M-1, @A[0], 0, M-1); SetLength(Buf2, M); APVMove(@Buf2[0], 0, N-1, @B[0], 0, N-1); I:=N; while I<=M-1 do begin Buf2[I] := 0; Inc(I); end; SetLength(Buf3, M); FTBaseGenerateComplexFFTPlan(M div 2, Plan); FFTR1DInternalEven(Buf, M, Buf3, Plan); FFTR1DInternalEven(Buf2, M, Buf3, Plan); Buf[0] := Buf[0]*Buf2[0]; Buf[1] := Buf[1]*Buf2[1]; I:=1; while I<=M div 2-1 do begin AX := Buf[2*I+0]; AY := Buf[2*I+1]; BX := Buf2[2*I+0]; BY := Buf2[2*I+1]; TX := AX*BX-AY*BY; TY := AX*BY+AY*BX; Buf[2*I+0] := TX; Buf[2*I+1] := TY; Inc(I); end; FFTR1DInvInternalEven(Buf, M, Buf3, Plan); SetLength(R, M); APVMove(@R[0], 0, M-1, @Buf[0], 0, M-1); end else begin // // M is non-smooth or non-even, general code (circular/non-circular): // * first part is the same for circular and non-circular // convolutions. zero padding, FFTs, inverse FFTs // * second part differs: // * for non-circular convolution we just copy array // * for circular convolution we add array tail to its head // P := FTBaseFindSmoothEven(M+N-1); SetLength(Buf, P); APVMove(@Buf[0], 0, M-1, @A[0], 0, M-1); I:=M; while I<=P-1 do begin Buf[I] := 0; Inc(I); end; SetLength(Buf2, P); APVMove(@Buf2[0], 0, N-1, @B[0], 0, N-1); I:=N; while I<=P-1 do begin Buf2[I] := 0; Inc(I); end; SetLength(Buf3, P); FTBaseGenerateComplexFFTPlan(P div 2, Plan); FFTR1DInternalEven(Buf, P, Buf3, Plan); FFTR1DInternalEven(Buf2, P, Buf3, Plan); Buf[0] := Buf[0]*Buf2[0]; Buf[1] := Buf[1]*Buf2[1]; I:=1; while I<=P div 2-1 do begin AX := Buf[2*I+0]; AY := Buf[2*I+1]; BX := Buf2[2*I+0]; BY := Buf2[2*I+1]; TX := AX*BX-AY*BY; TY := AX*BY+AY*BX; Buf[2*I+0] := TX; Buf[2*I+1] := TY; Inc(I); end; FFTR1DInvInternalEven(Buf, P, Buf3, Plan); if Circular then begin // // circular, add tail to head // SetLength(R, M); APVMove(@R[0], 0, M-1, @Buf[0], 0, M-1); if N>=2 then begin APVAdd(@R[0], 0, N-2, @Buf[0], M, M+N-2); end; end else begin // // non-circular, just copy // SetLength(R, M+N-1); APVMove(@R[0], 0, M+N-2, @Buf[0], 0, M+N-2); end; end; Exit; end; // // overlap-add method // if Alg=2 then begin Assert((Q+N-1) mod 2=0, 'ConvR1DX: internal error!'); SetLength(Buf, Q+N-1); SetLength(Buf2, Q+N-1); SetLength(Buf3, Q+N-1); FTBaseGenerateComplexFFTPlan((Q+N-1) div 2, Plan); // // prepare R // if Circular then begin SetLength(R, M); I:=0; while I<=M-1 do begin R[I] := 0; Inc(I); end; end else begin SetLength(R, M+N-1); I:=0; while I<=M+N-2 do begin R[I] := 0; Inc(I); end; end; // // pre-calculated FFT(B) // APVMove(@Buf2[0], 0, N-1, @B[0], 0, N-1); J:=N; while J<=Q+N-2 do begin Buf2[J] := 0; Inc(J); end; FFTR1DInternalEven(Buf2, Q+N-1, Buf3, Plan); // // main overlap-add cycle // I := 0; while I<=M-1 do begin P := Min(Q, M-I); APVMove(@Buf[0], 0, P-1, @A[0], I, I+P-1); J:=P; while J<=Q+N-2 do begin Buf[J] := 0; Inc(J); end; FFTR1DInternalEven(Buf, Q+N-1, Buf3, Plan); Buf[0] := Buf[0]*Buf2[0]; Buf[1] := Buf[1]*Buf2[1]; J:=1; while J<=(Q+N-1) div 2-1 do begin AX := Buf[2*J+0]; AY := Buf[2*J+1]; BX := Buf2[2*J+0]; BY := Buf2[2*J+1]; TX := AX*BX-AY*BY; TY := AX*BY+AY*BX; Buf[2*J+0] := TX; Buf[2*J+1] := TY; Inc(J); end; FFTR1DInvInternalEven(Buf, Q+N-1, Buf3, Plan); if Circular then begin J1 := Min(I+P+N-2, M-1)-I; J2 := J1+1; end else begin J1 := P+N-2; J2 := J1+1; end; APVAdd(@R[0], I, I+J1, @Buf[0], 0, J1); if P+N-2>=J2 then begin APVAdd(@R[0], 0, P+N-2-J2, @Buf[0], J2, P+N-2); end; I := I+P; end; Exit; end; end; end.