// Created with MonoDevelop
//
//    Copyright (C) 2008 Hiperscan GmbH Dresden, Germany
//
//    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 3 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, see <http://www.gnu.org/licenses/>.



using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using System.Globalization;
using System.Linq;
using System.Drawing;
using System.Threading.Tasks;

using MathNet.Numerics;
using MathNet.Numerics.LinearAlgebra.Double;
using MathNet.Numerics.Interpolation;
using MathNet.Numerics.IntegralTransforms;

using Hiperscan.Unix;
using System.Numerics;

namespace Hiperscan.Spectroscopy
{
	
	public enum DataType
	{
		Intensity,
		Absorbance,
		Background,
		Correction,
		Preview,
		Reflectance
	}
	
	public enum InterpolationType
	{
		Linear,
		NaturalCubic
	}
	
	[Serializable()]
	public partial class DataSet : System.Runtime.Serialization.ISerializable
	{

        private List<double> x;
		private List<double> y;
		
		private List<int> gaps;
		
		private DataType data_type;

		private bool is_continuous       = true;
		private bool is_drop_point       = false;
		private bool is_visible          = true;
		private bool is_background       = false;
		private bool is_region           = false;
		private bool? is_1nm_equidistant = null;
		
		private string id;
		private string label  = string.Empty;
		private string xlabel = string.Empty;
		private string ylabel = string.Empty;
		
		private double y_min = double.NaN;
		private double y_max = double.NaN;
		private double region_fill_limit = 0f;
		
		private Guid guid;
		
		private Color color = Color.Empty;
		private float line_width = 2f;
		private LineStyle   line_style   = LineStyle.Solid;
		private MarkerStyle marker_style = MarkerStyle.Cross1;
		private int z_order = int.MaxValue;
		
		private static int instances;


		internal DataSet(int capacity)
		{
			this.guid = Guid.NewGuid();

			if (capacity < 0)
			{
				this.x = new List<double>();
				this.y = new List<double>();
			}
			else
			{
				this.x = new List<double>(capacity);
				this.y = new List<double>(capacity);
			}
			
			this.gaps = new List<int>();
			
			this.data_type = DataType.Intensity;
			
			this.id = "data_set_" + DataSet.instances.ToString();
			++DataSet.instances;
		}
		
		internal DataSet() : this(-1)
		{
		}

		public DataSet(double[] x, double[] y) : this(x.Length)
		{
			if (x.Length != y.Length)
				throw new System.ArgumentException(Catalog.GetString("Arrays in a data set must have the same length."));

			if (x.Length < 2)
				throw new System.ArgumentException(Catalog.GetString("Arrays in a data set must have a length of at least 2."));
			
			this.x = new List<double>(x);
			this.y = new List<double>(y);
		}
		
		public DataSet(List<double> x, List<double> y) : this(x.Count)
		{
			if (x.Count != y.Count)
				throw new System.ArgumentException(Catalog.GetString("Lists in a data set must have the same element count."));

			if (x.Count < 2)
				throw new System.ArgumentException(Catalog.GetString("Lists in a data set must have an element count of at least 2."));

			this.x = new List<double>(x);
			this.y = new List<double>(y);
		}
		
		public DataSet(List<double> x, double y) : this(x.Count)
		{
			if (x.Count < 2)
				throw new System.ArgumentException(Catalog.GetString("Lists in a data set must have an element count of at least 2."));

			this.x = new List<double>(x);
			
			this.y = new List<double>(x.Count);			
			for (int ix=0; ix < x.Count; ++ix)
				this.y.Add(y);
		}
		
		public DataSet(double[] x, double y) : this(new List<double>(x), y)
		{
		}
		
		public DataSet(DataSet ds) : this(0)
		{
			this.x = new List<double>(ds.X);
			this.y = new List<double>(ds.Y);
			this.gaps = new List<int>(ds.Gaps);
		}
		
		private DataSet(SerializationInfo info, StreamingContext context) : this(0)
		{
			this.x = (List<double>)info.GetValue("X", typeof(List<double>));
			this.y = (List<double>)info.GetValue("Y", typeof(List<double>));
			
			this.id    = (string)info.GetValue("Id", typeof(string));
			this.label = (string)info.GetValue("Label", typeof(string));
			
			this.gaps = (List<int>)info.GetValue("Gaps", typeof(List<int>));
		}
		
		public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
		{
			info.AddValue("X",     this.x);
			info.AddValue("Y",     this.y);
			info.AddValue("Id",    this.id);
			info.AddValue("Label", this.label);
			info.AddValue("Gaps",  this.gaps);
		}
		
//		public DataSet(double[] x) : this()
//		{
//			this.x = new List<double>(x);
//			this.y = new List<double>(x);
//		}
//		
//		public DataSet(List<double> x) : this()
//		{
//			this.x = new List<double>(x);
//			this.y = new List<double>(x);
//		}
		
//		protected virtual void Dispose(bool disposing)
//		{
//			if (!this.is_disposed)
//			{
//				if (disposing)
//				{
//					this.x.Clear();
//					this.y.Clear();
//					this.x = null;
//					this.y = null;
//				}
//			}
//			this.is_disposed = true;
//		}
//		
//		public void Dispose()
//		{
//			this.Dispose(true);
//			GC.SuppressFinalize(this);
//		}
//		
//		~DataSet()
//		{
//			this.Dispose(false);
//		}
		
		public DataSet Filter(int window_size)
		{
			// window size has to be odd
			if (window_size % 2 == 0)
				++window_size;
			
			if (window_size > this.Count)
				throw new ArgumentException(Catalog.GetString("Window size must not be greater than element count.")); 

			DataSet ds = new DataSet(this.Count);
			
			// apply a rectangular window function (moving average)
			for (int ix=0; ix < this.Count; ++ix)
			{
				double yfi = 0;
				if (ix < window_size/2 || ix >= this.Count-window_size/2-1)
					yfi = this.Y[ix];
				else
				{
					for (int jx=-window_size/2; jx <= window_size/2; ++jx)
						yfi += this.Y[ix+jx];
				}
				
				ds.Add( this.X[ix], yfi/window_size );
			}
			
			return ds;
		}
		
		public DataSet FirLowpassFilter(double[] coeffs)
		{
			int N = coeffs.Length;

			// add values at beginnig and end of ds
			List<double> x = new List<double>(N);
			List<double> y = new List<double>(N);
			for (int ix=0; ix < N; ++ix)
			{
				x.Add(this.X[this.Count-1] + (double)ix + 1.0);
				y.Add(this.Y[this.Y.Count-1]);
			}
			DataSet after = new DataSet(x, y);

			DataSet ds;
			int count = this.Count;
			if (this.Is1nmEquidistant)
			{
				ds = new DataSet(this);
			}
			else
			{
				int ceil = (int)Math.Ceiling(this.X[this.Count-1]);
				List<double> xe = new List<double>(ceil+1);
				for (int xi = (int)Math.Floor(this.X[0]); xi <= ceil; ++xi)
				{
					xe.Add(xi);
				}
				ds = this.Interpolate(xe, InterpolationType.Linear);
				count = xe.Count;
			}
			
			ds.AddRange(after);
			
			ds = ds.Filter(coeffs);
			ds = ds - new DataSet.Operand((double)((N-1)/2), 0.0);
			ds = ds.GetRange((N-1)/2, count);

			return ds;
		}
		
		public DataSet FirBandpassFilter()
		{
			double[] coeffs = DataSet.GetFirBandpassCoefficients();
			int N = coeffs.Length;

			// add zeros at beginnig and end of ds
			List<double> x = new List<double>(N);
			List<double> y = new List<double>(N);
			for (int ix=0; ix < N; ++ix)
			{
				x.Add(this.X[this.Count-1] + (double)ix + 1f);
				y.Add(this.Y[this.Y.Count-1]);
			}
			DataSet after = new DataSet(x, y);
			
			DataSet ds = new DataSet(this);
			ds.AddRange(after);
			
			ds = ds.Filter(coeffs);
			ds = ds - new DataSet.Operand((double)((N-1)/2), 0f);
			ds = ds.GetRange((N-1)/2, this.Count);

			return ds;
		}
		
		public DataSet Filter(double[] b)
		{
			DataSet ds = new DataSet(this.Count);
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				double yfi = 0f;
				for (int jx=0; jx < b.Length; ++jx)
				{
					if (ix-jx < 0)
						yfi += this.Y[0] * b[jx];
					else
						yfi += this.Y[ix-jx] * b[jx];
				}
				
				ds.Add(this.X[ix], yfi);
			}
			
			return ds;
		}
		
		public DataSet Filter(int order, int window_size)
		{
			// window size has to be odd
			if (window_size % 2 == 0)
				++window_size;

			if (window_size > this.Count)
				throw new ArgumentException(Catalog.GetString("Window size must not be greater than element count.")); 

			Matrix FIR = SavitzkyGolay.FIR(order, window_size);
			DataSet ds = new DataSet((window_size+1)/2-1);

			// transient on
			for (int ix=0; ix < (window_size+1)/2-1; ++ix)
			{
				double yfi = 0f;
				for (int jx=0; jx < window_size; ++jx)
				{
					yfi += this.Y[jx] * FIR[window_size-ix-1, window_size-jx-1];
				}
				
				ds.Add(this.X[ix], yfi);
			}
			
			// steady state
            double[] b  = FIR.Row((window_size+1)/2-1).ToArray();
			DataSet ds_fi = this.Filter(b);
			ds_fi.RemoveRange(0, window_size-1);
			ds_fi = ds_fi - new Operand((double)((window_size+1)/2-1), 0f);
			ds.AddRange(ds_fi);
			
			// transient off
			int lx = 0;
			for (int ix=this.Count-(window_size+1)/2+1; ix < this.Count; ++ix)
			{
				double yfi = 0f;
				for (int jx=0; jx < window_size; ++jx)
				{
					yfi += this.Y[this.Count-jx-1] * FIR[(window_size+1)/2-2-lx, jx];
				}
				++lx;
				
				ds.Add(this.X[ix], yfi);
			}

			return ds;
		}

		public DataSet Differences()
		{
			DataSet ds = new DataSet(this.Count-1);
			
			for (int ix=0; ix < this.Count-1; ++ix)
			{
				ds.Add( this.X[ix], this.Y[ix+1] - this.Y[ix] );
			}
			
			return ds;
		}
		
		public DataSet Derivative(int polynomial_order, int window_size, int diff_order)
		{
			// window size has to be odd
			if (window_size % 2 == 0)
				++window_size;
			
			if (window_size > this.Count)
				throw new ArgumentException(Catalog.GetString("Window size must not be greater than element count."));
			
			Matrix SG = SavitzkyGolay.SG(polynomial_order, window_size);

			if (diff_order > SG.ColumnCount)
				throw new ArgumentException(Catalog.GetString("Derivative order must not be greater than polynomial order."));

			DataSet ds = new DataSet(this.Count-window_size+1);
			
			double dx = this.X[1] - this.X[0];
			
			for (int ix=0; ix < this.Count-window_size+1; ++ix)
			{
                Vector v = new DenseVector(window_size);
				for (int jx=0; jx < window_size; ++jx)
				{
					v[jx] = this.Y[ix+jx];
				}
				
                double difference = SG.Column(diff_order) * v;
                double derivative = SpecialFunctions.Factorial(diff_order) * difference / Math.Pow(dx, (double)diff_order);
				
				ds.Add(this.X[ix+(window_size+1)/2-1], derivative);
			}
			
			return ds;
		}
		
		public static DataSet Sign(DataSet ds)
		{
			List<double> y = new List<double>(ds.Count);
			
			for (int ix=0; ix < ds.Count; ++ix)
				y.Add(ds.Y[ix] >= 0 ? 1 : -1);
			
			return new DataSet(new List<double>(ds.X), y);
		}
		
		public static DataSet Ln(DataSet ds)
		{
			List<double> y = new List<double>(ds.Count);
			
			for (int ix=0; ix < ds.Count; ++ix)
				y.Add( Math.Log(ds.Y[ix]) );
			
			return new DataSet(new List<double>(ds.X), y);			
		}
		
		public static DataSet Log(DataSet ds)
		{
			List<double> y = new List<double>(ds.Count);
			
			for (int ix=0; ix < ds.Count; ++ix)
				y.Add( Math.Log10(ds.Y[ix]) );
			
			return new DataSet(new List<double>(ds.X), y);			
		}
		
		public static DataSet Pow(double basis, DataSet ds)
		{
			List<double> y = new List<double>(ds.Count);
			
			for (int ix=0; ix < ds.Count; ++ix)
				y.Add(Math.Pow(basis, ds.Y[ix]));
			
			return new DataSet(new List<double>(ds.X), y);			
		}
		
		public static DataSet Pow(DataSet ds, double exponent)
		{
			List<double> y = new List<double>(ds.Count);
			
			for (int ix=0; ix < ds.Count; ++ix)
				y.Add(Math.Pow(ds.Y[ix], exponent));
			
			return new DataSet(new List<double>(ds.X), y);			
		}
		
		public static double AbsorbanceLimit(DataSet background, DarkIntensity di)
		{
			return Math.Log10(background.YMax() / (3*di.Sigma));
		}
		
		public static DataSet Absorbance(DataSet intensity, DataSet background, DarkIntensity di)
		{
			DataSet ratio = background.Interpolate(intensity.X) / intensity;
			double max_absorbance = DataSet.AbsorbanceLimit(background, di);
			double max_ratio = Math.Pow(10.0, max_absorbance);
			List<int> indices = ratio.Find('y', '<', double.Epsilon);
			indices.AddRange(ratio.Find('y', '>', max_ratio));
			
			foreach (int ix in indices)
			{
				ratio.Y[ix] = max_ratio;
			}
			DataSet ds  = DataSet.Log(ratio);
			ds.DataType = DataType.Absorbance;
			
			return ds;
		}

		public static DataSet Reflectance(DataSet intensity, DataSet background)
		{
			DataSet ds = intensity / background.Interpolate(intensity.X);
			ds.DataType = DataType.Reflectance;

			return ds;
		}
		
		public static DataSet InverseAbsorbance(DataSet intensity, DataSet absorbance)
		{
			DataSet ds = DataSet.Pow(10f, absorbance) * intensity;
			ds.DataType = DataType.Background;
			
			return ds;
		}
		
		public void YShift(int shift)
		{
			List<double> shifted_y = new List<double>(this.Count);
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				int jx = ix - shift;
				
				while (jx < 0)
					jx += this.Count;
				
				jx %= this.Count;

				shifted_y.Add(this.Y[jx]);
			}
			
			this.y = shifted_y;
		}

		public double XMedian()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine x median value: Not enough data in set."));

			List<double> x = new List<double>(this.X);
			x.Sort();
			return (x[0] + x[x.Count-1]) / 2;
		}

		private double __xmin = double.NaN;
		public double XMin()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine x minimum value: Not enough data in set."));

			if (double.IsNaN(this.__xmin))
			{
				List<double> x = new List<double>(this.X);
				x.Sort();
				this.__xmin = x[0];
				this.__xmax = x[x.Count-1];
			}

			return this.__xmin;
		}
		
        public static double Mean(IEnumerable<double> X)
		{
			double sum = 0.0;
			
			foreach (double x in X)
			{
				if (double.IsNaN(x))
					continue;
				
				sum += x;
			}
            return sum / (double)X.Count();
		}

        public static DataSet Mean(IEnumerable<DataSet> data_sets)
		{
            List<DataSet> dss = data_sets.ToList();

            if (dss == null || dss.Count < 1)
				throw new ArgumentException(Catalog.GetString("Cannot calculate mean data set for an empty list."));

			DataSet mean_ds = dss[0];
            for (int ix=1; ix < dss.Count; ++ix)
			{
                mean_ds += dss[ix].Interpolate(mean_ds.X);
			}

			return mean_ds / dss.Count;
		}
		
		public static double Median(List<double> X)
		{
			List<double> x_sort = new List<double>(X);
			x_sort.Sort();
			int count = x_sort.Count;
			
			if (count%2 == 1)
				return x_sort[(count-1)/2];
			else
				return (x_sort[count/2-1] + x_sort[count/2]) / 2.0;
		}
		
		public double XMean()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine x mean value: Not enough data in set."));

			return DataSet.Mean(this.X);
		}
		
		public double YMean()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y mean value: Not enough data in set."));

			return DataSet.Mean(this.Y);
		}
		
		public double YMedian()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y median value: Not enough data in set."));

			return DataSet.Median(this.Y);
		}
		
		public static double Rms(List<double> X)
		{
			double sum = 0f;
			
			foreach (double x in X)
			{
				if (double.IsNaN(x))
					continue;
				
				sum += Math.Pow(x, 2f);
			}
			return Math.Sqrt( sum / (double)X.Count );
		}
		
		public double YRms()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y RMS value: Not enough data in set."));

			return DataSet.Rms(this.Y);
		}
		
        public static double Sigma(IEnumerable<double> X)
		{
			double xmean = DataSet.Mean(X);
            double N     = (double)X.Count();
			double sum   = 0f;
			
			foreach (double x in X)
			{
				if (double.IsNaN(x))
					continue;

				sum += Math.Pow( (x - xmean), 2f);
			}
			
			return Math.Sqrt(sum/N);
		}
		
		public double XSigma()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine x sigma value: Not enough data in set."));

			return DataSet.Sigma(this.X);
		}

		public double YSigma()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y sigma value: Not enough data in set."));

			return DataSet.Sigma(this.Y);
		}
		
		public void SetYMin(double y_min)
		{
			this.y_min = y_min;
		}
		
		public void SetYMax(double y_max)
		{
			this.y_max = y_max;
		}

		private double __ymin = double.NaN;
		public double YMin()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y minimum value: Not enough data in set."));

			if (double.IsNaN(this.__ymin))
			{
				List<double> y = new List<double>(this.Y);
				y.Sort();
				for (int ix=0; ix < y.Count; ++ix)
				{
					if (double.IsNaN(y[ix]) || double.IsNegativeInfinity(y[ix]))
						continue;
					this.__ymin = y[ix];
					break;
				}
			}

			if (double.IsNaN(this.__ymin))
				throw new NotFiniteNumberException(Catalog.GetString("Y-data contains non finite values only."));

			if (double.IsNaN(this.y_min))
				return this.__ymin;

			return (this.y_min < this.__ymin) ? this.y_min : this.__ymin;
		}
				
		private double __xmax = double.NaN;
		public double XMax()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine x maximum value: Not enough data in set."));

			if (double.IsNaN(this.__xmax))
			{
				List<double> x = new List<double>(this.X);
				x.Sort();
				this.__xmin = x[0];
				this.__xmax = x[x.Count-1];
			}
			return this.__xmax;
		}
				
		private double __ymax = double.NaN;
		public double YMax()
		{
			if (!this.IsValid)
				throw new Exception(Catalog.GetString("Cannot determine y minimum value: Not enough data in set."));

			if (double.IsNaN(this.__ymax))
			{
				List<double> y = new List<double>(this.Y);
				y.Sort();
				this.__ymax = y[y.Count-1];
			}

			if (double.IsNaN(this.__ymax))
				throw new NotFiniteNumberException(Catalog.GetString("Y-data contains NaN values only"));

			if (double.IsNaN(this.y_max))
				return this.__ymax;

			return (this.y_max > this.__ymax) ? this.y_max : this.__ymax;
		}
				
		public static DataSet Abs(DataSet ds)
		{
			return new DataSet(new List<double>(ds.X), DataSet.Abs(ds.Y));
		}

		public static List<double> Abs(List<double> list)
		{
			List<double> y = new List<double>(list.Count);

			for (int ix=0; ix < list.Count; ++ix)
				y.Add( Math.Abs(list[ix]) );

			return y;
		}

		public double Interpolate(double xi)
		{
			DataSet ds = this.Interpolate(new double[] { xi });
			return ds.Y[0];
		}
		
		public DataSet Interpolate(double[] xi)
		{
			return this.Interpolate(xi, InterpolationType.Linear);
		}
		
		public DataSet Interpolate(List<double> xi)
		{
			return this.Interpolate(xi, InterpolationType.Linear);
		}
		
		public DataSet Interpolate(double[] xi, InterpolationType type)
		{
            IInterpolation data;
			//IInterpolationMethod data;
			
			switch (type)
			{
				
			case InterpolationType.Linear:
                data = LinearSpline.Interpolate(this.X, this.Y);
				break;
				
			case InterpolationType.NaturalCubic:
                data = CubicSpline.InterpolateNatural(this.X, this.Y);
				break;
				
			default:
				throw new Exception(Catalog.GetString("Unknown interpolation type:") +
				                    " " + Enum.GetName(typeof(InterpolationType), type));
				
			}
				
			DataSet ds = new DataSet(xi.Length);
			foreach (double x in xi)
			{
				ds.Add(x, data.Interpolate(x));
			}
			
			return ds;
		}

		public DataSet Interpolate(List<double> xi, InterpolationType type)
		{
			return Interpolate(xi.ToArray(), type);
		}
		
		public DataSet ApplyLUT(DataSet LUT)
		{
            IInterpolation data = LinearSpline.Interpolate(LUT.X, LUT.Y);
			DataSet ds = new DataSet(this.Count);
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				ds.Add( data.Interpolate(this.X[ix]), this.Y[ix] );
			}
			return ds;
		}
		
		public double InterpolateMinimum(double a0, double b0, double eps)
		{
			double tau = 0.618;
			double   ai = a0;
			double   bi = b0;
			double[] p  = new double[2];
			p[0] = ai + (1-tau) * (bi-ai);
			p[1] = ai + tau * (bi-ai);
			
			for (int ix=0; true; ++ix)
			{
				if (ix > 100)
					throw new OperationCanceledException(string.Format(Catalog.GetString("The maximum number of iterations " +
					                                                                     "({0}) has been reached."),
					                                  100)
					                    );
				
				DataSet f = this.Interpolate(p, InterpolationType.NaturalCubic);
				
				if (f.Y[0] < f.Y[1])
				{
					bi   = p[1];
					p[1] = p[0];
					p[0] = ai + (1-tau) * (bi-ai);
				}
				else
				{
					ai   = p[0];
					p[0] = p[1];
					p[1] = ai + tau * (bi-ai);
				}
				
				if (bi-ai < eps)
					break;
			}
			
			return ai;
		}
		
		public List<int> Find(char xy, char op, double val)
		{
			List<int> indices = new List<int>();

			List<double> operand;
			
			switch (xy)
			{
			case 'x':
			case 'X':
				 operand = this.X;
				break;
				
			case 'y':
			case 'Y':
				operand = this.Y;
				break;
				
			default:
				throw new ArgumentException(Catalog.GetString("Unknown operand:") + " '" + xy + "'");
			}
			
			switch (op)
			{
			case '=':
				// find indices at operand crossing val
				for (int ix=1; ix < this.Count; ++ix)
				{
					if ( (operand[ix-1]-val) * (operand[ix]-val) < 0)
						indices.Add(ix);
				}
				break;
				
			case '<':
				// find indices for operand < val
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] < val)
						indices.Add(ix);
				}
				break;
				
			case '>':
				// find indices for operand > val
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] > val)
						indices.Add(ix);
				}
				break;
				
			default:
				throw new ArgumentException(Catalog.GetString("Unknown operator:") + " '" + op + "'");
			}
			
			return indices;
		}
		
		public List<int> Find(char xy, char op1, double val1, char op2, double val2)
		{
			List<int> indices = new List<int>();

			List<double> operand;
			
			switch (xy)
			{
			case 'x':
			case 'X':
				 operand = this.X;
				break;
				
			case 'y':
			case 'Y':
				operand = this.Y;
				break;
				
			default:
				throw new ArgumentException(Catalog.GetString("Unknown operand:") + " '" + xy + "'");
			}
			
			switch (op1)
			{
				
			case '<':
				// find indices for operand < val1
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] < val1)
						indices.Add(ix);
				}
				break;
				
			case '>':
				// find indices for operand > val1
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] > val1)
						indices.Add(ix);
				}
				break;
				
			default:
				throw new ArgumentException(Catalog.GetString("Unknown operator:") + " '" + op1 + "'");
			}
			
			switch (op2)
			{
				
			case '<':
				// find indices for operand < val2
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] >= val2 && indices.Contains(ix))
						indices.Remove(ix);
				}
				break;
				
			case '>':
				// find indices for operand > val1
				for (int ix=0; ix < this.Count; ++ix)
				{
					if ( operand[ix] <= val2 && indices.Contains(ix))
						indices.Remove(ix);
				}
				break;
				
			default:
				throw new ArgumentException(Catalog.GetString("Unknown operator:") + " '" + op2 + "'");
			}
			
			return indices;
		}
		
		public List<int> FindIndices(DataSet ds)
		{
			List<int> indices = new List<int>();
			
			foreach (double x in ds.X)
				indices.Add(this.X.IndexOf(x));
			
			return indices;
		}
		
		public class Extrema
		{
			private DataSet minima;
			private DataSet maxima;
			
			internal Extrema(DataSet minima, DataSet maxima)
			{
				this.minima = minima;
				this.maxima = maxima;
			}
			
			public DataSet Minima {
				get {return minima;}
			}
			
			public DataSet Maxima {
				get {return maxima;}
			}
			
			public DataSet All()
			{
				DataSet ds = new DataSet(this.minima.Count);
				ds.IsContinuous = false;
				ds.AddRange(this.minima);
				ds.AddRange(this.maxima);
				ds.Sort();
				return ds;
			}
			
			public void RemoveRange(int index, int count)
			{
				List<double> xarray = this.All().X.GetRange(index, count);
				
				foreach (double x in xarray)
				{
					if (this.minima.X.Contains(x))
					{
						int ix = this.minima.X.IndexOf(x);
						this.minima.RemoveAt(ix);
					}
					
					if (this.maxima.X.Contains(x))
					{
						int ix = this.maxima.X.IndexOf(x);
						this.maxima.RemoveAt(ix);
					}
				}
			}
		}
				
        public DataSet.Extrema FindExtrema(int window_size, double noise_limit, double cutoff, int eps)
        {
            return this.FindExtrema(window_size, noise_limit, cutoff, eps, new LevenbergMarquardt.OptimParams());
        }

        public DataSet.Extrema FindExtrema(int window_size, double noise_limit, double cutoff, int eps, LevenbergMarquardt.OptimParams optim_params)
		{
			// create an instance with "smoother" data
			DataSet ds = this.Filter(window_size);

			// use differences as approximation for differentation
			DataSet ds_diff =  ds.Differences();
			
			// zero crossing indicates extremum
			List<int> indices = ds_diff.Find('Y', '=', 0);			
			DataSet   extrema = ds[indices];
			extrema.IsContinuous = false;			

			// delete extrema caused by noise
			DataSet   extrema_diff_abs = DataSet.Abs(extrema.Differences());
			List<int> indices_del      = extrema_diff_abs.Find('Y', '<', noise_limit);
			extrema.RemoveAt(indices_del);
			
			// delete extrema beyond cutoff threshold
			indices_del = extrema.Find('Y', '<', cutoff);
			extrema.RemoveAt(indices_del);
			
			// delete extrema too close to the beginning or end
			indices_del.Clear();
			for (int ix=0; ix < extrema.Count; ++ix)
			{
				int n = this.FindNearestIndex(extrema.X[ix]);
				if (n < 10 || n > this.Count-10)
					indices_del.Add(ix);
			}
			extrema.RemoveAt(indices_del);
			
			// determine wheter it's a minimum or maximum
			indices = ds.FindIndices(extrema);
			DataSet minima = new DataSet(100);
			minima.IsContinuous = false;
			DataSet maxima = new DataSet(100);
			maxima.IsContinuous = false;
			List<int> indices_min = new List<int>();
			List<int> indices_max = new List<int>();
			
			foreach (int ix in indices)
			{
				if ( Math.Sign(ds.Y[ix] - ds.Y[ix-1]) < 0 )
					indices_min.Add(ix);
				else
					indices_max.Add(ix);
			}

			// find interpolated extrema
			foreach (int ix in indices_min)
			{
				List<int> range = new List<int>();
				for (int jx=ix-eps; jx<=ix+eps; ++jx)
				{
					if (jx >= 0 && jx < this.Count)
						range.Add(jx);
				}
				
				DataSet subset = new DataSet(this[range]);
				double[] p;
                try
                {
                    p = LevenbergMarquardt.Fit(new ParabolicLMFit(subset, 1), optim_params).Coefficients;
                }
                catch (LevenbergMarquardt.MaxIterationNumberException)
                {
                    continue;
                }

				double x = p[2];
				double y = p[0] + p[1] * Math.Pow(x - p[2], 2); 
				
				minima.Add(x, y);
			}
//			Console.WriteLine("Interpolated " + minima.Count + " minima");
			
			foreach (int ix in indices_max)
			{
				List<int> range = new List<int>();
				for (int jx=ix-eps; jx<=ix+eps; ++jx)
				{
					if (jx >= 0 && jx < this.Count)
						range.Add(jx);
				}
				
				DataSet subset = new DataSet(this[range]);
				double[] p;
				//try
				//{
				//	p = LevenbergMarquardt.Fit(subset, LevenbergMarquardt.FitType.Parabolic, -1);
				//}
				//catch (LevenbergMarquardt.MaxIterationNumberException)
				//{
				//	continue;
				//}

                try
                {
                    p = LevenbergMarquardt.Fit(new ParabolicLMFit(subset, -1)).Coefficients;
                }
                catch (LevenbergMarquardt.MaxIterationNumberException)
                {
                    continue;
                }
				
				double x = p[2];
				double y = p[0] + p[1] * Math.Pow(x - p[2], 2); 
				
				maxima.Add(x, y);
			}
//			Console.WriteLine("Interpolated " + minima.Count + " maxima");

			return new DataSet.Extrema(minima, maxima);
		}

		public DataSet.Extrema FindExtremaSG(int sg_order, int sg_window, double noise_limit, double cutoff)
		{
			// create an instance with "smoother" data
			DataSet ds = this.Filter(sg_order, sg_window);
			
			// use differences as approximation for differentation
			DataSet ds_diff = ds.Differences();
			
			// zero crossing indicates extremum
			List<int> indices = ds_diff.Find('Y', '=', 0);			
			DataSet   extrema = ds[indices];
			extrema.IsContinuous = false;			

			// delete extrema caused by noise
			List<int> indices_del;
			if (extrema.Count > 2)
			{
				DataSet   extrema_diff_abs = DataSet.Abs(extrema.Differences());
				indices_del = extrema_diff_abs.Find('Y', '<', noise_limit);
				extrema.RemoveAt(indices_del);
			}
			
			// delete extrema beyond cutoff threshold
			indices_del = extrema.Find('Y', '<', cutoff);
			extrema.RemoveAt(indices_del);
			
			// determine wheter it's a minimum or maximum
			indices = ds.FindIndices(extrema);
			DataSet minima = new DataSet(100);
			minima.IsContinuous = false;
			DataSet maxima = new DataSet(100);
			maxima.IsContinuous = false;
			List<int> indices_min = new List<int>();
			List<int> indices_max = new List<int>();
			
			foreach (int ix in indices)
			{
				if ( Math.Sign(ds.Y[ix] - ds.Y[ix-1]) < 0 )
					indices_min.Add(ix);
				else
					indices_max.Add(ix);
			}

			// find interpolated extrema
            IInterpolation data = LinearSpline.Interpolate(ds.X, ds.Y);
			
			foreach (int ix in indices_min)
			{
				if (ix < 1 || ix > ds.Count-1)
					continue;
				
				double a0 = ds.X[ix-1];
				double b0 = ds.X[ix+1];
				
				double x = ds.InterpolateMinimum(a0, b0, 1e-3);
				double y = data.Interpolate(x);

				minima.Add(x, y);
			}
//			Console.WriteLine("Interpolated " + minima.Count + " minima");
			
			ds *= new DataSet.Operand(1f, -1f);
			foreach (int ix in indices_max)
			{
				if (ix < 1 || ix > ds.Count-1)
					continue;
				
				double a0 = ds.X[ix-1];
				double b0 = ds.X[ix+1];
				
				double x = ds.InterpolateMinimum(a0, b0, 1e-3);
				double y = data.Interpolate(x);

				maxima.Add(x, y);
			}
//			Console.WriteLine("Interpolated " + minima.Count + " maxima");

			return new DataSet.Extrema(minima, maxima);
			
		}
		
		public int FindNearestIndex(double x)
		{
			double min_dist = double.MaxValue;
			int min_dist_ix = 0;
			for (int ix=0; ix < this.Count; ++ix)
			{
				if (Math.Abs(this.X[ix] - x) < min_dist)
				{
					min_dist = Math.Abs(this.X[ix] - x);
					min_dist_ix = ix;
				}
			}
			return min_dist_ix;
		}
		
		public double[] FindNearest(double x)
		{
			int ix = this.FindNearestIndex(x);
			return new double[2] { this.X[ix], this.Y[ix] };
		}
		
		public void Add(double x, double y)
		{
			this.X.Add(x);
			this.Y.Add(y);
		}
		
		public void AddRange(DataSet ds)
		{
			this.X.AddRange(ds.X);
			this.Y.AddRange(ds.Y);
		}

		public void RemoveAt(int ix)
		{
			if (this.Count < 3)
				throw new System.Exception(Catalog.GetString("Cannot remove element if the resulting element count is less than 2."));
			
			this.X.RemoveAt(ix);
			this.Y.RemoveAt(ix);
		}
		
		public void RemoveAt(List<int> indices)
		{
			indices.Sort();
			indices.Reverse();
			foreach (int ix in indices)
			{
				this.RemoveAt(ix);
			}
		}
		
		public void RemoveRange(int ix, int count)
		{
			if ((this.Count - count) < 2)
				throw new System.Exception(Catalog.GetString("Cannot remove range if the resulting element count is less than 2."));
				
			this.X.RemoveRange(ix, count);
			this.Y.RemoveRange(ix, count);
		}
		
		public DataSet GetRange(int index, int count)
		{
			return new DataSet(this.X.GetRange(index, count), this.Y.GetRange(index, count));
		}

		public DataSet GetRange(double lower, double upper)
		{
			return this.GetRange(this.FindNearestIndex(lower), this.FindNearestIndex(upper)-this.FindNearestIndex(lower)+1);
		}

		public void Sort()
		{
			List<double> x = new List<double>(this.X);
			List<double> y = new List<double>(this.X.Count);
			
			x.Sort();
			foreach (double val in x)
			{
				int ix = this.X.IndexOf(val);
				y.Add(this.Y[ix]);
			}
			this.x = x;
			this.y = y;			
		}
		
		public DataSet SNV()
		{
			double mean  = this.YMean();
			double sigma = this.YSigma();

			return (this - new Operand(0f, mean)) / new Operand(1f, sigma);
		}

		public double Cov()
		{
			double xmean = this.XMean();
			double ymean = this.YMean();
			double sum   = 0f;
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				sum += (this.X[ix] - xmean) * (this.Y[ix] - ymean);
			}
			
			return sum / (double)this.Count;
		}
		
		public double Corr()
		{
			return this.Cov() / (this.XSigma() * this.YSigma());
		}
		
		public double Norm2()
		{
			double sum = 0f;
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				sum += Math.Pow( (this.X[ix] - this.Y[ix]), 2f);
			}
			
			return Math.Sqrt(sum);
		}

		public double Norm2(List<double> weights)
		{
			if (this.Count != weights.Count)
				throw new ArgumentException(Catalog.GetString("Cannot calculate weighted 2-Norm: Weighting vector has wrong dimension."));
			
			double sum = 0f;
			for (int ix=0; ix < this.Count; ++ix)
			{
				sum += Math.Pow( (this.X[ix] - this.Y[ix]) * weights[ix], 2f);
			}
			
			return Math.Sqrt(sum);
		}
		
		public struct FWHMResult
		{
			private double x_peak;
			private double y_peak;
			private double fwhm;
			
			public FWHMResult(double x_peak, double y_peak, double fwhm)
			{
				this.x_peak = x_peak;
				this.y_peak = y_peak;
				this.fwhm   = fwhm;
			}
			
			public double XPeak
			{
				get { return this.x_peak; }
			}
			
			public double YPeak
			{
				get { return this.y_peak; }
			}
			public double FWHM
			{
				get { return this.fwhm; }
			}
		}
		
		public FWHMResult FWHM()
		{
			Extrema extrema = this.FindExtremaSG(2, 5, 0.05, -10.0);
			
			if (extrema.Maxima.Count == 0)
				throw new Exception(Catalog.GetString("No peak found."));

			double x_peak = 0;
			double y_peak = double.MinValue;
			for (int ix=0; ix < extrema.Maxima.Count; ++ix)
			{
				if (extrema.Maxima.Y[ix] > y_peak)
				{
					x_peak = extrema.Maxima.X[ix];
					y_peak = extrema.Maxima.Y[ix];
				}
			}
			double y_median = this.YMedian();
			double limit = (y_peak - y_median)/2f + y_median;
			
			List<int> indices = this.Find('y', '=', limit);
			if (indices.Count < 2)
				throw new Exception(Catalog.GetString("No peak found."));
			
			DataSet cross = this[indices];

			// left hand side
			indices = cross.Find('x', '<', x_peak);
			if (indices.Count < 1)
				throw new Exception(Catalog.GetString("No peak found."));
			double x_left;
			if (indices.Count == 1)
				x_left = cross.X[indices[0]];
			else
				x_left = cross[indices].XMax();
			
			// right hand side
			indices = cross.Find('x', '>', x_peak);
			if (indices.Count < 1)
				throw new Exception(Catalog.GetString("No peak found."));
			double x_right;
			if (indices.Count == 1)
				x_right = cross.X[indices[0]];
			else
				x_right = cross[indices].XMin();
			
			// interpolate peak
			List<double> xi = new List<double>();
			for (double x=x_left-1.0; x < x_right+1.0; x += 0.05)
			{
				xi.Add(x);
			}
			DataSet interp = this.Interpolate(xi, InterpolationType.Linear);
			
			indices = interp.Find('y', '=', limit);
			if (indices.Count != 2)
				throw new Exception(Catalog.GetString("No peak found."));
			
			double fhwm = interp.X[indices[1]] - interp.X[indices[0]];
			
			Console.WriteLine("FHWM result:");
			Console.WriteLine("x_peak = " + x_peak);
			Console.WriteLine("y_peak = " + y_peak);
			Console.WriteLine("fwhm = " + fhwm);
			
			return new FWHMResult(x_peak, y_peak, fhwm);
		}
		
		public struct LinearRegressionResult
		{
			private double offset;
			private double slope;

			public LinearRegressionResult(double offset, double slope)
			{
				this.offset = offset;
				this.slope  = slope;
			}
			
			public double GetRegressionValue(double x)
			{
				return x * this.Slope + this.Offset;
			}
			
			public List<double> GetRegressionValues(List<double> x)
			{
				List<double> result = new List<double>(x.Count);
				foreach (double v in x)
				{
					result.Add(v * this.Slope + this.Offset);
				}
				return result;
			}
			
			public double Offset
			{
				get { return this.offset; }
			}
			
			public double Slope
			{
				get { return this.slope; }
			}

			public override string ToString()
			{
				return string.Format("[LinearRegressionResult: Offset={0}, Slope={1}]", Offset, Slope);
			}

			public string ToCsvString()
			{
				return string.Format(CultureInfo.InvariantCulture, "LinearRegressionResult:,Offset:,{0},Slope:,{1}", Offset, Slope);
			}
		}
		
		public LinearRegressionResult LinearRegression()
		{
			double xmean = this.XMean();
			double ymean = this.YMean();
			
			double divident = 0.0;
			double divisor  = 0.0;
			
			for (int ix = 0; ix < this.Count; ++ix)
			{
				divident += (this.X[ix] - xmean) * (this.Y[ix] - ymean);
				divisor  += Math.Pow(this.X[ix] - xmean, 2.0);
			}
			
			double slope  = divident / divisor;
			double offset = ymean - slope * xmean;
			
			return new LinearRegressionResult(offset, slope);
		}
		
		public DataSet LinearRegressionCurve(LinearRegressionResult regression)
		{
			List<double> x = new List<double>(this.X);
			List<double> y = new List<double>(this.X.Count);
			
			foreach (double val in x)
			{
				y.Add(regression.Offset + val * regression.Slope);
			}
			
			return new DataSet(x, y);
		}

		public DataSet LinearRegressionCurve()
		{
			return this.LinearRegressionCurve(this.LinearRegression());
		}
		
		public DataSet FFT()
		{
			double[] xi = new double[1024];
			
			double delta = (this.X[this.Count-1] - this.X[0]) / (xi.Length - 1);
			
			for (int ix = 0; ix < xi.Length; ++ix)
			{
				xi[ix] = this.X[0] + (double)ix * delta * 2.0;
			}
			
			DataSet ds = this.Interpolate(xi);
			
			List<int> indices = ds.Find('X', '>', this.X[this.Count-1]);
			foreach (int ix in indices)
			{
				ds.Y[ix] = 0.0;
			}
			
            double[] real = ds.Y.ToArray();
            double[] imag = new double[real.Length];
            Fourier.Forward(real, imag);
			
			for (int ix=0; ix < ds.Count; ++ix)
			{
                ds.Y[ix] = Math.Sqrt(Math.Pow(real[ix], 2.0) + Math.Pow(imag[ix], 2.0));
			}
			
			return ds;
		}
		
		public struct FFTResult
		{
			DataSet real, imag;
			DataSet amplitude, phase;
			
			public FFTResult(double[] real, double[] imag)
			{
				if (real.Length != imag.Length)
					throw new ArgumentException(Catalog.GetString("Real and Imag fields must have same length."));
				
				// FIXME: calculate x axis
				double[] omega = new double[real.Length];
				for (int ix=0; ix < omega.Length; ++ix)
				{
					omega[ix] = 1000 + (double)ix;
				}
				
				double[] amplitude = new double[real.Length];
				double[] phase     = new double[real.Length];
			
				for (int ix=0; ix < real.Length; ++ix)
				{
					Complex c = new Complex(real[ix], imag[ix]);
				
                    amplitude[ix] = c.Magnitude;
                    phase[ix]     = c.Phase;
				}
				
				this.real = new DataSet(omega, real);
				this.imag = new DataSet(omega, imag);
				
				this.amplitude = new DataSet(omega, amplitude);
				this.phase     = new DataSet(omega, phase);
			}
			
			public DataSet Real
			{
				get { return this.real; }
			}
			
			public DataSet Imag
			{
				get { return this.imag; }
			}
			
			public DataSet Amplitude
			{
				get { return this.amplitude; }
			}
			
			public DataSet Phase
			{
				get { return this.phase; }
			}
		}
		
		public FFTResult FFTShift()
		{
			double[] xi = new double[1024];
			
			double delta = (this.X[this.Count-1] - this.X[0]) / (xi.Length - 1);
			
			for (int ix=0; ix < xi.Length; ++ix)
			{
				xi[ix] = this.X[0] + (double)ix * delta;
			}
			
			DataSet ds = this.Interpolate(xi);
			double[] real = ds.Y.ToArray();
            double[] imag = new double[real.Length];

            Fourier.Forward(real, imag);

            return new FFTResult(real, imag);
		}
		
		public class AutoScaleParameter
		{
			public DataSet Means  { get; private set; }
			public DataSet Sigmas { get; private set; }
		
			public AutoScaleParameter(DataSet[] data_sets, List<double> wavelengths)
			{
				if (data_sets == null || data_sets.Length < 1)
					throw new ArgumentException(Catalog.GetString("Auto scale parameter must contain at least one data set."));
				
				List<double> means  = new List<double>(data_sets.Length);
				List<double> sigmas = new List<double>(data_sets.Length);
				
				List<List<double>> yis = new List<List<double>>(data_sets.Length);
				
				foreach (DataSet ds in data_sets)
				{
					yis.Add(ds.FirLowpassFilter(DataSet.GetDefaultSystemFunctionCorrectionCoefficients()).Interpolate(wavelengths).Y);
				}

				for (int ix=0; ix < yis[0].Count; ++ix)
				{
					List<double> yi = new List<double>(yis.Count);
					for (int jx=0; jx  < yis.Count; ++jx)
					{
						yi.Add(yis[jx][ix]);
					}
					DataSet ds = new DataSet(yi, yi);
					
					means.Add(ds.YMean());
					sigmas.Add(ds.YSigma());
				}
				
				this.Means  = new DataSet(wavelengths, means);
				this.Sigmas = new DataSet(wavelengths, sigmas);
			}
		}
		
		public DataSet AutoScale(AutoScaleParameter parameter)
		{
			List<double> y = new List<double>(this.Count);
			
			List<double> means  = parameter.Means.Interpolate(this.X).Y;
			List<double> sigmas = parameter.Sigmas.Interpolate(this.X).Y;
			
			for (int ix=0; ix < this.Count; ++ix)
			{
				y.Add((this.Y[ix] - means[ix]) / sigmas[ix]);
			}
			
			return new DataSet(this.X, y);
		}

		public class MscParameter
		{
			public DataSet Means { get; private set; }

			
			public MscParameter(List<double> wavelengths, List<double> means)
			{
				this.Means = new DataSet(wavelengths, means);
			}
			
			public MscParameter(DataSet[] data_sets)
			{
				if (data_sets == null || data_sets.Length < 1)
					throw new ArgumentException(Catalog.GetString("MSC parameter must contain at least one data set."));

				List<double> xi = data_sets[0].X;
				double[] means = new double[xi.Count];
				double N = (double)data_sets.Length;
				
				foreach (DataSet ds in data_sets)
				{
					DataSet dsi = ds.Interpolate(xi);
					for (int ix=0; ix < dsi.Count; ++ix)
					{
						means[ix] += dsi.Y[ix] / N;
					}
				}
				
				this.Means = new DataSet(xi.ToArray(), means);
			}

            public override int GetHashCode()
            {
                return this.Means.GetHashCode();
            }
		}
		
		public DataSet MSC(MscParameter parameter)
		{
			//double[] p = LevenbergMarquardt.Fit(this, parameter.Means, LevenbergMarquardt.FitType.MSC, 1.0);
            double[] p = LevenbergMarquardt.Fit(new MSCLMFit(this, parameter.Means, 1.0)).Coefficients;
			return (this  - p[0]) / p[1];
		}
		
		public DataSet Detrend()
		{
			//double[] p = LevenbergMarquardt.Fit(this, LevenbergMarquardt.FitType.Quadradic, 1.0);
            double[] p = LevenbergMarquardt.Fit(new QuadraticLMFit(this, 1.0)).Coefficients;
			List<double> y = new List<double>(this.X.Count);
			foreach (double x in this.X)
			{
				y.Add(p[0] + p[1]*x + p[2]*Math.Pow(x, 2.0));
			}
			return this - new DataSet(this.X, y);
		}
		
		public void CopyPropertiesFrom(Spectrum spectrum)
		{
			this.Label       = spectrum.Label;
			this.Id          = spectrum.Id;
			this.Color       = spectrum.Color;
			this.LineStyle   = spectrum.LineStyle;
			this.MarkerStyle = spectrum.MarkerStyle;
			this.LineWidth   = spectrum.LineWidth;
			this.IsVisible   = spectrum.IsVisible;
			this.ZOrder      = spectrum.ZOrder;
		}
		
		public void CopyPropertiesFrom(DataSet ds)
		{
			this.Label       = ds.Label;
			this.Id          = ds.Id;
			this.Color       = ds.Color;
			this.LineStyle   = ds.LineStyle;
			this.MarkerStyle = ds.MarkerStyle;
			this.LineWidth   = ds.LineWidth;
			this.IsVisible   = ds.IsVisible;
			this.ZOrder      = ds.ZOrder;
		}

		public override int GetHashCode()
		{
			int hash = 0;
			foreach (double val in this.X)
			{
				hash ^= val.GetHashCode();
			}
			foreach (double val in this.Y)
			{
				hash ^= val.GetHashCode();
			}
			return hash;
		}

        public override bool Equals(object obj)
        {
            DataSet ds = obj as DataSet;

            if (ds == null)
                return false;

            return this.x.SequenceEqual(ds.X) && this.y.SequenceEqual(ds.Y);
        }

        public List<double> X
		{
			get { return this.x; }
		}
		
		public List<double> Y
		{
			get { return this.y; }
		}
		
		public List<int> Gaps
		{
			get { return this.gaps;  }
			set { this.gaps = value; }
		}
		
		public int Count
		{
			get {return this.X.Count;}
		}
		
		public bool IsContinuous
		{
			get {return this.is_continuous;}
			set {this.is_continuous = value;}
		}
		
		public bool IsDropPoint
		{
			get {return !this.is_continuous && this.is_drop_point;}
			set {this.is_drop_point = value;}
		}
		
		public bool IsRegion
		{
			get { return this.is_continuous && this.is_region; }
			set { this.is_region = value; }
		}
		
		public double RegionFillLimit
		{
			get { return this.region_fill_limit;  }
			set { this.region_fill_limit = value; }
		}
		
		public bool IsVisible
		{
			get {return this.is_visible;}
			set {this.is_visible = value;}
		}
		
		public bool IsBackground
		{
			get { return this.is_background;  }
			set { this.is_background = value; }
		}
		
		public bool IsPreview
		{
			get { return this.Id == "9_preview"; }
		}
		
		public string Label
		{
			get {return this.label;}
			set {this.label = value;}
		}
		
		public string Id
		{
			get {return this.id;}
			set {this.id = value;}
		}
		
		public Color Color
		{
			get { return this.color;  }
			set { this.color = value; }
		}
		
		public float LineWidth
		{
			get { return this.line_width;  }
			set { this.line_width = value; }
		}
		
		public LineStyle LineStyle
		{
			get { return this.line_style;  }
			set { this.line_style = value; }
		}

		public MarkerStyle MarkerStyle
		{
			get { return this.marker_style;  }
			set { this.marker_style = value; }
		}
		
		public int ZOrder
		{
			get { return this.z_order;  }
			set { this.z_order = value; }
		}
		
		public string XLabel
		{
			get { return this.xlabel;  }
			set { this.xlabel = value; }
		}
		
		public string YLabel
		{
			get { return this.ylabel;  }
			set { this.ylabel = value; }
		}
		
		public DataType DataType
		{
			get { return this.data_type;  }
			set { this.data_type = value; }
		}
		
		public bool IsValid
		{
			get
			{
				if (this.Count < 2)
					return false;
				
				return true;
			}
		}
		
		public Guid Guid
		{
			get { return this.guid; }
		}
		
		public bool Is1nmEquidistant
		{
			get
			{
				if (this.is_1nm_equidistant == null)
				{
					this.is_1nm_equidistant = true;
					for (int ix = 0; ix < this.Count-1; ++ix)
					{
						if ((this.X[ix+1] - this.X[ix]) != 1.0)
						{
							Console.WriteLine("WARNING: DataSet is not 1nm-equidistant: " + this.Id + " (" + (this.X[ix+1]-this.X[ix]) + " != 1.0");
							this.is_1nm_equidistant = false;
							break;
						}
					}
				}
				
				return (bool)this.is_1nm_equidistant;
			}
		}

		// indexer for collections
		public DataSet this[List<int> indices]
		{
			get
			{
				DataSet ds = new DataSet(indices.Count);
				
				for (int ix=0; ix < indices.Count; ++ix)
				{
					ds.Add( this.X[indices[ix]], this.Y[indices[ix]] );
				}
				return ds;
			}
			set
			{
				for (int ix=0; ix < indices.Count; ++ix)
				{
					this.Y[indices[ix]] = value.Y[ix];
				}
			}
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("[DataSet: X=[");
			foreach (double val in this.X)
				sb.AppendFormat(" {0,8}", val.ToString("F3"));
			sb.Append("]\n          Y=[");
			foreach (double val in this.Y)
				sb.AppendFormat(" {0,8}", val.ToString("F3"));
			sb.Append("]]");
			return sb.ToString();
		}
		
		public string ToCsvString()
		{
			StringBuilder sb = new StringBuilder();
			sb.Append("X:");
			foreach (double val in this.X)
				sb.AppendFormat(CultureInfo.InvariantCulture, ",{0}", val);
			sb.Append("\nY:");
			foreach (double val in this.Y)
				sb.AppendFormat(CultureInfo.InvariantCulture, ",{0}", val);
			return sb.ToString();
		}

		public struct Operand
		{
			internal double x_operand;
			internal double y_operand;
			
			public Operand(double x_operand, double y_operand)
			{
				this.x_operand = x_operand;
				this.y_operand = y_operand;
			}
		}

		public bool ContainsNaN
        {
            get
            {
                bool contains = false;
                Parallel.For(0, this.Count, index =>
                {
                    if (contains)
                        return;

                    if (Double.IsNaN(this.Y[index]) || Double.IsNaN(this.X[index]))
                    {
                        contains = true;
                        return;
                    }
                });
                return contains;
            }
        }
		
		public static DataSet operator*(DataSet lhs, DataSet.Operand rhs)
		{
			DataSet ds = new DataSet(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				ds.Add( lhs.X[ix] * rhs.x_operand, lhs.Y[ix] * rhs.y_operand );
			}
			return ds;
		}
		
		public static DataSet operator*(DataSet lhs, DataSet rhs)
		{
			if (lhs.Count != rhs.Count)
				throw new ArgumentException(Catalog.GetString("Multiplied DataSets must have same length.") +
				                            string.Format("lhs.Count={0}, rhs.Count={1}", lhs.Count, rhs.Count));

			List<double> y = new List<double>(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				y.Add(lhs.Y[ix] * rhs.Y[ix]);
			}
			return new DataSet(new List<double>(lhs.X), y);			
		}

		public static DataSet operator/(DataSet lhs, DataSet.Operand rhs)
		{
			DataSet ds = new DataSet(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				ds.Add( lhs.X[ix] / rhs.x_operand, lhs.Y[ix] / rhs.y_operand );
			}
			return ds;
		}
		
		public static DataSet operator/(DataSet lhs, DataSet rhs)
		{
			if (lhs.Count != rhs.Count)
				throw new ArgumentException(Catalog.GetString("Divided DataSets must have same length."));
			
			List<double> y = new List<double>(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				y.Add(lhs.Y[ix] / rhs.Y[ix]);
			}
			return new DataSet(new List<double>(lhs.X), y);			
		}

		public static DataSet operator+(DataSet lhs, DataSet.Operand rhs)
		{
			DataSet ds = new DataSet(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				ds.Add( lhs.X[ix] + rhs.x_operand, lhs.Y[ix] + rhs.y_operand );
			}
			return ds;
		}
		
		public static DataSet operator+(DataSet lhs, DataSet rhs)
		{
			if (lhs.Count != rhs.Count)
				throw new ArgumentException(Catalog.GetString("Added DataSets must have same length."));
			
			List<double> y = new List<double>(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				y.Add(lhs.Y[ix] + rhs.Y[ix]);
			}
			return new DataSet(new List<double>(lhs.X), y);	
		}
		
		public static DataSet operator-(DataSet lhs, DataSet.Operand rhs)
		{
			DataSet ds = new DataSet(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				ds.Add( lhs.X[ix] - rhs.x_operand, lhs.Y[ix] - rhs.y_operand );
			}
			return ds;
		}
		
		public static DataSet operator-(DataSet lhs, DataSet rhs)
		{
			if (lhs.Count != rhs.Count)
				throw new ArgumentException(Catalog.GetString("Subtracted DataSets must have same length."));
			
			List<double> y = new List<double>(lhs.Count);
			for (int ix=0; ix < lhs.Count; ++ix)
			{
				y.Add(lhs.Y[ix] - rhs.Y[ix]);
			}
			return new DataSet(new List<double>(lhs.X), y);			
		}
		
		public static DataSet operator+(DataSet lhs, double rhs)
		{
			return lhs + new DataSet.Operand(0f, rhs);
		}

		public static DataSet operator+(double lhs, DataSet rhs)
		{
			return rhs + new DataSet.Operand(0f, lhs);
		}

		public static DataSet operator-(DataSet lhs, double rhs)
		{
			return lhs - new DataSet.Operand(0f, rhs);
		}

		public static DataSet operator-(double lhs, DataSet rhs)
		{
			return (rhs * new DataSet.Operand(1f, -1f)) + new DataSet.Operand(0f, lhs);
		}

		public static DataSet operator*(DataSet lhs, double rhs)
		{
			return lhs * new DataSet.Operand(1f, rhs);
		}

		public static DataSet operator*(double lhs, DataSet rhs)
		{
			return rhs * new DataSet.Operand(1f, lhs);
		}

		public static DataSet operator/(DataSet lhs, double rhs)
		{
			return lhs / new DataSet.Operand(1f, rhs);
		}

		public static DataSet operator/(double lhs, DataSet rhs)
		{
			DataSet ds = new DataSet(rhs.Count);
			for (int ix=0; ix < rhs.Count; ++ix)
			{
				ds.Add( rhs.X[ix], lhs / rhs.Y[ix] );
			}
			return ds;
		}
		
		public static double[] GetDefaultSystemFunctionCorrectionCoefficients()
		{
			// FIXME: implement real system function correction
			return Get11nmFirLowpassCoefficients();
		}

        // octave: b=fir1(100, 1/6);
        public static double[] Get6nmFirLowpassCoefficients()
        {
            return new double[] { 0.0003945551578909242,0.0001904328763284081,-8.111494429882977e-05,-0.0003679601352354859,-0.0006035165619188798,-0.0007149349725294519,-0.000639615338177953,-0.0003481538119254796,0.0001330211910782818,0.0007093307361003256,0.001225677524142037,0.001498797200968055,0.001370640507608342,0.0007696421083232137,-0.0002401617260132965,-0.001443228810787122,-0.002504299290175211,-0.00304892911322412,-0.00277697559402262,-0.001578279816781313,0.0003876753909662327,0.002682315326275805,0.004666609364912438,0.005660348656943048,0.005147069029764881,0.002968447746887509,-0.0005549179343523198,-0.004627610669212221,-0.008129829855914242,-0.009894377035251934,-0.009048688751105939,-0.005330843265706666,0.0007183832130707578,0.007764345986962353,0.01391885483352542,0.01718115363186209,0.0159879201000283,0.009737786727758418,-0.00085503918690382,-0.01369603972287126,-0.02559034798772618,-0.03283981982531958,-0.03204539645766354,-0.02094007605275489,0.0009456023252245388,0.03195880213363037,0.06844379230423182,0.1053093843442415,0.1369425775255035,0.1582808960813578,0.1658121896685724,0.1582808960813578,0.1369425775255035,0.1053093843442415,0.06844379230423187,0.03195880213363037,0.000945602325224539,-0.0209400760527549,-0.03204539645766354,-0.03283981982531959,-0.02559034798772619,-0.01369603972287127,-0.0008550391869038196,0.009737786727758416,0.01598792010002829,0.01718115363186209,0.01391885483352541,0.007764345986962348,0.0007183832130707568,-0.005330843265706665,-0.009048688751105941,-0.009894377035251931,-0.008129829855914242,-0.004627610669212222,-0.0005549179343523209,0.00296844774688751,0.005147069029764885,0.005660348656943049,0.00466660936491244,0.002682315326275806,0.0003876753909662339,-0.001578279816781314,-0.002776975594022622,-0.003048929113224119,-0.002504299290175211,-0.001443228810787124,-0.0002401617260132971,0.0007696421083232147,0.001370640507608344,0.001498797200968055,0.001225677524142038,0.0007093307361003265,0.0001330211910782821,-0.0003481538119254798,-0.000639615338177953,-0.0007149349725294518,-0.0006035165619188799,-0.000367960135235486,-8.11149442988299e-05,0.0001904328763284081,0.0003945551578909244 };
        }
		
		public static double[] Get11nmFirLowpassCoefficients()
		{
			return new double[] {
				-7.91123056905783E-05,-5.63346013297579E-05,-7.26004357521453E-08,8.93923725165618E-05,
				0.00020232252412,0.00031900870473,0.00041165189117,0.00044878245475,0.00040178071418,
				0.00025246709824,2.52077320096113E-07,-0.00033289391436,-0.00070127385196,-0.00103922313543,
				-0.0012700713471,-0.00131951619712,-0.00113151915933,-0.00068392829241,-5.49496082658407E-07,
				0.00084350519842,0.0017255882973,0.0024894075411,0.00296820482123,0.00301441734202,
				0.00253126791583,0.00150058779573,9.47607725386044E-07,-0.0017889621938,-0.00360480779291,
				-0.00512922821641,-0.00603958570902,-0.0060646366917,-0.0050413799113,-0.00296203268291,
				-1.39239332831051E-06,0.00348309917221,0.00698261160137,0.00989872058785,0.0116297146872,
				0.01167055022558,0.00971202551334,0.00572317917803,1.80314373213957E-06,-0.00681862117064,
				-0.01381163998125,-0.01984918791605,-0.02373304652988,-0.02435103532775,-0.02083759623328,
				-0.01271620655025,-2.09385583838713E-06,0.01675194462973,0.03647668688375,0.05767568763363,
				0.07857408502943,0.09730986370781,0.11214390573337,0.12166185215212,0.12494126764405,
				0.12166185215212,0.11214390573337,0.09730986370781,0.07857408502943,0.05767568763363,
				0.03647668688375,0.01675194462973,-2.09385583838713E-06,-0.01271620655025,-0.02083759623328,
				-0.02435103532775,-0.02373304652988,-0.01984918791605,-0.01381163998125,-0.00681862117064,
				1.80314373213957E-06,0.00572317917803,0.00971202551334,0.01167055022558,0.0116297146872,
				0.00989872058785,0.00698261160137,0.00348309917221,-1.39239332831051E-06,-0.00296203268291,
				-0.0050413799113,-0.0060646366917,-0.00603958570902,-0.00512922821641,-0.00360480779291,
				-0.0017889621938,9.47607725386044E-07,0.00150058779573,0.00253126791583,0.00301441734202,
				0.00296820482123,0.0024894075411,0.0017255882973,0.00084350519842,-5.49496082658407E-07,
				-0.00068392829241,-0.00113151915933,-0.00131951619712,-0.0012700713471,-0.00103922313543,
				-0.00070127385196,-0.00033289391436,2.52077320096113E-07,0.00025246709824,0.00040178071418,
				0.00044878245475,0.00041165189117,0.00031900870473,0.00020232252412,8.93923725165618E-05,
				-7.26004357521453E-08,-5.63346013297579E-05,-7.91123056905783E-05};
		}
		
		public static double[] Get15nmFirLowpassCoefficients()
		{
			// FIXME: add algorithm for user defined characteristic
			// for now only fixed filter with
			// Fpass = 0.06667 1/nm, Fstop = 0.11667 1/nm, Apass = 1 dB, Astop = 100 dB
//			return new double[] {
//				1.06160203918370e-05, 1.58699743315200e-05, 2.60304509517019e-05, 3.85158876134531e-05,
//				5.23640003726302e-05, 6.57461962071049e-05, 7.58638873439123e-05, 7.87722136485093e-05,
//				6.94114505475002e-05, 4.16773948669506e-05, -1.13646253944701e-05, -9.70789839927444e-05,
//				-0.000222726824858080, -0.000394802847701850, -0.000618230090794771, -0.000895481844171498,
//				-0.00122573250764897, -0.00160405234782625, -0.00202076089927845, -0.00246103547847476,
//				-0.00290487262921832, -0.00332743456397018, -0.00369987619277274, -0.00399064599684623,
//				-0.00416727011221990, -0.00419853392408354, -0.00405698550149734, -0.00372158229215882,
//				-0.00318034338744144, -0.00243276188774199, -0.00149177463123860, -0.000385041422454406,
//				0.000844624654604215, 0.00213987823268830, 0.00343058833349141, 0.00463677427451987,
//				0.00567264601270496, 0.00645164068795849, 0.00689218638429967, 0.00692391726431207,
//				0.00649392522829259, 0.00557265118447655, 0.00415893374120368, 0.00228381484274255,
//				1.26719203250373e-05, -0.00255460238817270, -0.00528561300878641, -0.00801971385725387,
//				-0.0105744686091173, -0.0127540620304444, -0.0143593245857955, -0.0151988298131824,
//				-0.0151004847143325, -0.0139228902663264, -0.0115657887435007, -0.00797885670334192,
//				-0.00316823874557586, 0.00279974102872982, 0.00979803353518270, 0.0176417168172966,
//				0.0260941986050760, 0.0348765817887583, 0.0436797321087168, 0.0521784738624393,
//				0.0600471183457613, 0.0669754971609434, 0.0726845497341814, 0.0769405929517723,
//				0.0795673982323401, 0.0804553929168684, 0.0795673982323401, 0.0769405929517723,
//				0.0726845497341814, 0.0669754971609434, 0.0600471183457613, 0.0521784738624393,
//				0.0436797321087168, 0.0348765817887583, 0.0260941986050760, 0.0176417168172966,
//				0.00979803353518270, 0.00279974102872982, -0.00316823874557586, -0.00797885670334192,
//				-0.0115657887435007, -0.0139228902663264, -0.0151004847143325, -0.0151988298131824,
//				-0.0143593245857955, -0.0127540620304444, -0.0105744686091173, -0.00801971385725387,
//				-0.00528561300878641, -0.00255460238817270, 1.26719203250373e-05, 0.00228381484274255,
//				0.00415893374120368, 0.00557265118447655, 0.00649392522829259, 0.00692391726431207,
//				0.00689218638429967, 0.00645164068795849, 0.00567264601270496, 0.00463677427451987,
//				0.00343058833349141, 0.00213987823268830, 0.000844624654604215, -0.000385041422454406,
//				-0.00149177463123860, -0.00243276188774199, -0.00318034338744144, -0.00372158229215882,
//				-0.00405698550149734, -0.00419853392408354, -0.00416727011221990, -0.00399064599684623,
//				-0.00369987619277274, -0.00332743456397018, -0.00290487262921832, -0.00246103547847476,
//				-0.00202076089927845, -0.00160405234782625, -0.00122573250764897, -0.000895481844171498,
//				-0.000618230090794771, -0.000394802847701850, -0.000222726824858080, -9.70789839927444e-05,
//				-1.13646253944701e-05, 4.16773948669506e-05, 6.94114505475002e-05, 7.87722136485093e-05,
//				7.58638873439123e-05, 6.57461962071049e-05, 5.23640003726302e-05, 3.85158876134531e-05,
//				2.60304509517019e-05, 1.58699743315200e-05, 1.06160203918370e-05 };
			return new double[] {
				7.32310523996941e-05, 0.000105378137367897, 0.000135303158790317, 0.000157142981889277,
				0.000164562778586740, 0.000151566515437525, 0.000113432487036269, 4.76707711966172e-05,
				-4.51209669946715e-05, -0.000160634537581741, -0.000290584416675055, -0.000422895578207024,
				-0.000542403660072961, -0.000632071068904368, -0.000674660043462662, -0.000654739028071093,
				-0.000560839424478947, -0.000387533657718944, -0.000137179686446754, 0.000178922706064957,
				0.000540188929078742, 0.000917385668287677, 0.00127432567111943, 0.00157059429066730,
				0.00176516318563297, 0.00182062950990037, 0.00170771539036814, 0.00140958537707334,
				0.000925500290043245, 0.000273333069000074, -0.000509469619131499, -0.00136678756400243,
				-0.00222674309377802, -0.00300660005538184, -0.00361940761594107, -0.00398194685459168,
				-0.00402335610656086, -0.00369367152250529, -0.00297144049039123, -0.00186956117243185,
				-0.000438578978529745, 0.00123316958478617, 0.00302294156205334, 0.00478058468329522,
				0.00633890371735530, 0.00752681818071873, 0.00818442251366788, 0.00817880337301814,
				0.00741929488928788, 0.00587078392391790, 0.00356372760124105, 0.000599718631759340,
				-0.00284827695215700, -0.00654149826826649, -0.0101868166734663, -0.0134535543387403,
				-0.0159947554321897, -0.0174715396786459, -0.0175788273597851, -0.0160705089098553,
				-0.0127820658328162, -0.00764874532618293, -0.000717647681708560, 0.00784751189565296,
				0.0177696852977978, 0.0286699374082688, 0.0400865470776195, 0.0515006462491111,
				0.0623667438874208, 0.0721460627265829, 0.0803403530392529, 0.0865237632562132,
				0.0903704521896564, 0.0916759158741111, 0.0903704521896564, 0.0865237632562132,
				0.0803403530392529, 0.0721460627265829, 0.0623667438874208, 0.0515006462491111,
				0.0400865470776195, 0.0286699374082688, 0.0177696852977978, 0.00784751189565296,
				-0.000717647681708560, -0.00764874532618293, -0.0127820658328162, -0.0160705089098553,
				-0.0175788273597851, -0.0174715396786459, -0.0159947554321897, -0.0134535543387403,
				-0.0101868166734663, -0.00654149826826649, -0.00284827695215700, 0.000599718631759340,
				0.00356372760124105, 0.00587078392391790, 0.00741929488928788, 0.00817880337301814,
				0.00818442251366788, 0.00752681818071873, 0.00633890371735530, 0.00478058468329522,
				0.00302294156205334, 0.00123316958478617, -0.000438578978529745, -0.00186956117243185,
				-0.00297144049039123, -0.00369367152250529, -0.00402335610656086, -0.00398194685459168,
				-0.00361940761594107, -0.00300660005538184, -0.00222674309377802, -0.00136678756400243,
				-0.000509469619131499, 0.000273333069000074, 0.000925500290043245, 0.00140958537707334,
				0.00170771539036814, 0.00182062950990037, 0.00176516318563297, 0.00157059429066730,
				0.00127432567111943, 0.000917385668287677, 0.000540188929078742, 0.000178922706064957,
				-0.000137179686446754, -0.000387533657718944, -0.000560839424478947, -0.000654739028071093,
				-0.000674660043462662, -0.000632071068904368, -0.000542403660072961, -0.000422895578207024,
				-0.000290584416675055, -0.000160634537581741, -4.51209669946715e-05, 4.76707711966172e-05,
				0.000113432487036269, 0.000151566515437525, 0.000164562778586740, 0.000157142981889277,
				0.000135303158790317, 0.000105378137367897, 7.32310523996941e-05 };
		}
		
		private static double[] GetFirBandpassCoefficients()
		{
			// FIXME: add algorithm for user defined characteristic
			// for now only fixed filter with
			// Fstop1 = 0.01 1/nm, Fpass1 = 0.025 1/nm, 
			// Fpass2 = 0.05 1/nm, Fstop2 = 0.1 1/nm
			// Astop1 = 30 dB, Apass = 1 dB, Astop2 = 60 dB

			return new double[] {
				-0.000279387847765904, -0.000369042661401291, -0.000398874914641505, -0.000359029056496251,
				-0.000243117047899196, -4.88262750937245e-05, 0.000221681270004831, 0.000561614344518846,
				0.000959665281446992, 0.00140039831313234, 0.00186490525889127, 0.00233170395873492,
				0.00277783899886275, 0.00318013007156821, 0.00351650167311371, 0.00376731958736962, 
				0.00391665538456969, 0.00395340042575677, 0.00387215579953158, 0.00367383415430887, 
				0.00336592317565794, 0.00296237787624885, 0.00248312905509157, 0.00195321718130115, 
				0.00140158335640214, 0.000859570616773848, 0.000359208352978639, -6.86311897955800e-05, 
				-0.000396103600269029, -0.000600182988241485, -0.000664264937522461, -0.000579479662290969,
				-0.000345625135989188, 2.83524564125830e-05, 0.000524381019508340, 0.00111581119423094, 
				0.00176848589471382, 0.00244229521961209, 0.00309315182354083, 0.00367529480145259, 
				0.00414380711148110, 0.00445721401895027, 0.00458001921392142, 0.00448503203479624, 
				0.00415534412573251, 0.00358582693394021, 0.00278404232546183, 0.00177048642396862, 
				0.000578120294003166, -0.000748821330961308, -0.00215671235226243, -0.00358503796192342,
				-0.00496940937486831, -0.00624494908335492, -0.00734987812044322, -0.00822911790919299,
				-0.00883770976657658, -0.00914385591607448, -0.00913139730334606, -0.00880156540303466, 
				-0.00817387677680820, -0.00728607906890226, -0.00619310358593983, -0.00496503036967102,
				-0.00368412419544288, -0.00244105149721725, -0.00133043607392415, -0.000445952920511757,
				0.000124807750358106, 0.000305452288551670, 3.56075328928048e-05, -0.000724803212186013,
				-0.00199207522446611, -0.00375624146751021, -0.00597976684294654, -0.00859753662528274, 
				-0.0115181982776233, -0.0146268510421925, -0.0177890110614792, -0.0208557146231690, 
				-0.0236695617506681, -0.0260714499331062, -0.0279077061273222, -0.0290372966121408, 
				-0.0293387805651726, -0.0287166753876088, -0.0271069200713625, -0.0244811567326457,
				-0.0208495984872483, -0.0162623120444532, -0.0108088130395177, -0.00461594798884183,
				0.00215588473883038, 0.00931804514479379, 0.0166602906932557, 0.0239584778423786, 
				0.0309830070817165, 0.0375076543053368, 0.0433184093498703, 0.0482219379676020,
				0.0520532967469118, 0.0546825610313269, 0.0560200723749375, 0.0560200723749375,
				0.0546825610313269, 0.0520532967469118, 0.0482219379676020, 0.0433184093498703, 
				0.0375076543053368, 0.0309830070817165, 0.0239584778423786, 0.0166602906932557, 
				0.00931804514479379, 0.00215588473883038, -0.00461594798884183, -0.0108088130395177, 
				-0.0162623120444532, -0.0208495984872483, -0.0244811567326457, -0.0271069200713625, 
				-0.0287166753876088, -0.0293387805651726, -0.0290372966121408, -0.0279077061273222, 
				-0.0260714499331062, -0.0236695617506681, -0.0208557146231690, -0.0177890110614792, 
				-0.0146268510421925, -0.0115181982776233, -0.00859753662528274, -0.00597976684294654,
				-0.00375624146751021, -0.00199207522446611, -0.000724803212186013, 3.56075328928048e-05,
				0.000305452288551670, 0.000124807750358106, -0.000445952920511757, -0.00133043607392415,
				-0.00244105149721725, -0.00368412419544288, -0.00496503036967102, -0.00619310358593983,
				-0.00728607906890226, -0.00817387677680820, -0.00880156540303466, -0.00913139730334606,
				-0.00914385591607448, -0.00883770976657658, -0.00822911790919299, -0.00734987812044322,
				-0.00624494908335492, -0.00496940937486831, -0.00358503796192342, -0.00215671235226243,
				-0.000748821330961308, 0.000578120294003166, 0.00177048642396862, 0.00278404232546183,
				0.00358582693394021, 0.00415534412573251, 0.00448503203479624, 0.00458001921392142,
				0.00445721401895027, 0.00414380711148110, 0.00367529480145259, 0.00309315182354083,
				0.00244229521961209, 0.00176848589471382, 0.00111581119423094, 0.000524381019508340,
				2.83524564125830e-05, -0.000345625135989188, -0.000579479662290969, -0.000664264937522461,
				-0.000600182988241485, -0.000396103600269029, -6.86311897955800e-05, 0.000359208352978639,
				0.000859570616773848, 0.00140158335640214, 0.00195321718130115, 0.00248312905509157,
				0.00296237787624885, 0.00336592317565794, 0.00367383415430887, 0.00387215579953158,
				0.00395340042575677, 0.00391665538456969, 0.00376731958736962, 0.00351650167311371, 
				0.00318013007156821, 0.00277783899886275, 0.00233170395873492, 0.00186490525889127,
				0.00140039831313234, 0.000959665281446992, 0.000561614344518846, 0.000221681270004831,
				-4.88262750937245e-05, -0.000243117047899196, -0.000359029056496251, -0.000398874914641505,
				-0.000369042661401291, -0.000279387847765904 };
		}
	}
}
