2023-08-31 11:25:01 +02:00
/* ipDFT PMU hook.
2022-06-01 18:15:29 +02:00
*
2023-08-31 11:25:01 +02:00
* Author : Manuel Pitz < manuel . pitz @ eonerc . rwth - aachen . de >
* SPDX - FileCopyrightText : 2014 - 2023 Institute for Automation of Complex Power Systems , RWTH Aachen University
* SPDX - License - Identifier : Apache - 2.0
*/
2022-06-01 18:15:29 +02:00
# include <villas/hooks/pmu.hpp>
namespace villas {
namespace node {
class IpDftPmuHook : public PmuHook {
protected :
std : : complex < double > omega ;
std : : vector < std : : vector < std : : complex < double > > > dftMatrix ;
std : : vector < std : : complex < double > > dftResult ;
2023-08-31 11:25:01 +02:00
unsigned frequencyCount ; // Number of requency bins that are calculated
double estimationRange ; // The range around nominalFreq used for estimation
2022-06-01 18:15:29 +02:00
public :
IpDftPmuHook ( Path * p , Node * n , int fl , int prio , bool en = true ) :
PmuHook ( p , n , fl , prio , en ) ,
frequencyCount ( 0 ) ,
estimationRange ( 0 )
{ }
void prepare ( )
{
PmuHook : : prepare ( ) ;
const double startFrequency = nominalFreq - estimationRange ;
const double endFrequency = nominalFreq + estimationRange ;
const double frequencyResolution = ( double ) sampleRate / windowSize ;
frequencyCount = ceil ( ( endFrequency - startFrequency ) / frequencyResolution ) ;
2023-08-31 11:25:01 +02:00
// Initialize matrix of dft coeffients
2022-06-01 18:15:29 +02:00
dftMatrix . clear ( ) ;
for ( unsigned i = 0 ; i < frequencyCount ; i + + )
dftMatrix . emplace_back ( windowSize , 0.0 ) ;
using namespace std : : complex_literals ;
omega = exp ( ( - 2 i * M_PI ) / ( double ) ( windowSize ) ) ;
unsigned startBin = floor ( startFrequency / frequencyResolution ) ;
for ( unsigned i = 0 ; i < frequencyCount ; i + + ) {
for ( unsigned j = 0 ; j < windowSize ; j + + )
dftMatrix [ i ] [ j ] = pow ( omega , ( i + startBin ) * j ) ;
dftResult . push_back ( 0.0 ) ;
}
}
void parse ( json_t * json )
{
PmuHook : : parse ( json ) ;
int ret ;
json_error_t err ;
assert ( state ! = State : : STARTED ) ;
Hook : : parse ( json ) ;
ret = json_unpack_ex ( json , & err , 0 , " { s?: F} " ,
" estimation_range " , & estimationRange
) ;
if ( ret )
throw ConfigError ( json , err , " node-config-hook-ip-dft-pmu " ) ;
if ( estimationRange < = 0 )
throw ConfigError ( json , " node-config-hook-ip-dft-pmu-estimation_range " , " Estimation range cannot be less or equal than 0 tried to set {} " , estimationRange ) ;
}
PmuHook : : Phasor estimatePhasor ( dsp : : CosineWindow < double > * window , PmuHook : : Phasor lastPhasor )
{
PmuHook : : Phasor phasor = { 0 } ;
2023-08-31 11:25:01 +02:00
// Calculate DFT
2022-06-01 18:15:29 +02:00
for ( unsigned i = 0 ; i < frequencyCount ; i + + ) {
dftResult [ i ] = 0 ;
const unsigned size = ( * window ) . size ( ) ;
for ( unsigned j = 0 ; j < size ; j + + )
dftResult [ i ] + = ( * window ) [ j ] * dftMatrix [ i ] [ j ] ;
}
2023-08-31 11:25:01 +02:00
// End calculate DFT
2022-06-01 18:15:29 +02:00
2023-08-31 11:25:01 +02:00
// Find max bin
2022-06-01 18:15:29 +02:00
unsigned maxBin = 0 ;
double absAmplitude = 0 ;
for ( unsigned j = 0 ; j < frequencyCount ; j + + ) {
if ( absAmplitude < abs ( dftResult [ j ] ) ) {
absAmplitude = abs ( dftResult [ j ] ) ;
maxBin = j ;
}
}
2023-08-31 11:25:01 +02:00
// End find max bin
2022-06-01 18:15:29 +02:00
if ( maxBin = = 0 | | maxBin = = ( frequencyCount - 1 ) ) {
logger - > warn ( " Maximum frequency bin lies on window boundary. Using non-estimated results! " ) ;
2023-09-04 18:16:15 +02:00
//TODO: add handling to not forward this phasor!!
2022-06-01 18:15:29 +02:00
} else {
const double startFrequency = nominalFreq - estimationRange ;
const double frequencyResolution = ( double ) sampleRate / windowSize ;
double a = abs ( dftResult [ maxBin - 1 ] ) ;
double b = abs ( dftResult [ maxBin + 0 ] ) ;
double c = abs ( dftResult [ maxBin + 1 ] ) ;
double bPhase = atan2 ( dftResult [ maxBin ] . imag ( ) , dftResult [ maxBin ] . real ( ) ) ;
2023-08-31 11:25:01 +02:00
// Estimate phasor
// Based on https://ieeexplore.ieee.org/document/7980868
2022-06-01 18:15:29 +02:00
double delta = 0 ;
2023-08-31 11:25:01 +02:00
// Paper eq 8
2022-06-01 18:15:29 +02:00
if ( c > a ) {
delta = 1. * ( 2. * c - b ) / ( b + c ) ;
} else {
delta = - 1. * ( 2. * a - b ) / ( b + a ) ;
}
2023-08-31 11:25:01 +02:00
// Frequency estimation (eq 4)
2022-06-01 18:15:29 +02:00
phasor . frequency = startFrequency + ( ( double ) maxBin + delta ) * frequencyResolution ;
2023-08-31 11:25:01 +02:00
// Amplitude estimation (eq 9)
2022-06-01 18:15:29 +02:00
phasor . amplitude = b * abs ( ( M_PI * delta ) / sin ( M_PI * delta ) ) * abs ( pow ( delta , 2 ) - 1 ) ;
phasor . amplitude * = 2 / ( windowSize * window - > getCorrectionFactor ( ) ) ;
2023-08-31 11:25:01 +02:00
// Phase estimation (eq 10)
2022-06-01 18:15:29 +02:00
phasor . phase = bPhase - M_PI * delta ;
2023-08-31 11:25:01 +02:00
// ROCOF estimation
2022-06-01 18:15:29 +02:00
phasor . rocof = ( ( phasor . frequency - lastPhasor . frequency ) * ( double ) phasorRate ) ;
2023-08-31 11:25:01 +02:00
// End estimate phasor
2022-06-01 18:15:29 +02:00
}
2023-08-31 11:25:01 +02:00
if ( lastPhasor . frequency ! = 0 ) // Check if we already calculated a phasor before
2022-06-01 18:15:29 +02:00
phasor . valid = Status : : VALID ;
return phasor ;
}
} ;
2023-08-31 11:25:01 +02:00
// Register hook
2022-06-01 18:15:29 +02:00
static char n [ ] = " ip-dft-pmu " ;
static char d [ ] = " This hook calculates a phasor based on ipDFT " ;
static HookPlugin < IpDftPmuHook , n , d , ( int ) Hook : : Flags : : NODE_READ | ( int ) Hook : : Flags : : NODE_WRITE | ( int ) Hook : : Flags : : PATH > p ;
2023-08-28 09:34:02 +02:00
} // namespace node
} // namespace villas