2020-08-29 22:40:49 +02:00
/** DFT hook.
*
* @ author Manuel Pitz < manuel . pitz @ eonerc . rwth - aachen . de >
2022-03-15 09:28:57 -04:00
* @ copyright 2014 - 2022 , Institute for Automation of Complex Power Systems , EONERC
2022-07-04 18:20:03 +02:00
* @ license Apache 2.0
2020-08-29 22:40:49 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <cstring>
2021-02-16 12:49:15 +01:00
# include <cinttypes>
2021-02-16 14:13:49 +01:00
# include <complex>
# include <vector>
2021-11-24 12:40:29 +01:00
# include <sstream>
2021-08-10 10:12:48 -04:00
# include <villas/timing.hpp>
2021-02-16 12:49:15 +01:00
2021-02-10 14:54:38 +01:00
# include <villas/dumper.hpp>
2020-08-29 22:40:49 +02:00
# include <villas/hook.hpp>
2021-08-10 10:12:48 -04:00
# include <villas/sample.hpp>
2020-08-29 22:40:49 +02:00
2021-06-29 12:40:59 -04:00
/* Uncomment to enable dumper of memory windows */
2021-06-29 15:39:31 +02:00
//#define DFT_MEM_DUMP
2021-06-25 15:38:18 +02:00
2020-08-29 22:40:49 +02:00
namespace villas {
namespace node {
2021-08-31 15:10:35 +02:00
class PmuDftHook : public MultiSignalHook {
2020-08-29 22:40:49 +02:00
protected :
2021-06-25 18:02:08 +02:00
enum class PaddingType {
2020-09-04 17:24:14 +02:00
ZERO ,
SIG_REPEAT
} ;
2021-06-25 18:02:08 +02:00
enum class WindowType {
2020-09-04 17:24:14 +02:00
NONE ,
FLATTOP ,
HANN ,
HAMMING
} ;
2022-03-27 19:02:39 +02:00
enum class EstimationType {
2021-06-25 18:02:08 +02:00
NONE ,
2022-03-27 19:02:39 +02:00
QUADRATIC ,
IpDFT
2021-06-25 18:02:08 +02:00
} ;
2022-03-22 18:20:39 +01:00
enum class TimeAlign {
LEFT ,
CENTER ,
RIGHT ,
} ;
2021-06-25 18:02:08 +02:00
struct Point {
double x ;
2022-03-27 19:02:39 +02:00
std : : complex < double > y ;
2021-06-25 18:02:08 +02:00
} ;
2022-03-22 18:20:39 +01:00
struct DftEstimate {
double amplitude ;
double frequency ;
double phase ;
} ;
2021-06-29 15:31:22 +02:00
struct Phasor {
double frequency ;
double amplitude ;
double phase ;
2021-06-29 12:40:59 -04:00
double rocof ; /**< Rate of change of frequency. */
2021-06-29 15:31:22 +02:00
} ;
2021-07-01 20:54:23 +02:00
2021-02-16 13:25:37 +01:00
enum WindowType windowType ;
enum PaddingType paddingType ;
2022-03-27 19:02:39 +02:00
enum EstimationType estType ;
2022-03-22 18:20:39 +01:00
enum TimeAlign timeAlignType ;
2021-06-25 18:02:08 +02:00
2022-03-22 18:20:39 +01:00
std : : vector < std : : vector < double > > smpMemoryData ;
std : : vector < timespec > smpMemoryTs ;
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
std : : vector < double > ppsMemory ;
# endif
std : : vector < std : : vector < std : : complex < double > > > matrix ;
std : : vector < std : : vector < std : : complex < double > > > results ;
2021-02-16 14:13:49 +01:00
std : : vector < double > filterWindowCoefficents ;
2021-06-29 12:40:59 -04:00
std : : vector < std : : vector < double > > absResults ;
std : : vector < double > absFrequencies ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
uint64_t calcCount ;
2021-02-16 13:14:26 +01:00
unsigned sampleRate ;
2021-06-29 12:40:59 -04:00
double startFrequency ;
2021-02-16 11:43:53 +01:00
double endFreqency ;
double frequencyResolution ;
2021-06-29 12:40:59 -04:00
unsigned rate ;
2021-06-15 18:23:14 +00:00
unsigned ppsIndex ;
2021-02-16 13:14:26 +01:00
unsigned windowSize ;
unsigned windowMultiplier ; /**< Multiplyer for the window to achieve frequency resolution */
2021-11-24 12:40:29 +01:00
unsigned freqCount ; /**< Number of requency bins that are calculated */
bool channelNameEnable ; /**< Rename the output values with channel name or only descriptive name */
2021-02-16 11:43:53 +01:00
uint64_t smpMemPos ;
uint64_t lastSequence ;
2021-02-10 13:06:19 +01:00
2020-08-29 22:40:49 +02:00
std : : complex < double > omega ;
2020-09-03 12:05:45 +02:00
2021-06-29 12:40:59 -04:00
double windowCorrectionFactor ;
struct timespec lastCalc ;
double nextCalc ;
2020-08-31 18:09:25 +02:00
2022-03-22 18:20:39 +01:00
std : : vector < Phasor > lastResult ;
2021-02-10 13:06:19 +01:00
2021-06-29 12:40:59 -04:00
std : : string dumperPrefix ;
bool dumperEnable ;
# ifdef DFT_MEM_DUMP
Dumper origSigSync ;
Dumper windowdSigSync ;
Dumper ppsSigSync ;
# endif
Dumper phasorRocof ;
Dumper phasorPhase ;
Dumper phasorAmplitude ;
Dumper phasorFreq ;
2021-07-19 12:09:07 +02:00
double angleUnitFactor ;
2022-03-22 19:15:03 +01:00
double phaseOffset ;
double frequencyOffset ;
double amplitudeOffset ;
double rocofOffset ;
2021-07-19 12:09:07 +02:00
2020-08-29 22:40:49 +02:00
public :
2021-08-10 10:12:48 -04:00
PmuDftHook ( Path * p , Node * n , int fl , int prio , bool en = true ) :
2021-07-07 08:58:26 +02:00
MultiSignalHook ( p , n , fl , prio , en ) ,
2021-02-16 12:53:27 +01:00
windowType ( WindowType : : NONE ) ,
paddingType ( PaddingType : : ZERO ) ,
2022-03-27 19:02:39 +02:00
estType ( EstimationType : : NONE ) ,
2022-03-22 18:20:39 +01:00
timeAlignType ( TimeAlign : : CENTER ) ,
smpMemoryData ( ) ,
smpMemoryTs ( ) ,
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
2021-06-25 18:02:08 +02:00
ppsMemory ( ) ,
2021-06-29 12:40:59 -04:00
# endif
matrix ( ) ,
results ( ) ,
2021-02-16 14:13:49 +01:00
filterWindowCoefficents ( ) ,
2021-06-29 12:40:59 -04:00
absResults ( ) ,
absFrequencies ( ) ,
calcCount ( 0 ) ,
2021-02-16 11:43:53 +01:00
sampleRate ( 0 ) ,
2021-06-29 12:40:59 -04:00
startFrequency ( 0 ) ,
2021-02-16 11:43:53 +01:00
endFreqency ( 0 ) ,
frequencyResolution ( 0 ) ,
2021-06-29 12:40:59 -04:00
rate ( 0 ) ,
2021-06-15 18:23:14 +00:00
ppsIndex ( 0 ) ,
2021-02-16 11:43:53 +01:00
windowSize ( 0 ) ,
windowMultiplier ( 0 ) ,
freqCount ( 0 ) ,
2021-11-24 12:40:29 +01:00
channelNameEnable ( 1 ) ,
2021-06-25 18:02:08 +02:00
smpMemPos ( 0 ) ,
lastSequence ( 0 ) ,
2021-06-29 12:40:59 -04:00
windowCorrectionFactor ( 0 ) ,
lastCalc ( { 0 , 0 } ) ,
nextCalc ( 0.0 ) ,
2022-03-22 18:20:39 +01:00
lastResult ( ) ,
2021-06-29 12:40:59 -04:00
dumperPrefix ( " /tmp/plot/ " ) ,
2021-07-01 20:54:23 +02:00
dumperEnable ( false ) ,
2021-06-25 18:02:08 +02:00
# ifdef DFT_MEM_DUMP
2021-06-29 12:40:59 -04:00
origSigSync ( dumperPrefix + " origSigSync " ) ,
windowdSigSync ( dumperPrefix + " windowdSigSync " ) ,
ppsSigSync ( dumperPrefix + " ppsSigSync " ) ,
2021-06-25 18:02:08 +02:00
# endif
2021-06-29 12:40:59 -04:00
phasorRocof ( dumperPrefix + " phasorRocof " ) ,
phasorPhase ( dumperPrefix + " phasorPhase " ) ,
phasorAmplitude ( dumperPrefix + " phasorAmplitude " ) ,
2021-07-19 12:09:07 +02:00
phasorFreq ( dumperPrefix + " phasorFreq " ) ,
2022-03-22 19:15:03 +01:00
angleUnitFactor ( 1 ) ,
phaseOffset ( 0.0 ) ,
frequencyOffset ( 0.0 ) ,
amplitudeOffset ( 0.0 ) ,
rocofOffset ( 0.0 )
2021-06-29 12:40:59 -04:00
{ }
2020-08-29 22:40:49 +02:00
2021-02-10 13:06:19 +01:00
virtual void prepare ( )
{
2021-07-12 12:39:43 +02:00
MultiSignalHook : : prepare ( ) ;
2021-07-09 19:05:40 +02:00
2021-07-01 20:54:23 +02:00
dumperEnable = logger - > level ( ) < = SPDLOG_LEVEL_DEBUG ;
2021-08-10 10:12:48 -04:00
signals - > clear ( ) ;
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2020-10-21 21:04:28 +02:00
/* Add signals */
2021-08-10 10:12:48 -04:00
auto freqSig = std : : make_shared < Signal > ( " frequency " , " Hz " , SignalType : : FLOAT ) ;
auto amplSig = std : : make_shared < Signal > ( " amplitude " , " V " , SignalType : : FLOAT ) ;
2022-04-08 17:28:57 +02:00
auto phaseSig = std : : make_shared < Signal > ( " phase " , ( angleUnitFactor ) ? " rad " : " deg " , SignalType : : FLOAT ) ; //angleUnitFactor==1 means rad
2021-08-10 10:12:48 -04:00
auto rocofSig = std : : make_shared < Signal > ( " rocof " , " Hz/s " , SignalType : : FLOAT ) ;
2020-09-04 17:24:14 +02:00
2020-10-21 21:04:28 +02:00
if ( ! freqSig | | ! amplSig | | ! phaseSig | | ! rocofSig )
throw RuntimeError ( " Failed to create new signals " ) ;
2020-09-04 21:06:23 +02:00
2021-08-10 10:12:48 -04:00
if ( channelNameEnable ) {
auto suffix = fmt : : format ( " _{} " , signalNames [ i ] ) ;
freqSig - > name + = suffix ;
amplSig - > name + = suffix ;
phaseSig - > name + = suffix ;
rocofSig - > name + = suffix ;
}
signals - > push_back ( freqSig ) ;
signals - > push_back ( amplSig ) ;
signals - > push_back ( phaseSig ) ;
signals - > push_back ( rocofSig ) ;
2021-06-25 15:38:18 +02:00
}
2021-06-15 18:23:14 +00:00
2021-06-25 18:02:08 +02:00
/* Initialize sample memory */
2022-03-22 18:20:39 +01:00
smpMemoryData . clear ( ) ;
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
smpMemoryData . emplace_back ( windowSize , 0.0 ) ;
}
smpMemoryTs . clear ( ) ;
for ( unsigned i = 0 ; i < windowSize ; i + + ) {
smpMemoryTs . push_back ( { 0 } ) ;
}
lastResult . clear ( ) ;
for ( unsigned i = 0 ; i < windowSize ; i + + ) {
lastResult . push_back ( { 0 , 0 , 0 , 0 } ) ;
}
2022-07-04 18:20:03 +02:00
2021-06-25 18:02:08 +02:00
2021-06-15 18:23:14 +00:00
# ifdef DFT_MEM_DUMP
/* Initialize temporary ppsMemory */
ppsMemory . clear ( ) ;
ppsMemory . resize ( windowSize , 0.0 ) ;
# endif
2021-02-16 13:16:38 +01:00
/* Calculate how much zero padding ist needed for a needed resolution */
2021-06-25 18:02:08 +02:00
windowMultiplier = ceil ( ( ( double ) sampleRate / windowSize ) / frequencyResolution ) ;
2022-03-27 19:02:39 +02:00
if ( windowMultiplier > 1 & & estType = = EstimationType : : IpDFT )
throw RuntimeError ( " Window multiplyer must be 1 if lpdft is used. Change resolution, window_size_factor or frequency range! Current window multiplyer factor is {} " , windowMultiplier ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
freqCount = ceil ( ( endFreqency - startFrequency ) / frequencyResolution ) + 1 ;
2020-09-10 14:57:45 +02:00
2021-02-16 13:16:38 +01:00
/* Initialize matrix of dft coeffients */
2021-06-29 12:40:59 -04:00
matrix . clear ( ) ;
2021-02-16 14:13:49 +01:00
for ( unsigned i = 0 ; i < freqCount ; i + + )
2021-06-29 12:40:59 -04:00
matrix . emplace_back ( windowSize * windowMultiplier , 0.0 ) ;
2021-02-10 13:06:19 +01:00
2021-06-25 18:02:08 +02:00
/* Initalize dft results matrix */
2021-06-29 12:40:59 -04:00
results . clear ( ) ;
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2021-06-29 12:40:59 -04:00
results . emplace_back ( freqCount , 0.0 ) ;
absResults . emplace_back ( freqCount , 0.0 ) ;
2021-06-25 18:02:08 +02:00
}
2021-02-16 14:13:49 +01:00
filterWindowCoefficents . resize ( windowSize ) ;
2021-02-16 10:27:08 +01:00
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < freqCount ; i + + )
2021-06-29 12:40:59 -04:00
absFrequencies . emplace_back ( startFrequency + i * frequencyResolution ) ;
2021-02-10 13:06:19 +01:00
2021-02-16 09:36:14 +01:00
generateDftMatrix ( ) ;
2021-02-16 13:16:38 +01:00
calculateWindow ( windowType ) ;
2020-08-29 22:40:49 +02:00
state = State : : PREPARED ;
}
2021-06-29 12:40:59 -04:00
virtual void parse ( json_t * json )
2020-08-29 22:40:49 +02:00
{
2021-07-12 12:39:43 +02:00
MultiSignalHook : : parse ( json ) ;
2020-09-04 17:24:14 +02:00
int ret ;
2021-06-29 12:40:59 -04:00
int windowSizeFactor = 1 ;
const char * paddingTypeC = nullptr ;
const char * windowTypeC = nullptr ;
2022-03-27 19:02:39 +02:00
const char * estimateTypeC = nullptr ;
2021-07-19 12:09:07 +02:00
const char * angleUnitC = nullptr ;
2022-03-22 18:20:39 +01:00
const char * timeAlignC = nullptr ;
2020-08-29 22:40:49 +02:00
2021-06-29 12:40:59 -04:00
json_error_t err ;
2020-10-21 21:04:28 +02:00
2020-08-29 22:40:49 +02:00
assert ( state ! = State : : STARTED ) ;
2021-06-29 12:40:59 -04:00
Hook : : parse ( json ) ;
2020-08-29 22:40:49 +02:00
2022-03-22 20:21:02 +01:00
ret = json_unpack_ex ( json , & err , 0 , " { s?: i, s?: F, s?: F, s?: F, s?: i, s?: i, s?: s, s?: s, s?: s, s?: i, s?: s, s?: b, s?: s, s?: F, s?: F, s?: F, s?: F} " ,
2021-02-16 12:47:56 +01:00
" sample_rate " , & sampleRate ,
2021-06-29 12:40:59 -04:00
" start_freqency " , & startFrequency ,
2021-02-16 12:47:56 +01:00
" end_freqency " , & endFreqency ,
" frequency_resolution " , & frequencyResolution ,
2021-06-29 12:40:59 -04:00
" dft_rate " , & rate ,
" window_size_factor " , & windowSizeFactor ,
2021-02-16 12:47:56 +01:00
" window_type " , & windowTypeC ,
" padding_type " , & paddingTypeC ,
2022-03-27 19:02:39 +02:00
" estimate_type " , & estimateTypeC ,
2021-07-19 12:09:07 +02:00
" pps_index " , & ppsIndex ,
2021-11-24 12:40:29 +01:00
" angle_unit " , & angleUnitC ,
2022-03-22 18:20:39 +01:00
" add_channel_name " , & channelNameEnable ,
2022-03-22 20:21:02 +01:00
" timestamp_align " , & timeAlignC ,
2022-03-22 19:15:03 +01:00
" phase_offset " , & phaseOffset ,
" amplitude_offset " , & amplitudeOffset ,
" frequency_offset " , & frequencyOffset ,
" rocof_offset " , & rocofOffset
2020-09-04 17:24:14 +02:00
) ;
2021-02-16 11:53:42 +01:00
if ( ret )
2021-06-29 12:40:59 -04:00
throw ConfigError ( json , err , " node-config-hook-dft " ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
windowSize = sampleRate * windowSizeFactor / ( double ) rate ;
2022-03-22 20:21:02 +01:00
logger - > info ( " Set windows size to {} samples which fits {} times the rate {}s " , windowSize , windowSizeFactor , 1.0 / rate ) ;
2021-06-29 15:39:31 +02:00
2021-06-29 12:40:59 -04:00
if ( ! windowTypeC )
2021-02-16 14:15:14 +01:00
logger - > info ( " No Window type given, assume no windowing " ) ;
2021-02-16 11:43:53 +01:00
else if ( strcmp ( windowTypeC , " flattop " ) = = 0 )
2021-02-16 12:53:27 +01:00
windowType = WindowType : : FLATTOP ;
2021-02-16 11:43:53 +01:00
else if ( strcmp ( windowTypeC , " hamming " ) = = 0 )
2021-02-16 12:53:27 +01:00
windowType = WindowType : : HAMMING ;
2021-02-16 11:43:53 +01:00
else if ( strcmp ( windowTypeC , " hann " ) = = 0 )
2021-02-16 12:53:27 +01:00
windowType = WindowType : : HANN ;
2021-06-29 12:40:59 -04:00
else
throw ConfigError ( json , " node-config-hook-dft-window-type " , " Invalid window type: {} " , windowTypeC ) ;
2020-09-04 17:24:14 +02:00
2022-03-22 18:20:39 +01:00
if ( ! timeAlignC )
logger - > info ( " No timestamp alignment defined. Assume alignment center " ) ;
else if ( strcmp ( timeAlignC , " left " ) = = 0 )
timeAlignType = TimeAlign : : LEFT ;
else if ( strcmp ( timeAlignC , " center " ) = = 0 )
timeAlignType = TimeAlign : : CENTER ;
else if ( strcmp ( timeAlignC , " right " ) = = 0 )
timeAlignType = TimeAlign : : RIGHT ;
else
throw ConfigError ( json , " node-config-hook-dft-timestamp-alignment " , " Timestamp alignment {} not recognized " , timeAlignC ) ;
2021-07-19 12:09:07 +02:00
if ( ! angleUnitC )
logger - > info ( " No angle type given, assume rad " ) ;
else if ( strcmp ( angleUnitC , " rad " ) = = 0 )
angleUnitFactor = 1 ;
else if ( strcmp ( angleUnitC , " degree " ) = = 0 )
angleUnitFactor = 180 / M_PI ;
else
throw ConfigError ( json , " node-config-hook-dft-angle-unit " , " Angle unit {} not recognized " , angleUnitC ) ;
2021-06-29 12:40:59 -04:00
if ( ! paddingTypeC )
2021-02-16 14:15:14 +01:00
logger - > info ( " No Padding type given, assume no zeropadding " ) ;
2021-07-19 12:09:37 +02:00
else if ( strcmp ( paddingTypeC , " zero " ) = = 0 )
paddingType = PaddingType : : ZERO ;
2021-02-16 11:43:53 +01:00
else if ( strcmp ( paddingTypeC , " signal_repeat " ) = = 0 )
2021-02-16 12:53:27 +01:00
paddingType = PaddingType : : SIG_REPEAT ;
2021-06-29 12:40:59 -04:00
else
throw ConfigError ( json , " node-config-hook-dft-padding-type " , " Padding type {} not recognized " , paddingTypeC ) ;
2020-09-04 17:24:14 +02:00
2022-03-27 19:02:39 +02:00
if ( ! estimateTypeC ) {
2021-06-25 18:02:08 +02:00
logger - > info ( " No Frequency estimation type given, assume no none " ) ;
2022-03-27 19:02:39 +02:00
estType = EstimationType : : NONE ;
} else if ( strcmp ( estimateTypeC , " quadratic " ) = = 0 )
estType = EstimationType : : QUADRATIC ;
else if ( strcmp ( estimateTypeC , " ipdft " ) = = 0 )
estType = EstimationType : : IpDFT ;
2021-06-29 12:40:59 -04:00
state = State : : PARSED ;
}
virtual void check ( )
{
assert ( state = = State : : PARSED ) ;
2021-02-16 11:53:42 +01:00
if ( endFreqency < 0 | | endFreqency > sampleRate )
2021-06-29 12:40:59 -04:00
throw RuntimeError ( " End frequency must be smaller than sampleRate {} " , sampleRate ) ;
2021-02-16 11:53:42 +01:00
2021-06-29 12:40:59 -04:00
if ( frequencyResolution > ( double ) sampleRate / windowSize )
throw RuntimeError ( " The maximum frequency resolution with smaple_rate:{} and window_site:{} is {} " , sampleRate , windowSize , ( ( double ) sampleRate / windowSize ) ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
state = State : : CHECKED ;
2020-08-29 22:40:49 +02:00
}
2021-08-10 10:12:48 -04:00
virtual Hook : : Reason process ( struct Sample * smp )
2020-08-29 22:40:49 +02:00
{
assert ( state = = State : : STARTED ) ;
2021-02-16 13:14:26 +01:00
2021-07-07 08:58:26 +02:00
/* Update sample memory */
unsigned i = 0 ;
2022-03-22 18:20:39 +01:00
for ( auto index : signalIndices ) {
smpMemoryData [ i + + ] [ smpMemPos % windowSize ] = smp - > data [ index ] . f ;
}
smpMemoryTs [ smpMemPos % windowSize ] = smp - > ts . origin ;
2021-02-16 10:19:04 +01:00
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
ppsMemory [ smpMemPos % windowSize ] = smp - > data [ ppsIndex ] . f ;
# endif
2020-09-01 21:18:00 +02:00
2021-06-29 12:40:59 -04:00
bool run = false ;
2022-03-22 20:21:02 +01:00
double smpNsec = smp - > ts . origin . tv_sec * 1e9 + smp - > ts . origin . tv_nsec ;
2021-06-25 15:38:18 +02:00
2022-03-22 20:21:02 +01:00
if ( smpNsec > nextCalc ) {
run = true ;
nextCalc = ( smp - > ts . origin . tv_sec + ( ( ( calcCount % rate ) + 1 ) / ( double ) rate ) ) * 1e9 ;
2021-06-16 16:36:18 +00:00
}
2021-02-10 13:06:19 +01:00
2021-06-29 12:40:59 -04:00
if ( run ) {
lastCalc = smp - > ts . origin ;
2021-06-15 18:23:14 +00:00
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
double tmpPPSWindow [ windowSize ] ;
2021-06-23 20:36:39 +02:00
2021-06-29 12:40:59 -04:00
for ( unsigned i = 0 ; i < windowSize ; i + + )
tmpPPSWindow [ i ] = ppsMemory [ ( i + smpMemPos ) % windowSize ] ;
if ( dumperEnable )
ppsSigSync . writeDataBinary ( windowSize , tmpPPSWindow ) ;
# endif
2021-06-15 18:23:14 +00:00
2021-06-23 20:37:11 +02:00
# pragma omp parallel for
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2021-06-29 15:31:22 +02:00
Phasor currentResult = { 0 , 0 , 0 , 0 } ;
2021-06-29 12:40:59 -04:00
2022-03-22 18:20:39 +01:00
calculateDft ( PaddingType : : ZERO , smpMemoryData [ i ] , results [ i ] , smpMemPos ) ;
2022-07-04 18:20:03 +02:00
2021-06-23 20:36:39 +02:00
unsigned maxPos = 0 ;
2022-03-27 19:02:39 +02:00
double absAmplitude = 0 ;
2022-07-04 18:20:03 +02:00
2022-03-27 19:02:39 +02:00
for ( unsigned j = 0 ; j < freqCount ; j + + ) {
if ( absAmplitude < abs ( results [ i ] [ j ] ) ) {
absAmplitude = abs ( results [ i ] [ j ] ) ;
2021-06-23 20:25:46 +02:00
maxPos = j ;
2020-10-21 21:04:28 +02:00
}
2020-09-04 21:06:23 +02:00
}
2020-10-12 19:27:32 +02:00
2022-03-27 19:02:39 +02:00
int multiplier = paddingType = = PaddingType : : ZERO
? 1
: windowMultiplier ;
DftEstimate dftEstimate = { 0 } ;
if ( ( maxPos < 1 | | maxPos > = freqCount - 1 ) & & estType ! = EstimationType : : NONE ) {
logger - > warn ( " Maximum frequency bin lies on window boundary. Using non-estimated results! " ) ;
dftEstimate = noEstimation ( { 0 } , { absFrequencies [ maxPos + 0 ] , results [ i ] [ maxPos + 0 ] } , { 0 } , maxPos , startFrequency , frequencyResolution , multiplier , windowSize , windowCorrectionFactor ) ;
} else {
Point a = { absFrequencies [ maxPos - 1 ] , results [ i ] [ maxPos - 1 ] } ;
Point b = { absFrequencies [ maxPos + 0 ] , results [ i ] [ maxPos + 0 ] } ;
Point c = { absFrequencies [ maxPos + 1 ] , results [ i ] [ maxPos + 1 ] } ;
2022-07-04 18:20:03 +02:00
2022-03-27 19:02:39 +02:00
if ( estType = = EstimationType : : QUADRATIC )
dftEstimate = quadraticEstimation ( a , b , c , maxPos , startFrequency , frequencyResolution , multiplier , windowSize , windowCorrectionFactor ) ;
else if ( estType = = EstimationType : : IpDFT )
dftEstimate = lpdftEstimation ( a , b , c , maxPos , startFrequency , frequencyResolution , multiplier , windowSize , windowCorrectionFactor ) ;
else
dftEstimate = noEstimation ( { 0 } , b , { 0 } , maxPos , startFrequency , frequencyResolution , multiplier , windowSize , windowCorrectionFactor ) ;
2021-06-25 18:02:08 +02:00
}
2022-03-27 19:02:39 +02:00
currentResult . frequency = dftEstimate . frequency ;
currentResult . amplitude = dftEstimate . amplitude ;
2022-07-04 18:20:03 +02:00
currentResult . phase = dftEstimate . phase * angleUnitFactor ; //convert phase from rad to deg
2021-06-16 14:00:52 +00:00
2022-04-08 17:28:57 +02:00
if ( windowSize < = smpMemPos ) {
2020-10-21 21:04:28 +02:00
2022-03-22 19:15:03 +01:00
smp - > data [ i * 4 + 0 ] . f = currentResult . frequency + frequencyOffset ; /* Frequency */
smp - > data [ i * 4 + 1 ] . f = ( currentResult . amplitude / pow ( 2 , 0.5 ) ) + amplitudeOffset ; /* Amplitude */
smp - > data [ i * 4 + 2 ] . f = currentResult . phase + phaseOffset ; /* Phase */
smp - > data [ i * 4 + 3 ] . f = ( ( currentResult . frequency - lastResult [ i ] . frequency ) * ( double ) rate ) + rocofOffset ; /* ROCOF */ ;
2022-03-22 18:20:39 +01:00
lastResult [ i ] = currentResult ;
2021-06-23 20:34:40 +02:00
}
}
2021-06-29 12:40:59 -04:00
// The following is a debug output and currently only for channel 0
if ( dumperEnable & & windowSize * 5 < smpMemPos ) {
phasorFreq . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 0 ] . f ) ) ;
phasorPhase . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 2 ] . f ) ) ;
phasorAmplitude . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 1 ] . f ) ) ;
phasorRocof . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 3 ] . f ) ) ;
2021-02-10 13:06:19 +01:00
}
2021-06-23 20:27:09 +02:00
2021-07-07 08:58:26 +02:00
smp - > length = windowSize < smpMemPos ? signalIndices . size ( ) * 4 : 0 ;
2021-06-23 20:27:09 +02:00
2022-03-31 15:54:07 +00:00
if ( smpMemPos > = windowSize ) {
unsigned tsPos = 0 ;
if ( timeAlignType = = TimeAlign : : RIGHT )
tsPos = smpMemPos ;
else if ( timeAlignType = = TimeAlign : : LEFT )
tsPos = smpMemPos - windowSize ;
else if ( timeAlignType = = TimeAlign : : CENTER ) {
tsPos = smpMemPos - ( windowSize / 2 ) ;
}
smp - > ts . origin = smpMemoryTs [ tsPos % windowSize ] ;
2022-03-22 18:20:39 +01:00
}
2022-03-31 15:54:07 +00:00
2021-06-29 12:40:59 -04:00
calcCount + + ;
2020-10-12 19:27:32 +02:00
}
2021-02-10 13:06:19 +01:00
2021-06-25 18:02:08 +02:00
if ( smp - > sequence - lastSequence > 1 )
2021-02-16 14:15:14 +01:00
logger - > warn ( " Calculation is not Realtime. {} sampled missed " , smp - > sequence - lastSequence ) ;
2020-09-04 21:06:23 +02:00
2021-02-16 11:43:53 +01:00
lastSequence = smp - > sequence ;
2020-09-04 17:59:29 +02:00
2022-03-31 15:54:07 +00:00
if ( smpMemPos > = 2 * windowSize ) { //reset smpMemPos if greater than twice the window. Important to handle init
smpMemPos = windowSize ;
}
2022-04-08 17:28:57 +02:00
smpMemPos + + ;
2021-06-29 12:40:59 -04:00
if ( run & & windowSize < smpMemPos )
2020-10-21 21:04:28 +02:00
return Reason : : OK ;
2020-10-12 19:27:32 +02:00
2020-10-21 21:04:28 +02:00
return Reason : : SKIP_SAMPLE ;
2020-08-29 22:40:49 +02:00
}
2021-06-25 18:02:08 +02:00
/**
* This function generates the furie coeffients for the calculateDft function
*/
2021-02-16 13:16:38 +01:00
void generateDftMatrix ( )
{
2020-08-29 22:40:49 +02:00
using namespace std : : complex_literals ;
2021-02-16 13:16:38 +01:00
omega = exp ( ( - 2 i * M_PI ) / ( double ) ( windowSize * windowMultiplier ) ) ;
2021-06-29 12:40:59 -04:00
unsigned startBin = floor ( startFrequency / frequencyResolution ) ;
2021-06-15 18:23:14 +00:00
for ( unsigned i = 0 ; i < freqCount ; i + + ) {
for ( unsigned j = 0 ; j < windowSize * windowMultiplier ; j + + )
matrix [ i ] [ j ] = pow ( omega , ( i + startBin ) * j ) ;
}
}
/**
* This function calculates the discrete furie transform of the input signal
*/
void calculateDft ( enum PaddingType padding , std : : vector < double > & ringBuffer , std : : vector < std : : complex < double > > & results , unsigned ringBufferPos )
{
/* RingBuffer size needs to be equal to windowSize
* prepare sample window The following parts can be combined */
double tmpSmpWindow [ windowSize ] ;
for ( unsigned i = 0 ; i < windowSize ; i + + )
2021-08-10 10:12:48 -04:00
tmpSmpWindow [ i ] = ringBuffer [ ( i + ringBufferPos ) % windowSize ] * filterWindowCoefficents [ i ] ;
2021-06-15 18:23:14 +00:00
2021-06-23 20:18:12 +02:00
# ifdef DFT_MEM_DUMP
2021-06-29 12:40:59 -04:00
if ( dumperEnable )
origSigSync . writeDataBinary ( windowSize , tmpSmpWindow ) ;
2021-06-23 20:18:12 +02:00
# endif
2020-10-12 19:27:32 +02:00
2021-02-16 13:14:26 +01:00
for ( unsigned i = 0 ; i < freqCount ; i + + ) {
2021-06-25 18:02:08 +02:00
results [ i ] = 0 ;
2021-07-01 20:54:23 +02:00
2021-02-16 13:14:26 +01:00
for ( unsigned j = 0 ; j < windowSize * windowMultiplier ; j + + ) {
2021-02-16 12:53:27 +01:00
if ( padding = = PaddingType : : ZERO ) {
2021-07-01 18:29:22 +02:00
if ( j < windowSize )
2021-06-29 12:40:59 -04:00
results [ i ] + = tmpSmpWindow [ j ] * matrix [ i ] [ j ] ;
2021-02-10 13:06:19 +01:00
else
2021-06-25 18:02:08 +02:00
results [ i ] + = 0 ;
2020-10-21 21:04:28 +02:00
}
2021-02-16 13:16:38 +01:00
else if ( padding = = PaddingType : : SIG_REPEAT ) /* Repeat samples */
2021-06-29 12:40:59 -04:00
results [ i ] + = tmpSmpWindow [ j % windowSize ] * matrix [ i ] [ j ] ;
2020-08-29 22:40:49 +02:00
}
}
}
2020-08-31 11:48:35 +02:00
2021-06-25 18:02:08 +02:00
/**
* This function prepares the selected window coefficents
*/
2021-02-16 13:16:38 +01:00
void calculateWindow ( enum WindowType windowTypeIn )
{
switch ( windowTypeIn ) {
case WindowType : : FLATTOP :
for ( unsigned i = 0 ; i < windowSize ; i + + ) {
2021-06-25 18:02:08 +02:00
filterWindowCoefficents [ i ] = 0.21557895
- 0.41663158 * cos ( 2 * M_PI * i / ( windowSize ) )
2021-02-16 13:16:38 +01:00
+ 0.277263158 * cos ( 4 * M_PI * i / ( windowSize ) )
- 0.083578947 * cos ( 6 * M_PI * i / ( windowSize ) )
+ 0.006947368 * cos ( 8 * M_PI * i / ( windowSize ) ) ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
break ;
2020-09-04 17:24:14 +02:00
2021-02-16 13:16:38 +01:00
case WindowType : : HAMMING :
case WindowType : : HANN : {
double a0 = 0.5 ; /* This is the hann window */
if ( windowTypeIn = = WindowType : : HAMMING )
a0 = 25. / 46 ;
for ( unsigned i = 0 ; i < windowSize ; i + + ) {
filterWindowCoefficents [ i ] = a0 - ( 1 - a0 ) * cos ( 2 * M_PI * i / ( windowSize ) ) ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
break ;
2020-09-03 12:05:45 +02:00
}
2021-02-16 13:16:38 +01:00
default :
for ( unsigned i = 0 ; i < windowSize ; i + + ) {
filterWindowCoefficents [ i ] = 1 ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
break ;
2020-08-31 11:48:35 +02:00
}
2021-02-16 13:16:38 +01:00
2021-06-29 12:40:59 -04:00
windowCorrectionFactor / = windowSize ;
2020-08-31 11:48:35 +02:00
}
2021-06-25 18:02:08 +02:00
2022-03-27 19:02:39 +02:00
DftEstimate noEstimation ( const Point & a , const Point & b , const Point & c , unsigned maxFBin , double startFrequency , double frequencyResolution , double multiplier , double windowSize , double windowCorrectionFactor )
{
// Frequency estimation
double f_est = startFrequency + maxFBin * frequencyResolution ;
// Amplitude estimation
double a_est = abs ( b . y ) * 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
//Phase estimation
double phase_est = atan2 ( b . y . imag ( ) , b . y . real ( ) ) ;
return { a_est , f_est , phase_est } ;
}
/**
* This function is calculation the IpDFT based on the following paper :
2022-07-04 18:20:03 +02:00
*
2022-03-27 19:02:39 +02:00
* https : //ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7980868&tag=1
2022-07-04 18:20:03 +02:00
*
2022-03-27 19:02:39 +02:00
*/
DftEstimate lpdftEstimation ( const Point & a , const Point & b , const Point & c , unsigned maxFBin , double startFrequency , double frequencyResolution , double multiplier , double windowSize , double windowCorrectionFactor )
{
double delta = 0 ;
//paper eq 8
if ( abs ( c . y ) > abs ( a . y ) ) {
delta = 1. * ( 2. * abs ( c . y ) - abs ( b . y ) ) / ( abs ( b . y ) + abs ( c . y ) ) ;
} else {
2022-07-04 18:20:03 +02:00
delta = - 1. * ( 2. * abs ( a . y ) - abs ( b . y ) ) / ( abs ( b . y ) + abs ( a . y ) ) ;
2022-03-27 19:02:39 +02:00
}
2022-07-04 18:20:03 +02:00
2022-03-27 19:02:39 +02:00
// Frequency estimation (eq 4)
double f_est = startFrequency + ( ( double ) maxFBin + delta ) * frequencyResolution ;
// Amplitude estimation (eq 9)
double a_est = abs ( b . y ) * abs ( ( M_PI * delta ) / sin ( M_PI * delta ) ) * abs ( pow ( delta , 2 ) - 1 ) ;
a_est * = 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
//Phase estimation (eq 10)
double phase_est = atan2 ( b . y . imag ( ) , b . y . real ( ) ) - M_PI * delta ;
return { a_est , f_est , phase_est } ;
}
2021-06-25 18:02:08 +02:00
/**
* This function is calculating the mximum based on a quadratic interpolation
2021-06-29 12:40:59 -04:00
*
2021-06-25 18:02:08 +02:00
* This function is based on the following paper :
2022-03-22 18:20:39 +01:00
* https : //mgasior.web.cern.ch/pap/biw2004.pdf (equation 10) (freq estimation)
2021-06-25 18:02:08 +02:00
* https : //dspguru.com/dsp/howtos/how-to-interpolate-fft-peak/
*/
2022-03-27 19:02:39 +02:00
DftEstimate quadraticEstimation ( const Point & a , const Point & b , const Point & c , unsigned maxFBin , double startFrequency , double frequencyResolution , double multiplier , double windowSize , double windowCorrectionFactor )
2021-06-25 18:02:08 +02:00
{
2022-03-27 19:02:39 +02:00
using namespace std : : complex_literals ;
double ay_abs = abs ( a . y ) * 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
double by_abs = abs ( b . y ) * 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
double cy_abs = abs ( c . y ) * 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
2021-06-25 18:02:08 +02:00
// Frequency estimation
2022-03-27 19:02:39 +02:00
double maxBinEst = ( double ) maxFBin + ( cy_abs - ay_abs ) / ( 2 * ( 2 * by_abs - ay_abs - cy_abs ) ) ;
2022-03-22 18:20:39 +01:00
double f_est = startFrequency + maxBinEst * frequencyResolution ; // convert bin to frequency
2021-06-25 18:02:08 +02:00
// Amplitude estimation
2022-03-27 19:02:39 +02:00
double f = ( a . x * ( by_abs - cy_abs ) + b . x * ( cy_abs - ay_abs ) + c . x * ( ay_abs - by_abs ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( c . x - b . x ) ) ;
double g = ( pow ( a . x , 2 ) * ( by_abs - cy_abs ) + pow ( b . x , 2 ) * ( cy_abs - ay_abs ) + pow ( c . x , 2 ) * ( ay_abs - by_abs ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( b . x - c . x ) ) ;
double h = ( pow ( a . x , 2 ) * ( b . x * cy_abs - c . x * by_abs ) + a . x * ( pow ( c . x , 2 ) * by_abs - pow ( b . x , 2 ) * cy_abs ) + b . x * c . x * ay_abs * ( b . x - c . x ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( b . x - c . x ) ) ;
2022-03-22 18:20:39 +01:00
double a_est = f * pow ( f_est , 2 ) + g * f_est + h ;
//Phase estimation
2022-03-27 19:02:39 +02:00
double phase_est = atan2 ( b . y . imag ( ) , b . y . real ( ) ) ;
2021-06-25 18:02:08 +02:00
2022-03-22 18:20:39 +01:00
return { a_est , f_est , phase_est } ;
2021-06-25 18:02:08 +02:00
}
2020-08-29 22:40:49 +02:00
} ;
/* Register hook */
2021-09-13 11:41:13 +02:00
static char n [ ] = " pmu_dft " ;
2020-08-29 22:40:49 +02:00
static char d [ ] = " This hook calculates the dft on a window " ;
2021-08-31 15:10:35 +02:00
static HookPlugin < PmuDftHook , n , d , ( int ) Hook : : Flags : : NODE_READ | ( int ) Hook : : Flags : : NODE_WRITE | ( int ) Hook : : Flags : : PATH > p ;
2020-08-29 22:40:49 +02:00
} /* namespace node */
} /* namespace villas */