//******************************************************************************************************
// AverageFrequency.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:
// ----------------------------------------------------------------------------------------------------
// 11/22/2006 - J. Ritchie Carroll
// Initial version of source generated
// 12/24/2009 - Jian R. Zuo
// Converted code to C#
// 04/12/2010 - J. Ritchie Carroll
// Performed full code review, optimization and further abstracted code for average calculation.
// 12/13/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using GSF;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
using GSF.Units.EE;
using PhasorProtocolAdapters;
namespace PowerCalculations
{
///
/// Calculates a real-time average frequency reporting the average, maximum and minimum values.
///
[Description("Average Frequency: Calculates a real-time average frequency reporting the average, maximum, and minimum values")]
public class AverageFrequency : CalculatedMeasurementBase
{
#region [ Members ]
// Constants
private const double DefaultLowFrequencyThreshold = 57.0D;
private const double DefaultHighFrequencyThreshold = 62.0D;
private const bool DefaultReportUnreasonableResultsAsNaN = false;
// Fields
private double m_averageFrequency;
private double m_maximumFrequency;
private double m_minimumFrequency;
private readonly ConcurrentDictionary> m_lastValues = new ConcurrentDictionary>();
// Important: Make sure output definition defines points in the following order
private enum Output
{
Average,
Maximum,
Minimum
}
#endregion
#region [ Properties ]
///
/// Gets or sets low frequency reasonability threshold, inclusive.
///
[ConnectionStringParameter]
[Description("Defines low frequency reasonability threshold. Value is inclusive, i.e., frequency will be unreasonable at and beyond specified threshold.")]
[DefaultValue(DefaultLowFrequencyThreshold)]
public double LowFrequencyThreshold
{
get;
set;
}
///
/// Gets or sets high frequency reasonability threshold, inclusive.
///
[ConnectionStringParameter]
[Description("Defines high frequency reasonability threshold. Value is inclusive, i.e., frequency will be unreasonable at and beyond specified threshold.")]
[DefaultValue(DefaultHighFrequencyThreshold)]
public double HighFrequencyThreshold
{
get;
set;
}
///
/// Gets or sets flag that determines if unreasonable results should be reported as NaN.
///
[ConnectionStringParameter]
[Description("Defines flag that determines if unreasonable results should be reported as NaN.")]
[DefaultValue(DefaultReportUnreasonableResultsAsNaN)]
public bool ReportUnreasonableResultsAsNaN
{
get;
set;
}
///
/// Returns the detailed status of the calculator.
///
public override string Status
{
get
{
StringBuilder status = new StringBuilder();
status.AppendFormat(" Low frequency threshold: {0:N3} Hz", LowFrequencyThreshold);
status.AppendLine();
status.AppendFormat(" High frequency threshold: {0:N3} Hz", HighFrequencyThreshold);
status.AppendLine();
status.AppendFormat("Report unreasonable as NaN: {0}", ReportUnreasonableResultsAsNaN);
status.AppendLine();
status.AppendFormat(" Last average frequency: {0:N3} Hz", m_averageFrequency);
status.AppendLine();
status.AppendFormat(" Last maximum frequency: {0:N3} Hz", m_maximumFrequency);
status.AppendLine();
status.AppendFormat(" Last minimum frequency: {0:N3} Hz", m_minimumFrequency);
status.AppendLine();
status.Append(base.Status);
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Initializes the calculator.
///
public override void Initialize()
{
base.Initialize();
Dictionary settings = Settings;
string setting;
double threshold;
// Get settings
if (settings.TryGetValue("LowFrequencyThreshold", out setting) && double.TryParse(setting, out threshold))
LowFrequencyThreshold = threshold;
else
LowFrequencyThreshold = DefaultLowFrequencyThreshold;
if (settings.TryGetValue("HighFrequencyThreshold", out setting) && double.TryParse(setting, out threshold))
HighFrequencyThreshold = threshold;
else
HighFrequencyThreshold = DefaultHighFrequencyThreshold;
if (settings.TryGetValue("ReportUnreasonableResultsAsNaN", out setting))
ReportUnreasonableResultsAsNaN = setting.ParseBoolean();
else
ReportUnreasonableResultsAsNaN = DefaultReportUnreasonableResultsAsNaN;
// Validate input measurements
List validInputMeasurementKeys = new List();
for (int i = 0; i < InputMeasurementKeys.Length; i++)
{
if (InputMeasurementKeyTypes[i] == SignalType.FREQ)
validInputMeasurementKeys.Add(InputMeasurementKeys[i]);
}
if (validInputMeasurementKeys.Count == 0)
throw new InvalidOperationException("No valid frequency measurements were specified as inputs to the average frequency calculator.");
// Make sure only frequencies are used as input
InputMeasurementKeys = validInputMeasurementKeys.ToArray();
// Validate output measurements
if (OutputMeasurements.Length < Enum.GetValues(typeof(Output)).Length)
throw new InvalidOperationException("Not enough output measurements were specified for the average frequency calculator, expecting measurements for \"Average\", \"Maximum\", and \"Minimum\" frequencies - in this order.");
}
///
/// Calculates the average frequency for all frequencies that have reported in the specified lag time.
///
/// Single frame of measurement data within a one second sample.
/// Index of frame within the one second sample.
protected override void PublishFrame(IFrame frame, int index)
{
if (frame.Measurements.Count > 0)
{
double total = 0.0D;
double maximumFrequency = LowFrequencyThreshold;
double minimumFrequency = HighFrequencyThreshold;
int count = 0;
foreach (IMeasurement measurement in frame.Measurements.Values)
{
double frequency = measurement.AdjustedValue;
// Do some simple flat line avoidance, validating at three decimal places...
int truncatedFrequency = (int)(frequency * 1000.0D);
Tuple lastValues = m_lastValues.GetOrAdd(measurement.ID, id => new Tuple(truncatedFrequency, 0L));
long flatLineCount = lastValues.Item2;
if (lastValues.Item1 == truncatedFrequency)
{
flatLineCount++;
// If value remains the same more than 4 times, assume flat-line value
if (flatLineCount > 4L)
frequency = double.NaN;
}
else
{
flatLineCount = 0L;
}
m_lastValues[measurement.ID] = new Tuple(truncatedFrequency, flatLineCount);
// Validate frequency
if (!double.IsNaN(frequency) && frequency > LowFrequencyThreshold && frequency < HighFrequencyThreshold)
{
total += frequency;
if (frequency > maximumFrequency)
maximumFrequency = frequency;
if (frequency < minimumFrequency)
minimumFrequency = frequency;
count++;
}
}
if (count > 0)
{
m_averageFrequency = total / count;
m_maximumFrequency = maximumFrequency;
m_minimumFrequency = minimumFrequency;
}
if (ReportUnreasonableResultsAsNaN)
{
if (m_averageFrequency <= LowFrequencyThreshold || m_averageFrequency >= HighFrequencyThreshold)
m_averageFrequency = double.NaN;
if (m_maximumFrequency <= LowFrequencyThreshold || m_maximumFrequency >= HighFrequencyThreshold)
m_maximumFrequency = double.NaN;
if (m_minimumFrequency <= LowFrequencyThreshold || m_minimumFrequency >= HighFrequencyThreshold)
m_minimumFrequency = double.NaN;
}
// Provide calculated measurements for external consumption
IMeasurement[] outputMeasurements = OutputMeasurements;
OnNewMeasurements(new IMeasurement[]{
Measurement.Clone(outputMeasurements[(int)Output.Average], m_averageFrequency, frame.Timestamp),
Measurement.Clone(outputMeasurements[(int)Output.Maximum], m_maximumFrequency, frame.Timestamp),
Measurement.Clone(outputMeasurements[(int)Output.Minimum], m_minimumFrequency, frame.Timestamp)});
}
else
{
m_averageFrequency = 0.0D;
m_maximumFrequency = 0.0D;
m_minimumFrequency = 0.0D;
}
}
#endregion
}
}