001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.math4.transform; 018 019import java.util.function.UnaryOperator; 020import java.util.function.DoubleUnaryOperator; 021 022import org.apache.commons.numbers.complex.Complex; 023import org.apache.commons.numbers.core.ArithmeticUtils; 024 025/** 026 * Implements the Fast Cosine Transform for transformation of one-dimensional 027 * real data sets. For reference, see James S. Walker, <em>Fast Fourier 028 * Transforms</em>, chapter 3 (ISBN 0849371635). 029 * <p> 030 * There are several variants of the discrete cosine transform. The present 031 * implementation corresponds to DCT-I, with various normalization conventions, 032 * which are specified by the parameter {@link Norm}. 033 * <p> 034 * DCT-I is equivalent to DFT of an <em>even extension</em> of the data series. 035 * More precisely, if x<sub>0</sub>, …, x<sub>N-1</sub> is the data set 036 * to be cosine transformed, the extended data set 037 * x<sub>0</sub><sup>#</sup>, …, x<sub>2N-3</sub><sup>#</sup> 038 * is defined as follows 039 * <ul> 040 * <li>x<sub>k</sub><sup>#</sup> = x<sub>k</sub> if 0 ≤ k < N,</li> 041 * <li>x<sub>k</sub><sup>#</sup> = x<sub>2N-2-k</sub> 042 * if N ≤ k < 2N - 2.</li> 043 * </ul> 044 * <p> 045 * Then, the standard DCT-I y<sub>0</sub>, …, y<sub>N-1</sub> of the real 046 * data set x<sub>0</sub>, …, x<sub>N-1</sub> is equal to <em>half</em> 047 * of the N first elements of the DFT of the extended data set 048 * x<sub>0</sub><sup>#</sup>, …, x<sub>2N-3</sub><sup>#</sup> 049 * <br> 050 * y<sub>n</sub> = (1 / 2) ∑<sub>k=0</sub><sup>2N-3</sup> 051 * x<sub>k</sub><sup>#</sup> exp[-2πi nk / (2N - 2)] 052 * k = 0, …, N-1. 053 * <p> 054 * The present implementation of the discrete cosine transform as a fast cosine 055 * transform requires the length of the data set to be a power of two plus one 056 * (N = 2<sup>n</sup> + 1). Besides, it implicitly assumes 057 * that the sampled function is even. 058 */ 059public class FastCosineTransform implements RealTransform { 060 /** Operation to be performed. */ 061 private final UnaryOperator<double[]> op; 062 063 /** 064 * @param normalization Normalization to be applied to the 065 * transformed data. 066 * @param inverse Whether to perform the inverse transform. 067 */ 068 public FastCosineTransform(final Norm normalization, 069 final boolean inverse) { 070 op = create(normalization, inverse); 071 } 072 073 /** 074 * @param normalization Normalization to be applied to the 075 * transformed data. 076 */ 077 public FastCosineTransform(final Norm normalization) { 078 this(normalization, false); 079 } 080 081 /** 082 * {@inheritDoc} 083 * 084 * @throws IllegalArgumentException if the length of the data array is 085 * not a power of two plus one. 086 */ 087 @Override 088 public double[] apply(final double[] f) { 089 return op.apply(f); 090 } 091 092 /** 093 * {@inheritDoc} 094 * 095 * @throws IllegalArgumentException if the number of sample points is 096 * not a power of two plus one, if the lower bound is greater than or 097 * equal to the upper bound, if the number of sample points is negative. 098 */ 099 @Override 100 public double[] apply(final DoubleUnaryOperator f, 101 final double min, 102 final double max, 103 final int n) { 104 return apply(TransformUtils.sample(f, min, max, n)); 105 } 106 107 /** 108 * Perform the FCT algorithm (including inverse). 109 * 110 * @param f Data to be transformed. 111 * @return the transformed array. 112 * @throws IllegalArgumentException if the length of the data array is 113 * not a power of two plus one. 114 */ 115 private double[] fct(double[] f) { 116 final int n = f.length - 1; 117 if (!ArithmeticUtils.isPowerOfTwo(n)) { 118 throw new TransformException(TransformException.NOT_POWER_OF_TWO_PLUS_ONE, 119 Integer.valueOf(f.length)); 120 } 121 122 final double[] transformed = new double[f.length]; 123 124 if (n == 1) { // trivial case 125 transformed[0] = 0.5 * (f[0] + f[1]); 126 transformed[1] = 0.5 * (f[0] - f[1]); 127 return transformed; 128 } 129 130 // construct a new array and perform FFT on it 131 final double[] x = new double[n]; 132 x[0] = 0.5 * (f[0] + f[n]); 133 final int nShifted = n >> 1; 134 x[nShifted] = f[nShifted]; 135 // temporary variable for transformed[1] 136 double t1 = 0.5 * (f[0] - f[n]); 137 final double piOverN = Math.PI / n; 138 for (int i = 1; i < nShifted; i++) { 139 final int nMi = n - i; 140 final double fi = f[i]; 141 final double fnMi = f[nMi]; 142 final double a = 0.5 * (fi + fnMi); 143 final double arg = i * piOverN; 144 final double b = Math.sin(arg) * (fi - fnMi); 145 final double c = Math.cos(arg) * (fi - fnMi); 146 x[i] = a - b; 147 x[nMi] = a + b; 148 t1 += c; 149 } 150 final FastFourierTransform transformer = new FastFourierTransform(FastFourierTransform.Norm.STD, 151 false); 152 final Complex[] y = transformer.apply(x); 153 154 // reconstruct the FCT result for the original array 155 transformed[0] = y[0].getReal(); 156 transformed[1] = t1; 157 for (int i = 1; i < nShifted; i++) { 158 final int i2 = 2 * i; 159 transformed[i2] = y[i].getReal(); 160 transformed[i2 + 1] = transformed[i2 - 1] - y[i].getImaginary(); 161 } 162 transformed[n] = y[nShifted].getReal(); 163 164 return transformed; 165 } 166 167 /** 168 * Factory method. 169 * 170 * @param normalization Normalization to be applied to the 171 * transformed data. 172 * @param inverse Whether to perform the inverse transform. 173 * @return the transform operator. 174 */ 175 private UnaryOperator<double[]> create(final Norm normalization, 176 final boolean inverse) { 177 if (inverse) { 178 return normalization == Norm.ORTHO ? 179 f -> TransformUtils.scaleInPlace(fct(f), Math.sqrt(2d / (f.length - 1))) : 180 f -> TransformUtils.scaleInPlace(fct(f), 2d / (f.length - 1)); 181 } else { 182 return normalization == Norm.ORTHO ? 183 f -> TransformUtils.scaleInPlace(fct(f), Math.sqrt(2d / (f.length - 1))) : 184 f -> fct(f); 185 } 186 } 187 188 /** 189 * Normalization types. 190 */ 191 public enum Norm { 192 /** 193 * Should be passed to the constructor of {@link FastCosineTransform} 194 * to use the <em>standard</em> normalization convention. The standard 195 * DCT-I normalization convention is defined as follows 196 * <ul> 197 * <li>forward transform: 198 * y<sub>n</sub> = (1/2) [x<sub>0</sub> + (-1)<sup>n</sup>x<sub>N-1</sub>] 199 * + ∑<sub>k=1</sub><sup>N-2</sup> 200 * x<sub>k</sub> cos[π nk / (N - 1)],</li> 201 * <li>inverse transform: 202 * x<sub>k</sub> = [1 / (N - 1)] [y<sub>0</sub> 203 * + (-1)<sup>k</sup>y<sub>N-1</sub>] 204 * + [2 / (N - 1)] ∑<sub>n=1</sub><sup>N-2</sup> 205 * y<sub>n</sub> cos[π nk / (N - 1)],</li> 206 * </ul> 207 * where N is the size of the data sample. 208 */ 209 STD, 210 211 /** 212 * Should be passed to the constructor of {@link FastCosineTransform} 213 * to use the <em>orthogonal</em> normalization convention. The orthogonal 214 * DCT-I normalization convention is defined as follows 215 * <ul> 216 * <li>forward transform: 217 * y<sub>n</sub> = [2(N - 1)]<sup>-1/2</sup> [x<sub>0</sub> 218 * + (-1)<sup>n</sup>x<sub>N-1</sub>] 219 * + [2 / (N - 1)]<sup>1/2</sup> ∑<sub>k=1</sub><sup>N-2</sup> 220 * x<sub>k</sub> cos[π nk / (N - 1)],</li> 221 * <li>inverse transform: 222 * x<sub>k</sub> = [2(N - 1)]<sup>-1/2</sup> [y<sub>0</sub> 223 * + (-1)<sup>k</sup>y<sub>N-1</sub>] 224 * + [2 / (N - 1)]<sup>1/2</sup> ∑<sub>n=1</sub><sup>N-2</sup> 225 * y<sub>n</sub> cos[π nk / (N - 1)],</li> 226 * </ul> 227 * which makes the transform orthogonal. N is the size of the data sample. 228 */ 229 ORTHO; 230 } 231}