//****************************************************************************************************** // 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 } }