Commit 114af36d authored by Jan Dinkelbach's avatar Jan Dinkelbach
Browse files

fix powerflow with load profiles and use csvreader instead of loadprofilereader


Former-commit-id: 04d04a52
parent 471dc502
......@@ -15,6 +15,7 @@ wheelhouse/
*.svg
*.png
Examples/Notebooks/Grids/reference-results/
villas_sent_data.conf
# Folders
Libraries/
......
......@@ -34,7 +34,7 @@ TIME=$(date -d "+10 seconds" +%Y%m%dT%H%M%S) #-Iseconds
echo "Start simulation at: $TIME"
# Simulation params
OPTS="--timestep 1 --duration $((60)) --system-freq 50 --start-at $TIME --solver-domain STATIC --solver-type NRP"
OPTS="--timestep 1 --duration $((60)) --system-freq 50 --start-at $TIME --solver-domain SP --solver-type NRP"
echo "Simulation params: $OPTS"
CPS_LOG_PREFIX="[Sys ] " \
......
......@@ -63,9 +63,13 @@ namespace CPS {
*/
enum class DataFormat { HHMMSS, SECONDS, HOURS, MINUTES };
///
CSVReader(String name, std::list<std::experimental::filesystem::path> path, Logger::Level logLevel);
///
CSVReader(String name, String path, Logger::Level logLevel);
///
CSVReader(String name, std::list<std::experimental::filesystem::path> path, std::map<String, String>& assignList, Logger::Level logLevel);
///
CSVReader(String name, String path, std::map<String, String>& assignList, Logger::Level logLevel);
/// convert HH:MM:SS format timestamp into total seconds.
......@@ -94,7 +98,7 @@ namespace CPS {
/// TODO : deprecate in the future
/// read in load profile with time stamp format specified
LoadProfile readLoadProfile(std::experimental::filesystem::path file,
PowerProfile readLoadProfile(std::experimental::filesystem::path file,
Real start_time = -1, Real time_step = 1, Real end_time = -1,
CSVReader::DataFormat format = CSVReader::DataFormat::SECONDS);
///
......
/**
*
* @file
* @author Jan Dinkelbach <jdinkelbach@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
*
* CPowerSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
#include <experimental/filesystem>
#include <cps/SP/SP_Ph1_Load.h>
#include <cps/Logger.h>
#include <cps/SystemTopology.h>
#include <cps/PowerProfile.h>
namespace CPS {
/// reads load profiles (csv files only) and assign them to the corresponding load object
class LoadProfileReader {
private:
/// Logger
Logger::Log mSLog;
/// path of load profile files (csv file)
String mPath;
/// list of load profile files with path
std::vector<std::experimental::filesystem::path> mFileList;
/// assign pattern, used when the MANUAL mode is selected
std::map <String, String> mAssignPattern;
public:
/// set load profile assigning pattern. AUTO for assigning load profile name (csv file name) to load object with the same name (mName)
/// MANUAL for providing an assign pattern manually. see power flow example: CIM/CIGRE_MV_PowerFlowTest_LoadProfiles.cpp
enum class Mode { AUTO, MANUAL };
/// time stamp format. HHMMSS for Hours : Minutes : Seconds, it be casted to the corresponding SECONDS. SECONDS for profiles recorded with seconds.
enum class DataFormat { HHMMSS, SECONDS };
LoadProfileReader(String name, String path, Logger::Level logLevel);
LoadProfileReader(String name, String path, std::map<String, String>& assignList, Logger::Level logLevel);
/// convert HH:MM:SS format timestamp into total seconds.
/// e.g.: 00 : 01 : 00 -- > 60.
Real time_format_convert(const String& time);
/// read in load profile with time stamp format specified
PowerProfile read(fs::path file,
Real start_time = -1, Real time_step = 1, Real end_time = -1,
LoadProfileReader::DataFormat format = LoadProfileReader::DataFormat::SECONDS);
/// assign load profile to corresponding load object
void assign(SystemTopology& sys,
Real start_time = -1, Real time_step = 1, Real end_time = -1,
LoadProfileReader::Mode mode = LoadProfileReader::Mode::AUTO,
LoadProfileReader::DataFormat format = LoadProfileReader::DataFormat::SECONDS);
/// interpolation for PQ data points
PQData interpol_linear(std::map<Real, PQData>& data_PQ, Real x);
/// interpolation for weighting factor data points
Real interpol_linear(std::map<Real, Real>& data_wf, Real x);
};
// #### csv reader section
class LoadProfileRow {
public:
///
String const& get(std::size_t index) const { return m_data[index]; }
///
Int size() const;
///
void readNextRow(std::istream& str);
private:
///
std::vector<String> m_data;
};
class LoadProfileReaderIterator {
public:
LoadProfileReaderIterator(std::istream& str);
LoadProfileReaderIterator();
LoadProfileReaderIterator& next();
LoadProfileReaderIterator next(Int);
LoadProfileReaderIterator& step(Int time_step);
LoadProfileRow const& operator*() const { return m_row; };
Bool operator==(LoadProfileReaderIterator const& rhs) {
return ((this == & rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));
}
Bool operator!=(LoadProfileReaderIterator const&rhs) {
return !((*this) == rhs);
}
private:
std::istream* m_str;
LoadProfileRow m_row;
};
}
......@@ -15,7 +15,7 @@ add_library(cps STATIC
Component.cpp
PowerComponent.cpp
SystemTopology.cpp
#CSVReader.cpp
CSVReader.cpp
)
list(APPEND SOURCES
......
......@@ -355,10 +355,10 @@ void CSVReader::assignLoadProfileDP(std::vector<std::shared_ptr<CPS::DP::Ph1::Av
LoadProfile CSVReader::readLoadProfile(std::experimental::filesystem::path file,
PowerProfile CSVReader::readLoadProfile(std::experimental::filesystem::path file,
Real start_time, Real time_step, Real end_time, CSVReader::DataFormat format) {
LoadProfile load_profile;
PowerProfile load_profile;
std::ifstream csvfile(file);
bool need_that_conversion = (format == DataFormat::HHMMSS) ? true : false;
bool data_with_weighting_factor = false;
......@@ -393,14 +393,14 @@ LoadProfile CSVReader::readLoadProfile(std::experimental::filesystem::path file,
CPS::Real currentTime = (need_that_conversion) ? time_format_convert((*loop).get(0)) : std::stod((*loop).get(0));
if (data_with_weighting_factor) {
Real wf = std::stod((*loop).get(1));
load_profile.data_WF.insert(std::pair<Real, Real>(currentTime, wf));
load_profile.weightingFactors.insert(std::pair<Real, Real>(currentTime, wf));
}
else {
PQData pq;
// multiplied by 1000 due to unit conversion (kw to w)
pq.p = std::stod((*loop).get(1)) * 1000;
pq.q = std::stod((*loop).get(2)) * 1000;
load_profile.data_PQ.insert(std::pair<Real,PQData>(currentTime,pq));
load_profile.pqData.insert(std::pair<Real,PQData>(currentTime,pq));
}
if (end_time > 0 && currentTime > end_time)
......@@ -412,14 +412,14 @@ LoadProfile CSVReader::readLoadProfile(std::experimental::filesystem::path file,
}
for (auto x : times) {
if (load_profile.data_PQ.find(x) == load_profile.data_PQ.end()) {
if (load_profile.pqData.find(x) == load_profile.pqData.end()) {
if (data_with_weighting_factor) {
Real y = interpol_linear(load_profile.data_WF, x);
load_profile.data_WF.insert(std::pair<Real, Real>(x, y));
Real y = interpol_linear(load_profile.weightingFactors, x);
load_profile.weightingFactors.insert(std::pair<Real, Real>(x, y));
}
else {
PQData y = interpol_linear(load_profile.data_PQ, x);
load_profile.data_PQ.insert(std::pair<Real,PQData>(x,y));
PQData y = interpol_linear(load_profile.pqData, x);
load_profile.pqData.insert(std::pair<Real,PQData>(x,y));
}
}
}
......@@ -527,14 +527,14 @@ void CSVReader::assignLoadProfile(CPS::SystemTopology& sys, Real start_time, Rea
}
}
CPS::PQData CSVReader::interpol_linear(std::map<CPS::Real, CPS::PQData>& data_PQ, CPS::Real x) {
std::map <Real, PQData>::const_iterator entry = data_PQ.upper_bound(x);
CPS::PQData CSVReader::interpol_linear(std::map<CPS::Real, CPS::PQData>& pqData, CPS::Real x) {
std::map <Real, PQData>::const_iterator entry = pqData.upper_bound(x);
PQData y;
if (entry == data_PQ.end()) {
if (entry == pqData.end()) {
return (--entry)->second;
}
if (entry == data_PQ.begin()) {
if (entry == pqData.begin()) {
return entry->second;
}
std::map <Real, PQData>::const_iterator prev = entry;
......@@ -547,14 +547,14 @@ CPS::PQData CSVReader::interpol_linear(std::map<CPS::Real, CPS::PQData>& data_PQ
return y;
}
CPS::Real CSVReader::interpol_linear(std::map<CPS::Real, CPS::Real>& data_wf, CPS::Real x) {
std::map <Real, Real>::const_iterator entry = data_wf.upper_bound(x);
CPS::Real CSVReader::interpol_linear(std::map<CPS::Real, CPS::Real>& weightingFactors, CPS::Real x) {
std::map <Real, Real>::const_iterator entry = weightingFactors.upper_bound(x);
CPS::Real y;
if (entry == data_wf.end()) {
if (entry == weightingFactors.end()) {
return (--entry)->second;
}
if (entry == data_wf.begin()) {
if (entry == weightingFactors.begin()) {
return entry->second;
}
std::map <Real, Real>::const_iterator prev = entry;
......
/**
*
* @author Jan Dinkelbach <jdinkelbach@eonerc.rwth-aachen.de>
* @copyright 2017-2018, Institute for Automation of Complex Power Systems, EONERC
*
* CPowerSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <experimental/filesystem>
#include <cps/LoadProfileReader.h>
using namespace CPS;
void LoadProfileRow::readNextRow(std::istream& str) {
std::string line;
std::getline(str, line);
std::stringstream lineStream(line);
std::string cell;
m_data.clear();
while (std::getline(lineStream >> std::ws, cell, ',')) {
m_data.push_back(cell);
}
// This checks for a trailing comma with no data after it.
if (!lineStream && cell.empty()){
// If there was a trailing comma then add an empty element.
m_data.push_back("");
}
}
int LoadProfileRow::size() const {
return static_cast<Int>(m_data.size());
}
LoadProfileReaderIterator& LoadProfileReaderIterator::next() {
if (m_str) {
m_row.readNextRow(*m_str);
if (!(*m_str)) {
m_str = NULL;
}
}
return *this;
}
LoadProfileReaderIterator LoadProfileReaderIterator::next(int) {
LoadProfileReaderIterator tmp(*this);
this->next();
return tmp;
}
LoadProfileReaderIterator& LoadProfileReaderIterator::step(int time_step) {
while (time_step != 0) {
if (m_str) {
m_row.readNextRow(*m_str);
if (!(*m_str)) {
m_str = NULL;
}
}
time_step -= 1;
}
return *this;
}
LoadProfileReaderIterator::LoadProfileReaderIterator(std::istream& str)
: m_str(str.good() ? &str : NULL) {
this->next();
}
LoadProfileReaderIterator::LoadProfileReaderIterator():m_str(NULL) { }
LoadProfileReader::LoadProfileReader(CPS::String name, CPS::String path, CPS::Logger::Level logLevel) {
mSLog = Logger::get(name + "_ProfileReader", logLevel);
mPath = path;
for (const auto & entry : std::experimental::filesystem::directory_iterator(path)) {
mFileList.push_back(entry.path());
}
}
LoadProfileReader::LoadProfileReader(CPS::String name, CPS::String path, std::map<String, String>& assignList, CPS::Logger::Level logLevel)
: LoadProfileReader(name, path, logLevel) {
mAssignPattern = assignList;
}
CPS::Real LoadProfileReader::time_format_convert(const CPS::String& time) {
int hh, mm, ss = 0;
CPS::Real secs = 0;
if (sscanf(time.c_str(), "%d:%d:%d", &hh, &mm, &ss) >= 2) {
secs = hh * 3600 + mm * 60 + ss;
}
return secs;
}
LoadProfile LoadProfileReader::read(std::experimental::filesystem::path file,
Real start_time, Real time_step, Real end_time, LoadProfileReader::DataFormat format) {
LoadProfile load_profile;
std::ifstream csvfile(file);
bool need_that_conversion = (format == DataFormat::HHMMSS) ? true : false;
bool data_with_weighting_factor = false;
LoadProfileReaderIterator loop(csvfile);
// ignore the first row if it is a title
if (!std::isdigit((*loop).get(0)[0])) {
loop.next();
}
/*
loop over rows of the csv file to find the entry point to read in.
and determine data type prior to read in. (assuming only time,p,q or time,weighting factor)
if start_time and end_time are negative (as default), it reads in all rows.
*/
for (; loop != LoadProfileReaderIterator(); loop.next()) {
LoadProfileReaderIterator nextRow = loop;
nextRow.next();
CPS::Real nextTime = (need_that_conversion) ? time_format_convert((*nextRow).get(0)) : std::stod((*nextRow).get(0));
if ((*nextRow).size() == 2) {
data_with_weighting_factor = true;
}
if ((start_time < 0) | (nextTime >= Int(start_time)))
{
break;
}
}
/*
reading data after entry point until end_time is reached
*/
for (; loop != LoadProfileReaderIterator(); loop.next()) {
CPS::Real currentTime = (need_that_conversion) ? time_format_convert((*loop).get(0)) : std::stod((*loop).get(0));
if (data_with_weighting_factor) {
Real wf = std::stod((*loop).get(1));
load_profile.data_WF.insert(std::pair<Real, Real>(currentTime, wf));
}
else {
PQData pq;
// multiplied by 1000 due to unit conversion (kw to w)
pq.p = std::stod((*loop).get(1)) * 1000;
pq.q = std::stod((*loop).get(2)) * 1000;
load_profile.data_PQ.insert(std::pair<Real,PQData>(currentTime,pq));
}
if (end_time > 0 && currentTime > end_time)
break;
}
std::vector<CPS::Real> times;
for (CPS::Real x_ = start_time; x_ <= end_time; x_ += time_step) {
times.push_back(x_);
}
for (auto x : times) {
if (load_profile.data_PQ.find(x) == load_profile.data_PQ.end()) {
if (data_with_weighting_factor) {
Real y = interpol_linear(load_profile.data_WF, x);
load_profile.data_WF.insert(std::pair<Real, Real>(x, y));
}
else {
PQData y = interpol_linear(load_profile.data_PQ, x);
load_profile.data_PQ.insert(std::pair<Real,PQData>(x,y));
}
}
}
return load_profile;
}
void LoadProfileReader::assign(CPS::SystemTopology& sys, Real start_time, Real time_step, Real end_time,
LoadProfileReader::Mode mode, LoadProfileReader::DataFormat format) {
switch (mode) {
case LoadProfileReader::Mode::AUTO: {
for (auto obj : sys.mComponents) {
if (std::shared_ptr<CPS::SP::Ph1::Load> load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(obj)) {
mSLog->info("Comparing csv file names with load mRIDs ...");
String load_name = load->name();
for (auto file : mFileList) {
String file_name = file.filename().string();
/// changing file name and load name to upper case for later matching
for (auto & c : load_name) c = toupper(c);
for (auto & c : file_name) c = toupper(c);
/// strip off all non-alphanumeric characters
load_name.erase(remove_if(load_name.begin(), load_name.end(), [](char c) { return !isalnum(c); }), load_name.end());
file_name.erase(remove_if(file_name.begin(), file_name.end(), [](char c) { return !isalnum(c); }), file_name.end());
if (std::string(file_name.begin(), file_name.end() - 3).compare(load_name) == 0) {
load->mProfile = read(file, start_time, time_step, end_time, format);
load->use_profile = true;
mSLog->info("Assigned {} to {}", file.filename().string(), load->name());
}
}
}
}
break;
}
case LoadProfileReader::Mode::MANUAL: {
Int LP_assigned_counter = 0;
Int LP_not_assigned_counter = 0;
mSLog->info("Assigning load profiles with user defined pattern ...");
for (auto obj : sys.mComponents) {
if (std::shared_ptr<CPS::SP::Ph1::Load> load = std::dynamic_pointer_cast<CPS::SP::Ph1::Load>(obj)) {
std::map<String, String>::iterator file = mAssignPattern.find(load->name());
if (file == mAssignPattern.end()) {
mSLog->info("{} has no profile given.", load->name());
LP_not_assigned_counter++;
continue;
}
load->mProfile = read(std::experimental::filesystem::path(mPath + file->second + ".csv"), start_time, time_step, end_time);
load->use_profile = true;
mSLog->info("Assigned {}.csv to {}", file->second, load->name());
LP_assigned_counter++;
}
}
mSLog->info("Assigned profiles for {} loads, {} not assigned.", LP_assigned_counter, LP_not_assigned_counter);
break;
}
default: {
throw std::invalid_argument(
"load profile assign failed, please set correct mode for the load profile reader.");
break;
}
}
}
CPS::PQData LoadProfileReader::interpol_linear(std::map<CPS::Real, CPS::PQData>& data_PQ, CPS::Real x) {
std::map <Real, PQData>::const_iterator entry = data_PQ.upper_bound(x);
PQData y;
if (entry == data_PQ.end()) {
return (--entry)->second;
}
if (entry == data_PQ.begin()) {
return entry->second;
}
std::map <Real, PQData>::const_iterator prev = entry;
--prev;
const CPS::Real delta = (x - prev->first) / (entry->first - prev->first);
y.p = delta * entry->second.p + (1 - delta)*prev->second.p;
y.q = delta * entry->second.q + (1 - delta)*prev->second.q;
return y;
}
CPS::Real LoadProfileReader::interpol_linear(std::map<CPS::Real, CPS::Real>& data_wf, CPS::Real x) {
std::map <Real, Real>::const_iterator entry = data_wf.upper_bound(x);
CPS::Real y;
if (entry == data_wf.end()) {
return (--entry)->second;
}
if (entry == data_wf.begin()) {
return entry->second;
}
std::map <Real, Real>::const_iterator prev = entry;
--prev;
const CPS::Real delta = (x - prev->first) / (entry->first - prev->first);
y = delta * entry->second + (1 - delta)*prev->second;
return y;
}
......@@ -97,14 +97,14 @@ void SP::Ph1::Load::modifyPowerFlowBusType(PowerflowBusType powerflowBusType) {
void SP::Ph1::Load::updatePQ(Real time) {
if (mLoadProfile.weightingFactors.empty()) {
mPQ->attribute<Real>("P")->set(mLoadProfile.pqData.find(time)->second.p);
mPQ->attribute<Real>("Q")->set(mLoadProfile.pqData.find(time)->second.q);
this->attribute<Real>("P")->set(mLoadProfile.pqData.find(time)->second.p);
this->attribute<Real>("Q")->set(mLoadProfile.pqData.find(time)->second.q);
} else {
Real wf = mLoadProfile.weightingFactors.find(time)->second;
Real P_new = mPQ->attribute<Real>("P_nom")->get()*wf;
Real Q_new = mPQ->attribute<Real>("Q_nom")->get()*wf;
mPQ->attribute<Real>("P")->set(P_new);
mPQ->attribute<Real>("Q")->set(Q_new);
Real P_new = this->attribute<Real>("P_nom")->get()*wf;
Real Q_new = this->attribute<Real>("Q_nom")->get()*wf;
this->attribute<Real>("P")->set(P_new);
this->attribute<Real>("Q")->set(Q_new);
}
};
......
......@@ -20,7 +20,6 @@
#include <cps/CIM/Reader.h>
#include <DPsim.h>
#include <cps/LoadProfileReader.h>
using namespace std;
using namespace DPsim;
......
......@@ -20,7 +20,7 @@
#include <cps/CIM/Reader.h>
#include <DPsim.h>
#include <cps/LoadProfileReader.h>
#include <cps/CSVReader.h>