/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.analysis; import java.awt.Color; import org.opensourcephysics.display.ComplexDataset; import org.opensourcephysics.display.Data; import org.opensourcephysics.display.Dataset; import org.opensourcephysics.display.DisplayRes; import org.opensourcephysics.numerics.FFTReal; /** * FourierAnalysis adds gutter points to real data before performing a fast Fourier transform. * Gutter points increase the number points in order to approximate a nonperiodic function. * * The FFT output is phase shifted to account for the fact that the FFT basis functions are * defined on [0, 2*pi]. * * @author W. Christian * @version 1.0 */ public class FourierSinCosAnalysis implements Data { static final double PI2 = 2*Math.PI; FFTReal fft = new FFTReal(); double[] fftData, omega, freqs; private double[] cosVec, sinVec, gutterVec; ComplexDataset[] complexDatasets = new ComplexDataset[1]; Dataset[] realDatasets = new Dataset[3]; boolean radians = false; private String name = "Fourier Analysis Sin/Cos Data"; //$NON-NLS-1$ protected int datasetID = hashCode(); /** * Fourier analyzes the given data y[] after adding gutter points at the start and end of the z[] array. * * @param x double[] * @param y double[] * @param gutter int * @return double[] the Fourier spectrum */ public double[] doAnalysis(double[] x, double[] y, int gutter) { int offset = y.length%2; // zero if even number of points; one if odd number of points fftData = new double[y.length+2*gutter-offset]; gutterVec = new double[gutter]; System.arraycopy(y, 0, fftData, gutter, y.length-offset); fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts. double dx = x[1]-x[0]; double xmin = x[0]-gutter*dx; double xmax = x[x.length-1-offset]+(gutter+1)*dx; omega = fft.getNaturalOmega(xmin, xmax); freqs = fft.getNaturalFreq(xmin, xmax); cosVec = new double[omega.length]; sinVec = new double[omega.length]; double norm = 2.0/y.length; //double norm=2.0/fftData.length; for(int i = 0, nOmega = omega.length; i<nOmega; i++) { cosVec[i] = norm*Math.cos(omega[i]*xmin); sinVec[i] = norm*Math.sin(omega[i]*xmin); } cosVec[0] *= 0.5; // constant coefficient has factor of 1/2. sinVec[0] *= 0.5; for(int i = 0, nOmega = omega.length; i<nOmega; i++) { double re = fftData[2*i]; double im = fftData[2*i+1]; fftData[2*i] = re*cosVec[i]+im*sinVec[i]; // cos coefficient fftData[2*i+1] = -im*cosVec[i]+re*sinVec[i]; // sin coefficient } return fftData; } /** * Some elements (a Group, for instance) do not contain data, but a list of subelements which do. * This method is used by Data displaying tools to create as many pages as needed. * @return A list of DataInformation elements, null if the element itself is a DataInformation */ public java.util.List<Data> getDataList() { return null; } /** * Repeats the Fourier analysis of the real data y[] with the previously set scale and gutter. * * @param y double[] * @return double[] the Fourier sin/cos coefficients */ public double[] repeatAnalysis(double[] y) { int offset = y.length%2; // zero if even number of points; one if odd number of points if(fftData==null) { int n = y.length-offset; double[] x = new double[n]; double x0 = 0, dx = 1.0/n; for(int i = 0; i<n; i++) { x[i] = x0; x0 += dx; } doAnalysis(x, y, 0); } System.arraycopy(gutterVec, 0, fftData, 0, gutterVec.length); // zero the left gutter System.arraycopy(gutterVec, 0, fftData, fftData.length-1-gutterVec.length, gutterVec.length); // zero the right gutter System.arraycopy(y, 0, fftData, gutterVec.length, y.length-offset); fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts. for(int i = 0, nOmega = omega.length; i<nOmega; i++) { double re = fftData[2*i]; double im = fftData[2*i+1]; fftData[2*i] = re*cosVec[i]+im*sinVec[i]; fftData[2*i+1] = im*cosVec[i]-re*sinVec[i]; } return fftData; } /** * Gets the angular frequencies of the Fourier spectrum. * @return double[] */ public double[] getNaturalOmega() { return omega; } /** * Gets the frequencies of the Fourier spectrum. * @return double[] */ public double[] getNaturalFreq() { return freqs; } /** * Sets the radians flag for the frequency values of datasets. * Dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending * on the value of the radians flag. * * @param radians boolean */ public void useRadians(boolean radians) { this.radians = radians; } /** * Gets the radians flag. * Radians is true if the dataset uses angular frequency as the x-coordinate. * * @return boolean */ public boolean isRadians() { return radians; } /** * Gets the datasets that contain the result of the last Fourier analysis. * The power spectrum is contained in the first dataset. * Sine coefficients are contained in the second dataset. * Cosine coefficients are in the third dataset. * * Dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending * on the value of the radians flag. * * @return list of Datasets */ public java.util.ArrayList<Dataset> getDatasets() { java.util.ArrayList<Dataset> list = new java.util.ArrayList<Dataset>(); if(fftData==null) { return list; } if(realDatasets[0]==null) { realDatasets[0] = new Dataset(); realDatasets[0].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.Column.Power"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.PowerSpectrum")); //$NON-NLS-1$ realDatasets[0].setLineColor(Color.GREEN.darker()); realDatasets[0].setMarkerColor(Color.GREEN.darker()); realDatasets[0].setMarkerShape(Dataset.BAR); realDatasets[0].setMarkerSize(4); realDatasets[1] = new Dataset(); realDatasets[1].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.Column.Cosine"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.CosineCoefficients")); //$NON-NLS-1$ realDatasets[1].setLineColor(Color.CYAN.darker()); realDatasets[1].setMarkerColor(Color.CYAN.darker()); realDatasets[1].setMarkerShape(Dataset.BAR); realDatasets[1].setMarkerSize(4); realDatasets[2] = new Dataset(); realDatasets[2].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.Column.Sine"), //$NON-NLS-1$ DisplayRes.getString("FourierSinCosAnalysis.SineCoefficients")); //$NON-NLS-1$ realDatasets[2].setLineColor(Color.BLUE.darker()); realDatasets[2].setMarkerColor(Color.BLUE.darker()); realDatasets[2].setMarkerShape(Dataset.BAR); realDatasets[2].setMarkerSize(4); } else { realDatasets[0].clear(); realDatasets[1].clear(); realDatasets[2].clear(); } if(radians) { for(int i = 0, nOmega = omega.length; i<nOmega; i++) { double cos = fftData[2*i], sin = fftData[2*i+1]; realDatasets[0].append(omega[i], sin*sin+cos*cos); realDatasets[1].append(omega[i], sin); realDatasets[2].append(omega[i], cos); } } else { for(int i = 0, nFreqs = freqs.length; i<nFreqs; i++) { double sin = fftData[2*i], cos = fftData[2*i+1]; realDatasets[0].append(freqs[i], sin*sin+cos*cos); realDatasets[1].append(freqs[i], sin); realDatasets[2].append(freqs[i], cos); } } list.add(realDatasets[0]); list.add(realDatasets[1]); list.add(realDatasets[2]); return list; } /** * Sets a name that can be used to identify the dataset. * * @param name String */ public void setName(String name) { this.name = name; } /** * The column names to be used in the data display tool * @return */ public String[] getColumnNames() { return new String[] {name}; } /** * Gets the dataset name. * * @return String */ public String getName() { return name; } /** * Line colors for Data interface. * @return */ public java.awt.Color[] getLineColors() { return null; } /** * Fill colors for Data interface. * @return */ public java.awt.Color[] getFillColors() { return null; } /** * Gets the frequencies, power, cos, and sin coefficients. * @return double[][] */ public double[][] getData2D() { if(fftData==null) { return null; } double[][] data = new double[4][]; int n = fftData.length/2; data[1] = new double[n]; data[2] = new double[n]; data[3] = new double[n]; for(int i = 0; i<n; i++) { double cos = fftData[2*i], sin = fftData[2*i+1]; data[1][i] = sin*sin+cos*cos; data[2][i] = cos; data[3][i] = sin; } if(radians) { data[0] = omega; } else { data[0] = freqs; } return data; } /** * 3D data is not available. * * @return double[][][] */ public double[][][] getData3D() { return null; } /** * Sets the ID number of this Data. * * @param id the ID number */ public void setID(int id) { datasetID = id; } /** * Returns a unique identifier for this Data. * * @return the ID number */ public int getID() { return datasetID; } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */