This commit is contained in:
CismonX 2018-04-05 19:01:10 +08:00
commit 7e494f552b
14 changed files with 531 additions and 0 deletions

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
SOURCES = $(wildcard src/*.cpp)
OBJECTS = $(SOURCES:%.cpp=%.o)
APPLICATION = arma-flow
CXXFLAGS = -Wall -c -O2 -std=c++17 -o
LDFLAGS = -larmadillo -loptions -lstdc++fs
all: ${OBJECTS} ${APPLICATION}
${APPLICATION}: ${OBJECTS}
${CXX} -o $@ ${OBJECTS} ${LDFLAGS}
${OBJECTS}:
${CXX} ${CXXFLAGS} $@ ${@:%.o=%.cpp}
clean:
rm -f ${APPLICATION} ${OBJECTS}

18
README.md Normal file
View File

@ -0,0 +1,18 @@
# arma-flow
A simple power flow calculator using Newton's method.
## 1. Requirements
* A newer version of [Armadillo](http://arma.sourceforge.net/)
* Tested on 8.400.0
* The [Options](https://mulholland.xyz/docs/options/) library
* [RapidJSON]() for JSON output
* Compiler with supports C++17
## 2. Usage
See `arma-flow --help`.

66
src/args.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "args.hpp"
namespace flow
{
args::args() : arg_parser_(
"A simple power flow calculator using Newton's method.\n"
"usage: arma-flow [--version] [-h | --help] [-o <output_file>]\n"
" -n <node_data_file> -e <edge_data_file> [-r]\n"
" [-i <max_iterations>] [-a <accuracy>]\n"
" [-v | --verbose]",
"arma-flow version 0.0.1")
{
arg_parser_.newString("o", "result");
arg_parser_.newString("n");
arg_parser_.newString("e");
arg_parser_.newFlag("r");
arg_parser_.newInt("i", 100);
arg_parser_.newDouble("a", 0.00001);
arg_parser_.newFlag("verbose v");
}
bool args::input_file_path(std::string& nodes, std::string& edges)
{
if (!arg_parser_.found("n") || !arg_parser_.found("e"))
return false;
nodes = arg_parser_.getString("n");
edges = arg_parser_.getString("e");
return true;
}
bool args::output_file_path(std::string& output)
{
output = arg_parser_.getString("o");
return arg_parser_.found("o");
}
bool args::remove_first_line()
{
return arg_parser_.getFlag("r");
}
int args::max_iterations()
{
return arg_parser_.getInt("i");
}
double args::accuracy()
{
return arg_parser_.getDouble("a");
}
bool args::verbose()
{
return arg_parser_.getFlag("v");
}
void args::help()
{
arg_parser_.exitHelp();
}
void args::parse(int argc, char** argv)
{
arg_parser_.parse(argc, argv);
}
}

30
src/args.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <options.h>
namespace flow
{
class args
{
options::ArgParser arg_parser_;
public:
explicit args();
void parse(int argc, char** argv);
bool input_file_path(std::string& nodes, std::string& edges);
bool output_file_path(std::string& output);
bool remove_first_line();
int max_iterations();
double accuracy();
bool verbose();
void help();
};
}

84
src/calc.cpp Normal file
View File

@ -0,0 +1,84 @@
#include "calc.hpp"
#include "executor.hpp"
namespace flow
{
std::complex<double> calc::edge_data::impedance() const
{
const auto deno = r * r + x * x;
return { r / deno, -x / deno };
}
std::complex<double> calc::edge_data::grounding_admittance() const
{
return { 0, b };
}
void calc::init(const arma::dmat& nodes, const arma::dmat& edges,
int max_iterations, double accuracy)
{
if (max_iterations <= 0)
executor::print_and_exit("Bad number of iterations. Positive integer expected.");
if (accuracy < 0 || accuracy > 1)
executor::print_and_exit("Invalid accuracy.");
if (nodes.n_cols != 5 || edges.n_cols != 6)
executor::print_and_exit("Bad input matrix format.");
nodes.each_row([this](const arma::rowvec& row)
{
using node_type = node_data::node_type;
auto type = node_type::swing;
switch (static_cast<unsigned>(row[4]))
{
case 0:
break;
case 1:
type = node_type::pq;
break;
case 2:
type = node_type::pv;
break;
default:
executor::print_and_exit("Bad node type.");
}
nodes_.push_back({
node_num_++, row[0], row[1], row[2], row[3], type
});
});
edges.each_row([this](const arma::rowvec& row)
{
auto n1 = static_cast<unsigned>(row[0]) - 1;
auto n2 = static_cast<unsigned>(row[1]) - 1;
if (n1 >= node_num_ || n2 >= node_num_)
executor::print_and_exit("Bad node offset.");
edges_.push_back({
&nodes_[n1], &nodes_[n2], row[2], row[3], row[4], row[5]
});
});
}
void calc::node_admittance()
{
arma::cx_mat node_adm_cplx(node_num_, node_num_, arma::fill::zeros);
for (auto&& edge : edges_) {
const auto n1 = edge.n1->offset;
const auto n2 = edge.n2->offset;
const auto impedance = edge.impedance();
// Whether this edge has transformer.
if (edge.k) {
node_adm_cplx.at(n1, n1) += impedance;
node_adm_cplx.at(n2, n2) += impedance / (edge.k * edge.k);
node_adm_cplx.at(n1, n2) -= impedance / edge.k;
node_adm_cplx.at(n2, n1) -= impedance / edge.k;
}
else {
const auto delta_diag = impedance + edge.grounding_admittance();
node_adm_cplx.at(n1, n1) += delta_diag;
node_adm_cplx.at(n2, n2) += delta_diag;
node_adm_cplx.at(n1, n2) -= impedance;
node_adm_cplx.at(n2, n1) -= impedance;
}
}
node_adm_real_ = arma::real(node_adm_cplx);
node_adm_imag_ = arma::imag(node_adm_cplx);
}
}

55
src/calc.hpp Normal file
View File

@ -0,0 +1,55 @@
#pragma once
#include <armadillo>
#include <vector>
namespace flow
{
class calc
{
struct node_data
{
unsigned long offset;
double v, g, p, q;
enum node_type {
pq, pv, swing
} type;
};
struct edge_data
{
node_data *n1, *n2;
double r, x, b, k;
std::complex<double> impedance() const;
std::complex<double> grounding_admittance() const;
};
std::vector<node_data> nodes_;
unsigned long node_num_ = 0;
std::vector<edge_data> edges_;
arma::dmat node_adm_real_;
arma::dmat node_adm_imag_;
public:
explicit calc() = default;
void init(const arma::dmat& nodes, const arma::dmat& edges,
int max_iterations, double accuracy);
void node_admittance();
arma::dmat* get_node_adm_real()
{
return &node_adm_real_;
}
arma::dmat* get_node_adm_imag()
{
return &node_adm_imag_;
}
};
}

49
src/executor.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <iostream>
#include "executor.hpp"
#include "factory.hpp"
#include "output.hpp"
namespace flow
{
void executor::print_and_exit(const std::string& message)
{
std::cout << message << std::endl;
exit(1);
}
void executor::execute(int argc, char** argv) const
{
auto factory = factory::get();
auto args = factory->get_args();
args->parse(argc, argv);
std::string path_to_nodes, path_to_edges;
if (!args->input_file_path(path_to_nodes, path_to_edges))
args->help();
auto input = factory->get_input();
const auto remove = args->remove_first_line();
if (!input->from_csv_file(path_to_nodes, remove))
print_and_exit("Failed to read edge data from file.");
const auto nodes = input->get_mat();
if (!input->from_csv_file(path_to_edges, remove))
print_and_exit("Failed to read edge data from file.");
const auto edges = input->get_mat();
auto calc = factory->get_calc();
calc->init(nodes, edges, args->max_iterations(), args->accuracy());
calc->node_admittance();
const auto node_adm_real = calc->get_node_adm_real();
const auto node_adm_imag = calc->get_node_adm_imag();
const auto verbose = args->verbose();
if (verbose) {
std::cout << "Real part of node admittance matrix:" << std::endl;
output::print_dmat(*node_adm_real);
std::cout << "Imaginary part of node admittance matrix:" << std::endl;
output::print_dmat(*node_adm_imag);
}
std::string output_path;
if (!args->output_file_path(output_path))
std::cout << "Output file path not specified. Using result-*.csv by default." << std::endl;
}
}

15
src/executor.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
namespace flow
{
class executor
{
public:
explicit executor() = default;
static void print_and_exit(const std::string& message);
void execute(int argc, char** argv) const;
};
}

61
src/factory.hpp Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include "input.hpp"
#include "args.hpp"
#include "executor.hpp"
#include "calc.hpp"
#include "output.hpp"
namespace flow
{
class factory
{
input input_;
args args_;
executor executor_;
calc calc_;
output output_;
static factory singleton_;
explicit factory() = default;
public:
static factory* get()
{
return &singleton_;
}
input* get_input()
{
return &input_;
}
args* get_args()
{
return &args_;
}
executor* get_executor()
{
return &executor_;
}
calc* get_calc()
{
return &calc_;
}
output* get_output()
{
return &output_;
}
};
inline factory factory::singleton_;
}

27
src/input.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <experimental/filesystem>
#include "input.hpp"
namespace flow
{
bool input::from_csv_file(const std::string& path, bool remove_first_line)
{
std::ifstream ifstream;
ifstream.exceptions(std::ifstream::failbit);
namespace fs = std::experimental::filesystem;
try {
ifstream.open(path[0] == '/' ? path : fs::current_path().string() + '/' + path);
if (remove_first_line)
ifstream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
catch (const std::exception&) {
return false;
}
return do_read(ifstream);
}
const arma::dmat& input::get_mat() const
{
return mat_;
}
}

39
src/input.hpp Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <armadillo>
namespace flow
{
class input
{
/// The loaded matrix.
arma::dmat mat_;
/**
* Read matrix from an istream.
*
* @param istream The istream to be read.
* @return Whether read is successful.
*/
bool do_read(std::istream& istream)
{
mat_.clear();
return mat_.load(istream, arma::csv_ascii);
}
public:
/**
* Read matrix from a CSV file. The first line will be ignored.
*
* @param path Path to file.
* @param remove_first_line Whether to remove the first line of CSV file.
* @return Whether the file is successfully read.
*/
bool from_csv_file(const std::string& path, bool remove_first_line);
/**
* Get loaded matrix.
*/
const arma::dmat& get_mat() const;
};
}

6
src/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "factory.hpp"
int main(int argc, char** argv)
{
flow::factory::get()->get_executor()->execute(argc, argv);
}

49
src/output.cpp Normal file
View File

@ -0,0 +1,49 @@
#include <iomanip>
#include "output.hpp"
#include "executor.hpp"
#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/ioctl.h>
#include <unistd.h>
#endif // _WIN32
namespace flow
{
int output::max_elems_per_line()
{
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
return 10;
const auto width = csbi.dwSize.X;
#else
winsize win;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &win);
const auto width = win.ws_col;
#endif // _WIN32
return std::floor((width - 7) / 11);
}
void output::print_dmat(const arma::dmat& mat)
{
mat.each_row([](const arma::rowvec& row)
{
std::cout << std::setprecision(7) << std::left;
auto counter = 0;
auto elems = max_elems_per_line();
for (auto&& elem : row)
{
if (++counter > elems) {
std::cout << "...(" << row.n_elem - elems << ')';
break;
}
std::cout << std::setw(10) << elem;
}
std::cout << std::endl;
});
}
}

16
src/output.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include <armadillo>
namespace flow
{
class output
{
static int max_elems_per_line();
public:
static void print_dmat(const arma::dmat& mat);
};
}