//******************************************************************************************************
// RealTimeSlope.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 01/24/2006 - J. Ritchie Carroll
// Generated original version of source code.
// 09/17/2008 - J. Ritchie Carroll
// Converted to C#.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Threading;
using GSF.Units;
namespace GSF.NumericalAnalysis
{
/// Calculates slope for a real-time continuous data stream.
public class RealTimeSlope
{
#region [ Members ]
// Events
///
/// Raised when new status messages come from the .
///
///
/// is status message from the .
///
public event EventHandler> Status;
///
/// Raised when new real-time has been calculated and is available.
///
public event EventHandler Recalculated;
// Fields
private int m_regressionInterval;
private int m_pointCount;
private List m_xValues;
private List m_yValues;
private double m_slope;
private double m_lastSlope;
private DateTime m_slopeRun;
private bool m_calculating;
#endregion
#region [ Constructors ]
///
/// Creates a default instance of the real-time slope calculation class. Must call Initialize before using.
///
public RealTimeSlope()
{
}
/// Creates a new instance of the real-time slope calculation class.
/// Time span over which to calculate slope.
/// Estimated data points per second.
public RealTimeSlope(int regressionInterval, double estimatedRefreshInterval)
: this()
{
Initialize(regressionInterval, estimatedRefreshInterval);
}
#endregion
#region [ Properties ]
/// Gets current calculated slope for data set.
public double Slope
{
get
{
return m_slope;
}
}
/// Gets run-time, in seconds, for which slope has maintained a continuous positive or negative
/// trend.
public Time RunTime
{
get
{
return (m_regressionInterval + Ticks.ToSeconds(DateTime.UtcNow.Ticks - m_slopeRun.Ticks));
}
}
#endregion
#region [ Methods ]
/// Adds a new x, y data pair to continuous data set.
/// New x-axis value.
/// New y-axis value.
public void Calculate(double x, double y)
{
lock (m_xValues)
{
// Adds latest values to regression data set.
m_xValues.Add(x);
m_yValues.Add(y);
// Keeps a constant number of points by removing them from the left.
while (m_xValues.Count > m_pointCount)
{
m_xValues.RemoveAt(0);
}
while (m_yValues.Count > m_pointCount)
{
m_yValues.RemoveAt(0);
}
}
if (m_xValues.Count >= m_pointCount && !m_calculating)
{
// Performs curve fit calculation on separate thread, since it could be time consuming.
#if ThreadTracking
ManagedThread thread = ManagedThreadPool.QueueUserWorkItem(PerformCalculation);
thread.Name = "GSF.Math.RealTimeSlope.PerformCalculation()";
#else
ThreadPool.QueueUserWorkItem(PerformCalculation);
#endif
}
}
///
/// Initializes real-time slope calculation.
///
/// Time span over which to calculate slope.
/// Estimated data points per second.
public void Initialize(int regressionInterval, double estimatedRefreshInterval)
{
m_slopeRun = DateTime.UtcNow;
m_regressionInterval = regressionInterval;
m_pointCount = m_regressionInterval * (int)(1 / estimatedRefreshInterval);
if ((object)m_xValues == null)
{
m_xValues = new List();
}
else
{
lock (m_xValues)
{
m_xValues.Clear();
}
}
if ((object)m_yValues == null)
{
m_yValues = new List();
}
else
{
lock (m_yValues)
{
m_yValues.Clear();
}
}
}
private void PerformCalculation(object state)
{
try
{
m_calculating = true;
double[] xValues;
double[] yValues;
// Calculations are made against a copy of the current data set to keep lock time on
// data values down to a minimum. This allows data to be added with minimal delay.
lock (m_xValues)
{
xValues = m_xValues.ToArray();
yValues = m_yValues.ToArray();
}
// Takes new values and calculates slope (curve fit for 1st order polynomial).
m_slope = CurveFit.Compute(1, xValues, yValues)[1];
}
catch (Exception ex)
{
if ((object)Status != null)
Status(this, new EventArgs("CurveFit failed: " + ex.Message));
}
finally
{
m_calculating = false;
}
if (Math.Sign(m_slope) != Math.Sign(m_lastSlope))
m_slopeRun = DateTime.UtcNow;
m_lastSlope = m_slope;
// Notifies consumer of new calculated slope.
if ((object)Recalculated != null)
Recalculated(this, EventArgs.Empty);
}
#endregion
}
}