//******************************************************************************************************
// LossOfField.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:
// ----------------------------------------------------------------------------------------------------
// 12/02/2009 - Jian R. Zuo
// Generated original version of source code.
// 12/16/2009 - Jian R. Zuo
// Reading parameters configuration from database
// 04/12/2010 - J. Ritchie Carroll
// Performed full code review, optimization and further abstracted code for LOF detection.
// 12/13/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using GSF.Collections;
using GSF.Diagnostics;
using GSF.TimeSeries;
using GSF.TimeSeries.Adapters;
using GSF.Units;
using GSF.Units.EE;
using PhasorProtocolAdapters;
namespace PowerCalculations.EventDetection
{
///
/// Represents an algorithm that detects Loss of Field from a synchrophasor device.
///
[Description("Loss of Field: Detects Loss-of-Field from a synchrophasor device")]
public class LossOfField : CalculatedMeasurementBase
{
#region [ Members ]
// Constants
private const double SqrtOf3 = 1.7320508075688772935274463415059D;
// Fields
private double m_pSet; // Threshold of P-set MW: default value -600 mW
private double m_qSet; // Threshold of Q-set MVar: default value 200 mVar
private double m_qAreaSet; // Threshold of Q-area MVar-sec: default value 500 mVar-sec
private double m_voltageThreshold; // Threshold of Voltage: default value 0.95 p.u. or 475 kV
private double m_qAreamVar; // Calculated Q area value
private int m_analysisInterval; // Interval between adjacent calculations
private long m_count; // Running frame count
private long m_count1; // Last frame count
private long m_count2; // Current frame count
private MeasurementKey m_voltageMagnitude; // Measurement input key for voltage magnitude
private MeasurementKey m_voltageAngle; // Measurement input key for voltage angle
private MeasurementKey m_currentMagnitude; // Measurement input key for current magnitude
private MeasurementKey m_currentAngle; // Measurement input key for current angle
// Important: Make sure output definition defines points in the following order
private enum Output
{
WarningSignal,
RealPower,
ReactivePower,
QAreaValue
}
#endregion
#region [ Properties ]
///
/// Gets or sets the threshold of P-set MW.
///
[ConnectionStringParameter,
Description("Define the threshold of P-set MW."),
DefaultValue(-600)]
public double PSet
{
get
{
return m_pSet;
}
set
{
m_pSet = value;
}
}
///
/// Gets or sets the threshold of Q-set MVar.
///
[ConnectionStringParameter,
Description("Define the threshold of Q-set MVar."),
DefaultValue(200)]
public double QSet
{
get
{
return m_qSet;
}
set
{
m_qSet = value;
}
}
///
/// Gets or sets the threshold of Q-area MVar-sec.
///
[ConnectionStringParameter,
Description("Define the threshold of Q-area MVar-sec."),
DefaultValue(500)]
public double QAreaSet
{
get
{
return m_qAreaSet;
}
set
{
m_qAreaSet = value;
}
}
///
/// Gets or sets the threshold of voltage, in volts.
///
[ConnectionStringParameter,
Description("Define the threshold of voltage, in volts."),
DefaultValue(475000)]
public double VoltageThreshold
{
get
{
return m_voltageThreshold;
}
set
{
m_voltageThreshold = value;
}
}
///
/// Gets or sets the interval between adjacent calculations.
///
[ConnectionStringParameter,
Description("Define the interval between adjacent calculations. The default value is the frame-rate defined in the connection string for this Loss of Field."),
DefaultValue("")]
public int AnalysisInterval
{
get
{
return m_analysisInterval;
}
set
{
m_analysisInterval = value;
}
}
///
/// Returns the detailed status of the detector.
///
public override string Status
{
get
{
StringBuilder status = new StringBuilder();
status.AppendFormat(" Calculated Q-area value: {0}", m_qAreamVar);
status.AppendLine();
status.AppendFormat(" P-Set value: {0}", m_pSet);
status.AppendLine();
status.AppendFormat(" Q-Set value: {0}", m_qSet);
status.AppendLine();
status.AppendFormat(" Q-Area set value: {0}", m_qAreaSet);
status.AppendLine();
status.AppendFormat(" Voltage threshold: {0}", m_voltageThreshold);
status.AppendLine();
status.AppendFormat(" Calculation interval: {0}", m_analysisInterval);
status.AppendLine();
status.Append(base.Status);
return status.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Initializes the detector.
///
public override void Initialize()
{
base.Initialize();
Dictionary settings = Settings;
string setting;
// Load parameters
if (settings.TryGetValue("pSet", out setting))
m_pSet = double.Parse(setting);
else
m_pSet = -600;
if (settings.TryGetValue("qSet", out setting))
m_qSet = double.Parse(setting);
else
m_qSet = 200;
if (settings.TryGetValue("qAreaSet", out setting))
m_qAreaSet = double.Parse(setting);
else
m_qAreaSet = 500;
if (settings.TryGetValue("voltageThreshold", out setting))
m_voltageThreshold = double.Parse(setting);
else
m_voltageThreshold = 475000;
if (settings.TryGetValue("analysisInterval", out setting))
m_analysisInterval = int.Parse(setting);
else
m_analysisInterval = FramesPerSecond;
m_count = 0;
m_count1 = 0;
m_count2 = 0;
// Load needed measurement keys from defined InputMeasurementKeys
int index;
// Get expected voltage magnitude
index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.VPHM);
if (index < 0)
throw new InvalidOperationException("No voltage magnitude input measurement key was not found - this is a required input measurement for the loss of field detector.");
m_voltageMagnitude = InputMeasurementKeys[index];
// Get expected voltage angle
index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.VPHA);
if (index < 0)
throw new InvalidOperationException("No voltage angle input measurement key was not found - this is a required input measurement for the loss of field detector.");
m_voltageAngle = InputMeasurementKeys[index];
// Get expected current magnitude
index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.IPHM);
if (index < 0)
throw new InvalidOperationException("No current magnitude input measurement key was not found - this is a required input measurement for the loss of field detector.");
m_currentMagnitude = InputMeasurementKeys[index];
// Get expected current angle
index = InputMeasurementKeyTypes.IndexOf(signalType => signalType == SignalType.IPHA);
if (index < 0)
throw new InvalidOperationException("No current angle input measurement key was not found - this is a required input measurement for the loss of field detector.");
m_currentAngle = InputMeasurementKeys[index];
// Make sure only these phasor measurements are used as input
InputMeasurementKeys = new[] { m_voltageMagnitude, m_voltageAngle, m_currentMagnitude, m_currentAngle };
// Validate output measurements
if (OutputMeasurements.Length < Enum.GetValues(typeof(Output)).Length)
throw new InvalidOperationException("Not enough output measurements were specified for the loss of field detector, expecting measurements for \"Warning Signal Status (0 = Not Signaled, 1 = Signaled)\", \"Real Power\", \"Reactive Power\" and \"Q-Area Value\" - in this order.");
}
///
/// Publishes the of time-aligned collection of values that arrived within the
/// adapter's defined .
///
/// of measurements with the same timestamp that arrived within that are ready for processing.
/// Index of within a second ranging from zero to - 1.
protected override void PublishFrame(IFrame frame, int index)
{
// Increment frame counter
m_count++;
if (m_count % m_analysisInterval == 0)
{
IDictionary measurements = frame.Measurements;
IMeasurement measurement;
double voltageMagnitude, voltageAngle, currentMagnitude, currentAngle, realPower, reactivePower, deltaT;
bool warningSignaled = false;
m_count1 = m_count2;
m_count2 = m_count;
if (measurements.TryGetValue(m_voltageMagnitude, out measurement))
voltageMagnitude = measurement.AdjustedValue;
else
return;
if (measurements.TryGetValue(m_voltageAngle, out measurement))
voltageAngle = Angle.FromDegrees(measurement.AdjustedValue);
else
return;
if (measurements.TryGetValue(m_currentMagnitude, out measurement))
currentMagnitude = measurement.AdjustedValue;
else
return;
if (measurements.TryGetValue(m_currentAngle, out measurement))
currentAngle = Angle.FromDegrees(measurement.AdjustedValue);
else
return;
realPower = 3 * voltageMagnitude * currentMagnitude * Math.Cos(voltageAngle - currentAngle) / SI.Mega;
reactivePower = 3 * voltageMagnitude * currentMagnitude * Math.Sin(voltageAngle - currentAngle) / SI.Mega;
deltaT = (m_count2 - m_count1) / (double)FramesPerSecond;
if (realPower < m_pSet && reactivePower > m_qSet)
{
m_qAreamVar = m_qAreamVar + deltaT * (reactivePower - m_qSet);
if (m_qAreamVar > m_qAreaSet && voltageMagnitude < m_voltageThreshold / SqrtOf3)
{
warningSignaled = true;
OutputLOFWarning(realPower, reactivePower, m_qAreamVar);
}
}
else
m_qAreamVar = 0;
// Expose output measurement values
IMeasurement[] outputMeasurements = OutputMeasurements;
OnNewMeasurements(new IMeasurement[]
{
Measurement.Clone(outputMeasurements[(int)Output.WarningSignal], warningSignaled ? 1.0D : 0.0D, frame.Timestamp),
Measurement.Clone(outputMeasurements[(int)Output.RealPower], realPower, frame.Timestamp),
Measurement.Clone(outputMeasurements[(int)Output.ReactivePower], reactivePower, frame.Timestamp),
Measurement.Clone(outputMeasurements[(int)Output.QAreaValue], m_qAreamVar, frame.Timestamp)
});
}
}
private void OutputLOFWarning(double realPower, double reactivePower, double qAreamVar)
{
OnStatusMessage(MessageLevel.Info, $"Loss of Field Detected!\r\n Real power = {realPower}\r\n Reactive Power = {reactivePower}\r\n Q Area = {qAreamVar}\r\n");
}
#endregion
}
}