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