1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-16 00:00:02 +01:00
VILLASnode/lib/hooks/pmu_dft.cpp

675 lines
21 KiB
C++
Raw Normal View History

/** 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
*********************************************************************************/
#include <cstring>
2021-02-16 12:49:15 +01:00
#include <cinttypes>
#include <complex>
#include <vector>
#include <sstream>
#include <villas/timing.hpp>
2021-02-16 12:49:15 +01:00
2021-02-10 14:54:38 +01:00
#include <villas/dumper.hpp>
#include <villas/hook.hpp>
#include <villas/sample.hpp>
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
namespace villas {
namespace node {
class PmuDftHook : public MultiSignalHook {
protected:
enum class PaddingType {
ZERO,
SIG_REPEAT
};
enum class WindowType {
NONE,
FLATTOP,
HANN,
HAMMING
};
enum class EstimationType {
NONE,
QUADRATIC,
IpDFT
};
2022-03-22 18:20:39 +01:00
enum class TimeAlign {
LEFT,
CENTER,
RIGHT,
};
struct Point {
double x;
std::complex<double> y;
};
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;
enum EstimationType estType;
2022-03-22 18:20:39 +01:00
enum TimeAlign timeAlignType;
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;
std::vector<double> filterWindowCoefficents;
2021-06-29 12:40:59 -04:00
std::vector<std::vector<double>> absResults;
std::vector<double> absFrequencies;
2021-06-29 12:40:59 -04:00
uint64_t calcCount;
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;
unsigned ppsIndex;
unsigned windowSize;
unsigned windowMultiplier; /**< Multiplyer for the window to achieve frequency resolution */
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
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;
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;
double angleUnitFactor;
double phaseOffset;
double frequencyOffset;
double amplitudeOffset;
double rocofOffset;
public:
PmuDftHook(Path *p, Node *n, int fl, int prio, bool en = true) :
MultiSignalHook(p, n, fl, prio, en),
2021-02-16 12:53:27 +01:00
windowType(WindowType::NONE),
paddingType(PaddingType::ZERO),
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
ppsMemory(),
2021-06-29 12:40:59 -04:00
#endif
matrix(),
results(),
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),
ppsIndex(0),
2021-02-16 11:43:53 +01:00
windowSize(0),
windowMultiplier(0),
freqCount(0),
channelNameEnable(1),
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),
#ifdef DFT_MEM_DUMP
2021-06-29 12:40:59 -04:00
origSigSync(dumperPrefix + "origSigSync"),
windowdSigSync(dumperPrefix + "windowdSigSync"),
ppsSigSync(dumperPrefix + "ppsSigSync"),
#endif
2021-06-29 12:40:59 -04:00
phasorRocof(dumperPrefix + "phasorRocof"),
phasorPhase(dumperPrefix + "phasorPhase"),
phasorAmplitude(dumperPrefix + "phasorAmplitude"),
phasorFreq(dumperPrefix + "phasorFreq"),
angleUnitFactor(1),
phaseOffset(0.0),
frequencyOffset(0.0),
amplitudeOffset(0.0),
rocofOffset(0.0)
2021-06-29 12:40:59 -04:00
{ }
2021-02-10 13:06:19 +01:00
virtual void prepare()
{
MultiSignalHook::prepare();
2021-07-01 20:54:23 +02:00
dumperEnable = logger->level() <= SPDLOG_LEVEL_DEBUG;
signals->clear();
for (unsigned i = 0; i < signalIndices.size(); i++) {
2020-10-21 21:04:28 +02:00
/* Add signals */
auto freqSig = std::make_shared<Signal>("frequency", "Hz", SignalType::FLOAT);
auto amplSig = std::make_shared<Signal>("amplitude", "V", SignalType::FLOAT);
auto phaseSig = std::make_shared<Signal>("phase", (angleUnitFactor)?"rad":"deg", SignalType::FLOAT);//angleUnitFactor==1 means rad
auto rocofSig = std::make_shared<Signal>("rocof", "Hz/s", SignalType::FLOAT);
2020-10-21 21:04:28 +02:00
if (!freqSig || !amplSig || !phaseSig || !rocofSig)
throw RuntimeError("Failed to create new signals");
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
}
/* 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
#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 */
windowMultiplier = ceil(((double) sampleRate / windowSize) / frequencyResolution);
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);
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();
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
/* Initalize dft results matrix */
2021-06-29 12:40:59 -04:00
results.clear();
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);
}
filterWindowCoefficents.resize(windowSize);
2021-02-16 10:27:08 +01: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);
state = State::PREPARED;
}
2021-06-29 12:40:59 -04:00
virtual void parse(json_t *json)
{
MultiSignalHook::parse(json);
int ret;
2021-06-29 12:40:59 -04:00
int windowSizeFactor = 1;
const char *paddingTypeC = nullptr;
const char *windowTypeC = nullptr;
const char *estimateTypeC = nullptr;
const char *angleUnitC = nullptr;
2022-03-22 18:20:39 +01:00
const char *timeAlignC = nullptr;
2021-06-29 12:40:59 -04:00
json_error_t err;
2020-10-21 21:04:28 +02:00
assert(state != State::STARTED);
2021-06-29 12:40:59 -04:00
Hook::parse(json);
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,
"estimate_type", &estimateTypeC,
"pps_index", &ppsIndex,
"angle_unit", &angleUnitC,
2022-03-22 18:20:39 +01:00
"add_channel_name", &channelNameEnable,
"timestamp_align", &timeAlignC,
"phase_offset", &phaseOffset,
"amplitude_offset", &amplitudeOffset,
"frequency_offset", &frequencyOffset,
"rocof_offset", &rocofOffset
);
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");
2021-06-29 12:40:59 -04:00
windowSize = sampleRate * windowSizeFactor / (double) rate;
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);
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);
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);
if (!estimateTypeC) {
logger->info("No Frequency estimation type given, assume no none");
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));
2021-06-29 12:40:59 -04:00
state = State::CHECKED;
}
virtual Hook::Reason process(struct Sample *smp)
{
assert(state == State::STARTED);
/* 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
2021-06-29 12:40:59 -04:00
bool run = false;
double smpNsec = smp->ts.origin.tv_sec * 1e9 + smp->ts.origin.tv_nsec;
2021-06-25 15:38:18 +02: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-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
#pragma omp parallel for
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;
double absAmplitude = 0;
2022-07-04 18:20:03 +02:00
for(unsigned j = 0; j < freqCount; j++) {
if (absAmplitude < abs(results[i][j])) {
absAmplitude = abs(results[i][j]);
maxPos = j;
2020-10-21 21:04:28 +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
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);
}
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
if (windowSize <= smpMemPos) {
2020-10-21 21:04:28 +02: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-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
}
smp->length = windowSize < smpMemPos ? signalIndices.size() * 4 : 0;
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++;
}
2021-02-10 13:06:19 +01: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);
2021-02-16 11:43:53 +01:00
lastSequence = smp->sequence;
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;
}
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-21 21:04:28 +02:00
return Reason::SKIP_SAMPLE;
}
/**
* This function generates the furie coeffients for the calculateDft function
*/
2021-02-16 13:16:38 +01:00
void generateDftMatrix()
{
using namespace std::complex_literals;
2021-02-16 13:16:38 +01:00
omega = exp((-2i * M_PI) / (double)(windowSize * windowMultiplier));
2021-06-29 12:40:59 -04:00
unsigned startBin = floor(startFrequency / frequencyResolution);
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++)
tmpSmpWindow[i] = ringBuffer[(i + ringBufferPos) % windowSize] * filterWindowCoefficents[i];
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
for (unsigned i = 0; i < freqCount; i++) {
results[i] = 0;
2021-07-01 20:54:23 +02: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
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-31 11:48:35 +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++) {
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;
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
}
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
*
* https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7980868&tag=1
2022-07-04 18:20:03 +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-07-04 18:20:03 +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};
}
/**
* This function is calculating the mximum based on a quadratic interpolation
2021-06-29 12:40:59 -04: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)
* https://dspguru.com/dsp/howtos/how-to-interpolate-fft-peak/
*/
DftEstimate quadraticEstimation(const Point &a, const Point &b, const Point &c, unsigned maxFBin, double startFrequency, double frequencyResolution, double multiplier, double windowSize, double windowCorrectionFactor)
{
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);
// Frequency estimation
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
// Amplitude estimation
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
double phase_est = atan2(b.y.imag(), b.y.real());
2022-03-22 18:20:39 +01:00
return { a_est, f_est , phase_est};
}
};
/* Register hook */
2021-09-13 11:41:13 +02:00
static char n[] = "pmu_dft";
static char d[] = "This hook calculates the dft on a window";
static HookPlugin<PmuDftHook, n, d, (int) Hook::Flags::NODE_READ | (int) Hook::Flags::NODE_WRITE | (int) Hook::Flags::PATH> p;
} /* namespace node */
} /* namespace villas */