/*
* 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.FFT;
/**
* FourierAnalysis adds gutter points to complex-number 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 FourierAnalysis implements Data {
static final double PI2 = 2*Math.PI;
FFT fft = new FFT();
double[] fftData, omega, freqs;
double[] cosVec, sinVec, gutterVec;
ComplexDataset[] complexDatasets = new ComplexDataset[1];
Dataset[] realDatasets = new Dataset[2];
boolean radians = false;
private String name = "Fourier Analysis Complex Data"; //$NON-NLS-1$
protected int datasetID = hashCode();
/**
* Fourier analyzes the given complex data z[] after adding gutter points at the start and end of the z[] array.
*
* @param x double[]
* @param z double[]
* @param gutter int
* @return double[] the Fourier spectrum
*/
public double[] doAnalysis(double[] x, double[] z, int gutter) {
fftData = new double[z.length+4*gutter];
gutterVec = new double[2*gutter];
System.arraycopy(z, 0, fftData, 2*gutter, z.length);
fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
fft.toNaturalOrder(fftData);
double dx = x[1]-x[0];
double xmin = x[0]-gutter*dx;
double xmax = x[x.length-1]+(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 = fftData.length/(z.length);
//double norm=1;
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);
}
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;
}
/**
* Repeats the Fourier analysis of the complex data z[] with the previously set scale and gutter.
*
* @param z double[]
* @return double[] the Fourier spectrum
*/
public double[] repeatAnalysis(double[] z) {
if(fftData==null) {
int n = z.length;
double[] x = new double[n];
double x0 = 0, dx = 1.0/n;
for(int i = 0; i<n; i++) {
x[i] = x0;
x0 += dx;
}
return doAnalysis(x, z, 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(z, 0, fftData, gutterVec.length, z.length);
fft.transform(fftData); // Computes the FFT of data leaving the result in fft_pts.
fft.toNaturalOrder(fftData);
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 a list that contains the complex dataset of the last Fourier analysis.
* Complex dataset x-values are either frequencies (cycles) or angular frequencies (radians) depending
* on the value of the radians flag.
*
* @return list of ComplexDatasets
*/
public java.util.List<Data> getDataList() {
java.util.ArrayList<Data> list = new java.util.ArrayList<Data>();
if(fftData==null) {
return list;
}
if(complexDatasets[0]==null) {
complexDatasets[0] = new ComplexDataset();
complexDatasets[0].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
DisplayRes.getString("FourierAnalysis.Column.Real"), //$NON-NLS-1$
DisplayRes.getString("FourierAnalysis.Column.Imaginary")); //$NON-NLS-1$
} else {
complexDatasets[0].clear();
}
if(radians) {
complexDatasets[0].append(omega, fftData);
} else {
complexDatasets[0].append(freqs, fftData);
}
list.add(complexDatasets[0]);
return list;
}
/**
* Gets the complex datasets that contain the result of the last Fourier analysis.
* Real coefficients are contained in the first dataset.
* Complex coefficients are in the second dataset.
*
* @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("FourierAnalysis.Column.Real"), //$NON-NLS-1$
DisplayRes.getString("FourierAnalysis.RealCoefficients")); //$NON-NLS-1$
realDatasets[0].setLineColor(Color.RED);
realDatasets[1] = new Dataset();
realDatasets[1].setXYColumnNames(DisplayRes.getString("FourierAnalysis.Column.Frequency"), //$NON-NLS-1$
DisplayRes.getString("FourierAnalysis.Column.Imaginary"), //$NON-NLS-1$
DisplayRes.getString("FourierAnalysis.ImaginaryCoefficients")); //$NON-NLS-1$
realDatasets[1].setLineColor(Color.BLUE);
} else {
realDatasets[0].clear();
realDatasets[1].clear();
}
if(radians) {
for(int i = 0, nOmega = omega.length; i<nOmega; i++) {
double re = fftData[2*i], im = fftData[2*i+1];
realDatasets[0].append(omega[i], re);
realDatasets[1].append(omega[i], im);
}
} else {
for(int i = 0, nFreqs = freqs.length; i<nFreqs; i++) {
double re = fftData[2*i], im = fftData[2*i+1];
realDatasets[0].append(freqs[i], re);
realDatasets[1].append(freqs[i], im);
}
}
list.add(realDatasets[0]);
list.add(realDatasets[1]);
return list;
}
/**
* Gets the frequencies, real, and imaginary coefficients.
* @return double[][]
*/
public double[][] getData2D() {
if(fftData==null) {
return null;
}
double[][] data = new double[3][];
int n = fftData.length/2;
data[1] = new double[n];
data[2] = new double[n];
for(int i = 0; i<n; i++) {
double re = fftData[2*i], im = fftData[2*i+1];
data[1][i] = re;
data[2][i] = im;
}
if(radians) {
data[0] = omega;
} else {
data[0] = freqs;
}
return data;
}
/**
* 3D data is not available.
*
* @return double[][][]
*/
public double[][][] getData3D() {
return null;
}
/**
* Sets a name that can be used to identify the dataset.
*
* @param name String
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the dataset name.
*
* @return String
*/
public String getName() {
return name;
}
/**
* The column names to be used in the data display tool
* @return
*/
public String[] getColumnNames() {
return new String[] {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;
}
/**
* 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
*/