diff --git a/.clang-tidy b/.clang-tidy index b001e712..455a7f33 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -11,6 +11,7 @@ google-*, modernize*, -modernize-use-trailing-return-type, -modernize-use-override, +-modernize-use-ranges, -modernize-avoid-c-arrays, -modernize-pass-by-value, -modernize-use-equals-default, @@ -24,11 +25,14 @@ hicpp-*, clang-analyzer-*, bugprone-*, -bugprone-narrowing-conversions, +-bugprone-easily-swappable-parameters, misc-*, -misc-non-private-member-variables-in-classes, +-misc-include-cleaner, cert-*, -cert-err58-cpp, portability-*, +-portability-template-virtual-member-function, readability-*, -readability-magic-numbers, performance-* @@ -45,6 +49,7 @@ google-*, modernize*, -modernize-use-trailing-return-type, -modernize-use-override, +-modernize-use-ranges, -modernize-use-using, -modernize-avoid-c-arrays, -modernize-pass-by-value, @@ -65,6 +70,7 @@ misc-*, cert-*, -cert-err58-cpp, portability-*, +-portability-template-virtual-member-function, readability-*, -readability-magic-numbers, -readability-identifier-naming, @@ -74,7 +80,6 @@ performance-* HeaderFilterRegex: 'src/helics/*.hp?p?' -AnalyzeTemporaryDtors: false CheckOptions: - key: cert-dcl59-cpp.HeaderFileExtensions value: h,hh,hpp,hxx @@ -315,7 +320,7 @@ CheckOptions: - key: readability-identifier-naming.MethodSuffix value: '' - key: readability-identifier-naming.NamespaceCase - value: lowercase + value: lower_case - key: readability-identifier-naming.NamespacePrefix value: '' - key: readability-identifier-naming.NamespaceSuffix diff --git a/.github/actions/run-clang-tidy-pr.sh b/.github/actions/run-clang-tidy-pr.sh index 958faf4d..580f0249 100755 --- a/.github/actions/run-clang-tidy-pr.sh +++ b/.github/actions/run-clang-tidy-pr.sh @@ -1,4 +1,6 @@ #!/bin/bash +set -euo pipefail + FILES_URL="$(jq -r '.pull_request._links.self.href' "$GITHUB_EVENT_PATH")/files" FILES=$(curl -s -X GET -G "$FILES_URL" | jq -r '.[] | .filename') echo "====Files Changed in PR====" @@ -12,10 +14,25 @@ if ((filecount > 0 && filecount <= 20)); then cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DGRIDDYN_BUILD_C_SHARED_LIBRARY=ON .. cd .. echo "====Run clang-tidy====" + if [[ -x /usr/bin/run-clang-tidy ]]; then + TIDY_CMD=(/usr/bin/run-clang-tidy) + elif command -v run-clang-tidy >/dev/null 2>&1; then + TIDY_CMD=(run-clang-tidy) + elif command -v clang-tidy >/dev/null 2>&1; then + TIDY_CMD=(clang-tidy) + elif [[ -f /usr/share/clang/run-clang-tidy.py ]]; then + TIDY_CMD=(python3 /usr/share/clang/run-clang-tidy.py) + else + echo "clang-tidy helper not found in PATH and /usr/share/clang/run-clang-tidy.py is unavailable" + exit 2 + fi while read -r line; do if echo "$line" | grep -E '\.(cpp|hpp|c|h)$'; then - python3 /usr/share/clang/run-clang-tidy.py "$line" -p build -quiet - rc=$? + if "${TIDY_CMD[@]}" -p build -quiet "$line"; then + rc=0 + else + rc=$? + fi echo "clang-tidy exit code: $rc" if [[ "$rc" != "0" ]]; then tidyerr=1 diff --git a/.github/workflows/braid-build.yml b/.github/workflows/braid-build.yml index b06d263b..a10d75ff 100644 --- a/.github/workflows/braid-build.yml +++ b/.github/workflows/braid-build.yml @@ -3,12 +3,10 @@ name: Braid Build on: push: branches: - - develop - - modernization + - main pull_request: branches: - - develop - - modernization + - main workflow_dispatch: concurrency: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 7f64d0fc..bf7cf8ae 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -3,12 +3,10 @@ name: CI Tests on: push: branches: - - develop - - modernization + - main pull_request: branches: - - develop - - modernization + - main workflow_dispatch: concurrency: diff --git a/.github/workflows/compiler-tests.yml b/.github/workflows/compiler-tests.yml index 5480d5d0..ed991ace 100644 --- a/.github/workflows/compiler-tests.yml +++ b/.github/workflows/compiler-tests.yml @@ -3,12 +3,10 @@ name: Compiler Tests on: push: branches: - - develop - - modernization + - main pull_request: branches: - - develop - - modernization + - main workflow_dispatch: concurrency: diff --git a/.github/workflows/static-analyzers.yml b/.github/workflows/static-analyzers.yml index a1ba3456..573f70e2 100644 --- a/.github/workflows/static-analyzers.yml +++ b/.github/workflows/static-analyzers.yml @@ -1,7 +1,13 @@ name: Static Analyzers on: + push: + branches: + - main pull_request: + branches: + - main + workflow_dispatch: jobs: cpplint: diff --git a/ThirdParty/concurrency b/ThirdParty/concurrency index e8c95110..b9aba9dd 160000 --- a/ThirdParty/concurrency +++ b/ThirdParty/concurrency @@ -1 +1 @@ -Subproject commit e8c951104c7a550dcf892a5e322436c672d03155 +Subproject commit b9aba9dd89dbfa97392d4b416097e602cd9f1f39 diff --git a/ThirdParty/utilities b/ThirdParty/utilities index 954ae3ac..84d16880 160000 --- a/ThirdParty/utilities +++ b/ThirdParty/utilities @@ -1 +1 @@ -Subproject commit 954ae3acb8c55741bd3ab37ddd4b69b1b19b5425 +Subproject commit 84d16880fbbf65627f8bc46d1e334b54dfdbbac3 diff --git a/config/cmake/FindSuiteSparse.cmake b/config/cmake/FindSuiteSparse.cmake index a96f47ee..b627be83 100644 --- a/config/cmake/FindSuiteSparse.cmake +++ b/config/cmake/FindSuiteSparse.cmake @@ -403,11 +403,13 @@ macro(SuiteSparse_FIND_COMPONENTS) set(SuiteSparse_${suitesparseCompUC}_FOUND OFF) else() set(SuiteSparse_${suitesparseCompUC}_FOUND ON) - set_target_properties( - ${SuiteSparseNameSpace}::${suitesparseCompLC} - PROPERTIES INTERFACE_INCLUDE_DIRECTORIES - "${SuiteSparse_${suitesparseCompUC}_INCLUDE_DIR}" - ) + if(SuiteSparse_${suitesparseCompUC}_INCLUDE_DIR) + set_target_properties( + ${SuiteSparseNameSpace}::${suitesparseCompLC} + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES + "${SuiteSparse_${suitesparseCompUC}_INCLUDE_DIR}" + ) + endif() endif() # if one or both (include dir or filepath lib), then we provide a new cmake cache variable diff --git a/config/cmake/updateGitSubmodules.cmake b/config/cmake/updateGitSubmodules.cmake index 7e3b789c..24bccefa 100644 --- a/config/cmake/updateGitSubmodules.cmake +++ b/config/cmake/updateGitSubmodules.cmake @@ -26,15 +26,17 @@ macro(submod_update_all) if(ENABLE_SUBMODULE_UPDATE) message(STATUS "Git Submodule Update") execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} -c safe.directory=${PROJECT_SOURCE_DIR} submodule update + --init + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_OUTPUT + ERROR_VARIABLE GIT_ERROR ) if(GIT_RESULT) message( WARNING - "Automatic submodule checkout with `git submodule --init` failed with error ${GIT_RESULT} and output ${GIT_OUTPUT}. Checkout the submodules before building." + "Automatic submodule checkout with `git submodule --init` failed with error ${GIT_RESULT} and output ${GIT_OUTPUT}${GIT_ERROR}. Checkout the submodules before building." ) endif() else() @@ -46,15 +48,17 @@ macro(submod_update target) if(ENABLE_SUBMODULE_UPDATE) message(STATUS "Git Submodule Update ${target}") execute_process( - COMMAND ${GIT_EXECUTABLE} submodule update --init -- ${target} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} -c safe.directory=${PROJECT_SOURCE_DIR} submodule update + --init -- ${target} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_OUTPUT + ERROR_VARIABLE GIT_ERROR ) if(GIT_RESULT) message( WARNING - "Automatic submodule checkout with `git submodule --init -- $[target}` failed with error ${GIT_RESULT} and output ${GIT_OUTPUT}. Checkout the submodules before building." + "Automatic submodule checkout with `git submodule --init -- ${target}` failed with error ${GIT_RESULT} and output ${GIT_OUTPUT}${GIT_ERROR}. Checkout the submodules before building." ) endif() else() diff --git a/src/fileInput/gridDynReadDYR.cpp b/src/fileInput/gridDynReadDYR.cpp index 66ef296f..162d2212 100644 --- a/src/fileInput/gridDynReadDYR.cpp +++ b/src/fileInput/gridDynReadDYR.cpp @@ -4,34 +4,36 @@ * SPDX-License-Identifier: BSD-3-Clause */ +#include "core/coreDefinitions.hpp" +#include "core/coreObject.h" #include "core/objectFactory.hpp" #include "fileInput.h" #include "gmlc/utilities/stringConversion.h" +#include "gmlc/utilities/stringOps.h" #include "griddyn/Exciter.h" #include "griddyn/GenModel.h" #include "griddyn/Generator.h" #include "griddyn/Governor.h" #include "griddyn/gridBus.h" -#include +#include "readerInfo.h" #include #include -#include #include namespace griddyn { -static std::shared_ptr cof = coreObjectFactory::instance(); - -void loadGENROU(coreObject* parentObject, stringVec& tokens); -void loadESDC1A(coreObject* parentObject, stringVec& tokens); -void loadTGOV1(coreObject* parentObject, stringVec& tokens); -void loadEXDC2(coreObject* parentObject, stringVec& tokens); -void loadSEXS(coreObject* parentObject, stringVec& tokens); +namespace { + void loadGENROU(coreObject* parentObject, stringVec& tokens); + void loadESDC1A(coreObject* parentObject, stringVec& tokens); + void loadTGOV1(coreObject* parentObject, stringVec& tokens); + void loadEXDC2(coreObject* parentObject, stringVec& tokens); + void loadSEXS(coreObject* parentObject, stringVec& tokens); +} // namespace void loadDYR(coreObject* parentObject, const std::string& fileName, const basicReaderInfo& /*bri*/) { std::ifstream file(fileName.c_str(), std::ios::in); - std::string line, line2; // line storage - std::string temp1; // temporary storage for substrings + std::string line; // line storage + std::string line2; if (!(file.is_open())) { parentObject->log(parentObject, print_level::error, "Unable to open file " + fileName); @@ -66,7 +68,7 @@ void loadDYR(coreObject* parentObject, const std::string& fileName, const basicR } else if (type == "'ESDC1A'") { loadESDC1A(parentObject, lineTokens); } else if (type == "'EXDC2'") { - loadESDC1A(parentObject, lineTokens); + loadEXDC2(parentObject, lineTokens); } else if (type == "'TGOV1'") { loadTGOV1(parentObject, lineTokens); } else if (type == "'SEXS'") { @@ -77,145 +79,144 @@ void loadDYR(coreObject* parentObject, const std::string& fileName, const basicR } } -void loadGENROU(coreObject* parentObject, stringVec& tokens) -{ - int id = std::stoi(tokens[0]); - gridBus* bus = static_cast(parentObject->findByUserID("bus", id)); - id = std::stoi(tokens[2]); - Generator* gen = bus->getGen(id - 1); - - auto params = gmlc::utilities::str2vector(tokens, kNullVal); - - GenModel* sm = static_cast(cof->createObject("genmodel", "6")); - sm->set("tdop", params[3]); - sm->set("tdopp", params[4]); - sm->set("tqop", params[5]); - sm->set("tqopp", params[6]); - sm->set("h", params[7]); - sm->set("d", params[8]); - sm->set("xd", params[9]); - sm->set("xq", params[10]); - sm->set("xdp", params[11]); - sm->set("xqp", params[12]); - sm->set("xdpp", params[13]); - sm->set("xqpp", params[13]); - sm->set("xl", params[14]); - sm->set("s1", params[15]); - sm->set("s12", params[16]); - - gen->add(sm); -} - -void loadESDC1A(coreObject* parentObject, stringVec& tokens) -{ - int id = std::stoi(tokens[0]); - gridBus* bus = static_cast(parentObject->findByUserID("bus", id)); - id = std::stoi(tokens[2]); - Generator* gen = bus->getGen(id - 1); - - auto params = gmlc::utilities::str2vector(tokens, kNullVal); - Exciter* sm; - if (params[6] > 0.0) // dc1a model must have tb>0 otherwise revert to type1 +namespace { + void loadGENROU(coreObject* parentObject, stringVec& tokens) { - sm = static_cast(cof->createObject("exciter", "dc1a")); - } else { - sm = static_cast(cof->createObject("exciter", "type1")); + const int busId = std::stoi(tokens[0]); + const auto* bus = static_cast(parentObject->findByUserID("bus", busId)); + const int genId = std::stoi(tokens[2]); + auto* gen = bus->getGen(genId - 1); + + auto params = gmlc::utilities::str2vector(tokens, kNullVal); + + auto cof = coreObjectFactory::instance(); + auto* genModel = static_cast(cof->createObject("genmodel", "6")); + genModel->set("tdop", params[3]); + genModel->set("tdopp", params[4]); + genModel->set("tqop", params[5]); + genModel->set("tqopp", params[6]); + genModel->set("h", params[7]); + genModel->set("d", params[8]); + genModel->set("xd", params[9]); + genModel->set("xq", params[10]); + genModel->set("xdp", params[11]); + genModel->set("xqp", params[12]); + genModel->set("xdpp", params[13]); + genModel->set("xqpp", params[13]); + genModel->set("xl", params[14]); + genModel->set("s1", params[15]); + genModel->set("s12", params[16]); + + gen->add(genModel); } - // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. - // sm->set("tr", params[3]); - sm->set("ka", params[4]); - sm->set("ta", params[5]); - if (params[6] > 0) { - sm->set("tb", params[6]); - sm->set("tc", params[7]); + + void loadESDC1A(coreObject* parentObject, stringVec& tokens) + { + const int busId = std::stoi(tokens[0]); + const auto* bus = static_cast(parentObject->findByUserID("bus", busId)); + const int genId = std::stoi(tokens[2]); + auto* gen = bus->getGen(genId - 1); + + auto params = gmlc::utilities::str2vector(tokens, kNullVal); + Exciter* exciterModel; + auto cof = coreObjectFactory::instance(); + if (params[6] > 0.0) // dc1a model must have tb>0 otherwise revert to type1 + { + exciterModel = static_cast(cof->createObject("exciter", "dc1a")); + } else { + exciterModel = static_cast(cof->createObject("exciter", "type1")); + } + // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. + // exciterModel->set("tr", params[3]); + exciterModel->set("ka", params[4]); + exciterModel->set("ta", params[5]); + if (params[6] > 0) { + exciterModel->set("tb", params[6]); + exciterModel->set("tc", params[7]); + } + exciterModel->set("vrmax", params[8]); + exciterModel->set("vrmin", params[9]); + exciterModel->set("ke", params[10]); + exciterModel->set("te", params[11]); + exciterModel->set("kf", params[12]); + exciterModel->set("tf", params[13]); + // TODO(phlpt): Compute the saturation coefficients to translate appropriately. + + gen->add(exciterModel); } - sm->set("vrmax", params[8]); - sm->set("vrmin", params[9]); - sm->set("ke", params[10]); - sm->set("te", params[11]); - sm->set("kf", params[12]); - sm->set("tf", params[13]); - // TODO(phlpt): Compute the saturation coefficients to translate appropriately. - - gen->add(sm); -} -void loadEXDC2(coreObject* parentObject, stringVec& tokens) -{ - int id = std::stoi(tokens[0]); - gridBus* bus = static_cast(parentObject->findByUserID("bus", id)); - id = std::stoi(tokens[2]); - Generator* gen = bus->getGen(id - 1); - - auto params = gmlc::utilities::str2vector(tokens, kNullVal); - - Exciter* sm = static_cast(cof->createObject("exciter", "dc2a")); - // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. - // sm->set("tr", params[3]); - sm->set("ka", params[4]); - sm->set("ta", params[5]); - sm->set("tb", params[6]); - sm->set("tc", params[7]); - sm->set("vrmax", params[8]); - sm->set("vrmin", params[9]); - sm->set("ke", params[10]); - sm->set("te", params[11]); - sm->set("kf", params[12]); - sm->set("tf", params[13]); - // TODO(phlpt): Compute the saturation coefficients to translate appropriately. - - gen->add(sm); -} + void loadEXDC2(coreObject* parentObject, stringVec& tokens) + { + const int busId = std::stoi(tokens[0]); + const auto* bus = static_cast(parentObject->findByUserID("bus", busId)); + const int genId = std::stoi(tokens[2]); + auto* gen = bus->getGen(genId - 1); + + auto params = gmlc::utilities::str2vector(tokens, kNullVal); + + auto cof = coreObjectFactory::instance(); + auto* exciterModel = static_cast(cof->createObject("exciter", "dc2a")); + // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. + // exciterModel->set("tr", params[3]); + exciterModel->set("ka", params[4]); + exciterModel->set("ta", params[5]); + exciterModel->set("tb", params[6]); + exciterModel->set("tc", params[7]); + exciterModel->set("vrmax", params[8]); + exciterModel->set("vrmin", params[9]); + exciterModel->set("ke", params[10]); + exciterModel->set("te", params[11]); + exciterModel->set("kf", params[12]); + exciterModel->set("tf", params[13]); + // TODO(phlpt): Compute the saturation coefficients to translate appropriately. + + gen->add(exciterModel); + } -void loadSEXS(coreObject* parentObject, stringVec& tokens) -{ - int id = std::stoi(tokens[0]); - gridBus* bus = static_cast(parentObject->findByUserID("bus", id)); - id = std::stoi(tokens[2]); - Generator* gen = bus->getGen(id - 1); - - auto params = gmlc::utilities::str2vector(tokens, kNullVal); - Exciter* sm; - - sm = static_cast(cof->createObject("exciter", "dc1a")); - - // sm->set("tr", params[3]); - sm->set("ka", params[5]); - sm->set("ta", params[6]); - // if (params[6] > 0) { - sm->set("tb", params[4]); - sm->set("tc", params[3] * params[4]); - //} - sm->set("vrmax", params[8]); - sm->set("vrmin", params[7]); - sm->set("ke", 1.0); - sm->set("te", 0.0001); - sm->set("kf", 0.0); - sm->set("tf", 1.0); - - gen->add(sm); -} -void loadTGOV1(coreObject* parentObject, stringVec& tokens) -{ - int id = std::stoi(tokens[0]); - gridBus* bus = static_cast(parentObject->findByUserID("bus", id)); - id = std::stoi(tokens[2]); - Generator* gen = bus->getGen(id - 1); - - auto params = gmlc::utilities::str2vector(tokens, kNullVal); - - Governor* sm = static_cast(cof->createObject("governor", "tgov1")); - // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. - // sm->set("tr", params[3]); - sm->set("r", params[3]); - sm->set("t1", params[4]); - sm->set("pmax", params[5]); - sm->set("pmin", params[6]); - sm->set("t2", params[6]); - sm->set("t3", params[7]); - sm->set("dt", params[8]); - - gen->add(sm); -} + void loadSEXS(coreObject* parentObject, stringVec& tokens) + { + const int busId = std::stoi(tokens[0]); + const auto* bus = static_cast(parentObject->findByUserID("bus", busId)); + const int genId = std::stoi(tokens[2]); + auto* gen = bus->getGen(genId - 1); + + auto params = gmlc::utilities::str2vector(tokens, kNullVal); + auto cof = coreObjectFactory::instance(); + auto* exciterModel = static_cast(cof->createObject("exciter", "sexs")); + + // exciterModel->set("tr", params[3]); + exciterModel->set("ka", params[5]); + exciterModel->set("tb", params[4]); + exciterModel->set("ta", params[3] * params[4]); + exciterModel->set("te", params[6]); + exciterModel->set("vrmax", params[8]); + exciterModel->set("vrmin", params[7]); + + gen->add(exciterModel); + } + void loadTGOV1(coreObject* parentObject, stringVec& tokens) + { + const int busId = std::stoi(tokens[0]); + const auto* bus = static_cast(parentObject->findByUserID("bus", busId)); + const int genId = std::stoi(tokens[2]); + auto* gen = bus->getGen(genId - 1); + + auto params = gmlc::utilities::str2vector(tokens, kNullVal); + + auto cof = coreObjectFactory::instance(); + auto* governorModel = static_cast(cof->createObject("governor", "tgov1")); + // TODO(phlpt): TR not implemented yet; no voltage compensation implemented. + // governorModel->set("tr", params[3]); + governorModel->set("r", params[3]); + governorModel->set("t1", params[4]); + governorModel->set("pmax", params[5]); + governorModel->set("pmin", params[6]); + governorModel->set("t2", params[6]); + governorModel->set("t3", params[7]); + governorModel->set("dt", params[8]); + + gen->add(governorModel); + } +} // namespace } // namespace griddyn diff --git a/src/griddyn/CMakeLists.txt b/src/griddyn/CMakeLists.txt index 2071f0f8..36ec8fa3 100644 --- a/src/griddyn/CMakeLists.txt +++ b/src/griddyn/CMakeLists.txt @@ -83,6 +83,7 @@ set(exciter_headers exciters/ExciterDC2A.h exciters/ExciterIEEEtype1.h exciters/ExciterIEEEtype2.h + exciters/ExciterSEXS.h ) set(exciter_sources @@ -92,6 +93,7 @@ set(exciter_sources exciters/ExciterDC2A.cpp exciters/ExciterIEEEtype1.cpp exciters/ExciterIEEEtype2.cpp + exciters/ExciterSEXS.cpp stabilizers/Stabilizer.cpp ) diff --git a/src/griddyn/Exciter.h b/src/griddyn/Exciter.h index ea423f94..cfebc81c 100644 --- a/src/griddyn/Exciter.h +++ b/src/griddyn/Exciter.h @@ -8,6 +8,7 @@ #define ___W_GRIDDYN_GRIDDYN_SRC_GRIDDYN_EXCITER_H_ #include "gridSubModel.h" +#include "units/units_decl.hpp" #include #include namespace griddyn { @@ -56,30 +57,30 @@ class Exciter: public gridSubModel { virtual stringVec localStateNames() const override; virtual void residual(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double resid[], - const solverMode& sMode) override; + const solverMode& solverMode) override; virtual void derivative(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double deriv[], - const solverMode& sMode) override; + const solverMode& solverMode) override; virtual void jacobianElements(const IOdata& inputs, - const stateData& sD, - matrixData& md, + const stateData& stateData, + matrixData& matrix, const IOlocs& inputLocs, - const solverMode& sMode) override; + const solverMode& solverMode) override; // handle the rootfinding functions virtual void rootTest(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double root[], - const solverMode& sMode) override; + const solverMode& solverMode) override; virtual void rootTrigger(coreTime time, const IOdata& inputs, const std::vector& rootMask, - const solverMode& sMode) override; + const solverMode& solverMode) override; virtual change_code rootCheck(const IOdata& inputs, - const stateData& sD, - const solverMode& sMode, + const stateData& stateData, + const solverMode& solverMode, check_level_t level) override; // virtual void setTime(coreTime time){prevTime=time;}; diff --git a/src/griddyn/exciters/Exciter.cpp b/src/griddyn/exciters/Exciter.cpp index 9c259f57..97035a13 100644 --- a/src/griddyn/exciters/Exciter.cpp +++ b/src/griddyn/exciters/Exciter.cpp @@ -4,31 +4,43 @@ * SPDX-License-Identifier: BSD-3-Clause */ +#include "../Exciter.h" + #include "../Generator.h" -#include "../gridBus.h" +#include "../gridComponentHelperClasses.h" +#include "../gridDynDefinitions.hpp" +#include "../gridPrimary.h" +#include "ExciterDC1A.h" #include "ExciterDC2A.h" +#include "ExciterIEEEtype1.h" #include "ExciterIEEEtype2.h" +#include "ExciterSEXS.h" +#include "core/coreDefinitions.hpp" +#include "core/coreObject.h" #include "core/coreObjectTemplates.hpp" #include "core/objectFactoryTemplates.hpp" +#include "solvers/solverMode.hpp" +#include "units/units.hpp" #include "utilities/matrixData.hpp" #include -#include +#include #include #include -// note that there is only 1 dynamic state since V_R = E_f - namespace griddyn { namespace exciters { - // setup the object factories - static childTypeFactory gfe1("exciter", "dc1a"); - static childTypeFactory gfe2("exciter", "dc2a"); - static childTypeFactory gfet1("exciter", "type1"); - static typeFactory gf("exciter", - stringVec{"basic", "fast"}, - "type1"); // setup type 1 as the default - static childTypeFactory gfet2("exciter", "type2"); - + namespace { + // setup the object factories + static childTypeFactory gfeDc1a("exciter", "dc1a"); // NOLINT + static childTypeFactory gfeDc2a("exciter", "dc2a"); // NOLINT + static childTypeFactory gfeType1("exciter", "type1"); // NOLINT + static typeFactory gfeDefault( // NOLINT + "exciter", + stringVec{"basic", "fast"}, + "type1"); // setup type 1 as the default + static childTypeFactory gfeType2("exciter", "type2"); // NOLINT + static childTypeFactory gfeSexs("exciter", "sexs"); // NOLINT + } // namespace } // namespace exciters Exciter::Exciter(const std::string& objName): gridSubModel(objName) @@ -36,7 +48,7 @@ Exciter::Exciter(const std::string& objName): gridSubModel(objName) m_inputSize = 4; m_outputSize = 1; } -// cloning function + coreObject* Exciter::clone(coreObject* obj) const { auto* gdE = cloneBase(this, obj); @@ -67,96 +79,95 @@ void Exciter::checkForLimits() offsets.local().local.algRoots = 1; } } -// initial conditions + +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void Exciter::dynObjectInitializeB(const IOdata& inputs, const IOdata& desiredOutput, IOdata& fieldSet) { - double* gs = m_state.data(); - double V = inputs[voltageInLocation]; + auto* stateValues = m_state.data(); + const double voltage = inputs[voltageInLocation]; if (desiredOutput.empty() || (desiredOutput[0] == kNullVal)) { - gs[0] = (Vref + vBias - V) / Ka; - fieldSet[0] = gs[0]; + stateValues[0] = (Vref + vBias - voltage) / Ka; + fieldSet[0] = stateValues[0]; } else { - gs[0] = desiredOutput[0]; + stateValues[0] = desiredOutput[0]; - vBias = V - Vref + gs[0] / Ka; + vBias = voltage - Vref + (stateValues[0] / Ka); fieldSet[exciterVsetInLocation] = Vref; } } -// residual void Exciter::residual(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double resid[], - const solverMode& sMode) + const solverMode& solverMode) { - if (isAlgebraicOnly(sMode)) { + if (isAlgebraicOnly(solverMode)) { return; } - auto offset = offsets.getDiffOffset(sMode); - const double* es = sD.state + offset; - const double* esp = sD.dstate_dt + offset; - double* rv = resid + offset; + auto offset = offsets.getDiffOffset(solverMode); + const auto* exciterState = stateData.state + offset; + const auto* exciterStateDerivatives = stateData.dstate_dt + offset; + auto* residualValues = resid + offset; if (opFlags[outside_vlim]) { - rv[0] = -esp[0]; + residualValues[0] = -exciterStateDerivatives[0]; } else { - rv[0] = (-es[0] + Ka * (Vref + vBias - inputs[voltageInLocation])) / Ta - esp[0]; + residualValues[0] = + (((-exciterState[0]) + (Ka * (Vref + vBias - inputs[voltageInLocation]))) / Ta) - + exciterStateDerivatives[0]; } } void Exciter::derivative(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double deriv[], - const solverMode& sMode) + const solverMode& solverMode) { - auto Loc = offsets.getLocations(sD, deriv, sMode, this); - const double* es = Loc.diffStateLoc; - double* d = Loc.destDiffLoc; + auto locations = offsets.getLocations(stateData, deriv, solverMode, this); + const auto* exciterState = locations.diffStateLoc; + auto* derivatives = locations.destDiffLoc; if (opFlags[outside_vlim]) { - d[0] = 0.0; + derivatives[0] = 0.0; } else { - d[0] = (-es[0] + Ka * (Vref + vBias - inputs[voltageInLocation])) / Ta; + derivatives[0] = + ((-exciterState[0]) + (Ka * (Vref + vBias - inputs[voltageInLocation]))) / Ta; } } -// Jacobian void Exciter::jacobianElements(const IOdata& /*inputs*/, - const stateData& sD, - matrixData& md, + const stateData& stateData, + matrixData& matrix, const IOlocs& inputLocs, - const solverMode& sMode) + const solverMode& solverMode) { - if (isAlgebraicOnly(sMode)) { + if (isAlgebraicOnly(solverMode)) { return; } - auto offset = offsets.getDiffOffset(sMode); + auto offset = offsets.getDiffOffset(solverMode); - // Ef (Vr) if (opFlags[outside_vlim]) { - md.assign(offset, offset, -sD.cj); + matrix.assign(offset, offset, -stateData.cj); } else { - md.assign(offset, offset, -1.0 / Ta - sD.cj); - md.assignCheckCol(offset, inputLocs[voltageInLocation], -Ka / Ta); + matrix.assign(offset, offset, (-1.0 / Ta) - stateData.cj); + matrix.assignCheckCol(offset, inputLocs[voltageInLocation], -Ka / Ta); } - - // printf("%f\n",sD.cj); } void Exciter::rootTest(const IOdata& inputs, - const stateData& sD, + const stateData& stateData, double root[], - const solverMode& sMode) + const solverMode& solverMode) { - auto offset = offsets.getDiffOffset(sMode); - int rootOffset = offsets.getRootOffset(sMode); - double Efield = sD.state[offset]; + auto offset = offsets.getDiffOffset(solverMode); + const auto rootOffset = offsets.getRootOffset(solverMode); + const double eField = stateData.state[offset]; if (opFlags[outside_vlim]) { root[rootOffset] = Vref + vBias - inputs[voltageInLocation]; } else { - root[rootOffset] = std::min(Vrmax - Efield, Efield - Vrmin) + 0.0001; - if (Efield > Vrmax) { + root[rootOffset] = std::min(Vrmax - eField, eField - Vrmin) + 0.0001; + if (eField > Vrmax) { opFlags.set(etrigger_high); } } @@ -165,9 +176,9 @@ void Exciter::rootTest(const IOdata& inputs, void Exciter::rootTrigger(coreTime time, const IOdata& inputs, const std::vector& rootMask, - const solverMode& sMode) + const solverMode& solverMode) { - int rootOffset = offsets.getRootOffset(sMode); + const auto rootOffset = offsets.getRootOffset(solverMode); if (rootMask[rootOffset] != 0) { if (opFlags[outside_vlim]) { LOG_NORMAL("root trigger back in bounds"); @@ -185,21 +196,21 @@ void Exciter::rootTrigger(coreTime time, } alert(this, JAC_COUNT_DECREASE); } - stateData sD(time, m_state.data()); + const stateData stateData(time, m_state.data()); - derivative(inputs, sD, m_dstate_dt.data(), cLocalSolverMode); + derivative(inputs, stateData, m_dstate_dt.data(), cLocalSolverMode); } } change_code Exciter::rootCheck(const IOdata& inputs, - const stateData& /*sD*/, - const solverMode& /*sMode*/, + const stateData& /*stateData*/, + const solverMode& /*solverMode*/, check_level_t /*level*/) { - double Efield = m_state[0]; + const double eField = m_state[0]; change_code ret = change_code::no_change; if (opFlags[outside_vlim]) { - double test = Vref + vBias - inputs[voltageInLocation]; + const double test = Vref + vBias - inputs[voltageInLocation]; if (opFlags[etrigger_high]) { if (test < 0) { opFlags.reset(outside_vlim); @@ -215,13 +226,13 @@ change_code Exciter::rootCheck(const IOdata& inputs, } } } else { - if (Efield > Vrmax + 0.0001) { + if (eField > Vrmax + 0.0001) { opFlags.set(etrigger_high); opFlags.set(outside_vlim); m_state[0] = Vrmax; alert(this, JAC_COUNT_DECREASE); ret = change_code::jacobian_change; - } else if (Efield < Vrmin - 0.0001) { + } else if (eField < Vrmin - 0.0001) { opFlags.set(outside_vlim); m_state[0] = Vrmin; alert(this, JAC_COUNT_DECREASE); @@ -231,17 +242,16 @@ change_code Exciter::rootCheck(const IOdata& inputs, return ret; } -static const stringVec exciterFields{"ef"}; - stringVec Exciter::localStateNames() const { - return exciterFields; + return {"ef"}; } + void Exciter::set(const std::string& param, const std::string& val) { - coreObject::set(param, val); + gridSubModel::set(param, val); } -// set parameters + void Exciter::set(const std::string& param, double val, units::unit unitType) { if (param == "vref") { @@ -261,24 +271,22 @@ void Exciter::set(const std::string& param, double val, units::unit unitType) } } -static const std::vector inputNamesStr{ - {"voltage", "v", "volt"}, - {"vset", "setpoint", "voltageset"}, - {"pmech", "power", "mechanicalpower"}, - {"omega", "frequency", "w", "f"}, -}; - const std::vector& Exciter::inputNames() const { + static const std::vector inputNamesStr{ + {"voltage", "v", "volt"}, + {"vset", "setpoint", "voltageset"}, + {"pmech", "power", "mechanicalpower"}, + {"omega", "frequency", "w", "f"}, + }; // NOLINT(bugprone-throwing-static-initialization) return inputNamesStr; } -static const std::vector outputNamesStr{ - {"e", "field", "exciter"}, -}; - const std::vector& Exciter::outputNames() const { + static const std::vector outputNamesStr{ + {"e", "field", "exciter"}, + }; // NOLINT(bugprone-throwing-static-initialization) return outputNamesStr; } diff --git a/src/griddyn/exciters/ExciterSEXS.cpp b/src/griddyn/exciters/ExciterSEXS.cpp new file mode 100644 index 00000000..c794bdf8 --- /dev/null +++ b/src/griddyn/exciters/ExciterSEXS.cpp @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2014-2020, Lawrence Livermore National Security + * See the top-level NOTICE for additional details. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "ExciterSEXS.h" + +#include "../Exciter.h" +#include "../gridComponentHelperClasses.h" +#include "../gridDynDefinitions.hpp" +#include "../gridPrimary.h" +#include "core/coreDefinitions.hpp" +#include "core/coreObject.h" +#include "core/coreObjectTemplates.hpp" +#include "solvers/solverMode.hpp" +#include "utilities/matrixData.hpp" +#include +#include +#include +#include + +namespace griddyn::exciters { + +ExciterSEXS::ExciterSEXS(const std::string& objName): Exciter(objName) +{ + Ka = 12.0; + Ta = 0.1; + Tb = 0.46; + Te = 0.5; + Vrmin = -0.9; + Vrmax = 1.0; +} + +coreObject* ExciterSEXS::clone(coreObject* obj) const +{ + auto* gdE = cloneBase(this, obj); + if (gdE == nullptr) { + return obj; + } + gdE->Te = Te; + gdE->Tb = Tb; + return gdE; +} + +void ExciterSEXS::dynObjectInitializeA(coreTime /*time0*/, std::uint32_t /*flags*/) +{ + offsets.local().local.diffSize = 2; + offsets.local().local.jacSize = 6; + checkForLimits(); +} + +void ExciterSEXS::dynObjectInitializeB(const IOdata& inputs, + const IOdata& desiredOutput, + IOdata& fieldSet) +{ + Exciter::dynObjectInitializeB(inputs, desiredOutput, fieldSet); + + auto* stateValues = m_state.data(); + const auto vErr = Vref + vBias - inputs[voltageInLocation]; + if (Tb != 0.0) { + stateValues[1] = (stateValues[0] / Ka) - ((Ta / Tb) * vErr); + } else { + stateValues[1] = 0.0; + } + + m_dstate_dt[0] = 0.0; + m_dstate_dt[1] = 0.0; +} + +void ExciterSEXS::set(const std::string& param, const std::string& val) +{ + Exciter::set(param, val); +} + +void ExciterSEXS::set(const std::string& param, double val, units::unit unitType) +{ + if (param == "tb") { + Tb = val; + } else if (param == "te") { + Te = val; + } else { + Exciter::set(param, val, unitType); + } +} + +stringVec ExciterSEXS::localStateNames() const +{ + return {"ef", "x"}; +} + +double ExciterSEXS::regulatorOutput(const IOdata& inputs, const double stateX) const +{ + const auto invTb = (Tb != 0.0) ? (1.0 / Tb) : 0.0; + const auto vErr = Vref + vBias - inputs[voltageInLocation]; + return Ka * (stateX + (Ta * invTb * vErr)); +} + +void ExciterSEXS::residual(const IOdata& inputs, + const stateData& stateData, + double resid[], + const solverMode& solverMode) +{ + if (!hasDifferential(solverMode)) { + return; + } + + derivative(inputs, stateData, resid, solverMode); + + auto offset = offsets.getDiffOffset(solverMode); + const auto* stateDerivatives = stateData.dstate_dt + offset; + resid[offset] -= stateDerivatives[0]; + resid[offset + 1] -= stateDerivatives[1]; +} + +void ExciterSEXS::derivative(const IOdata& inputs, + const stateData& stateData, + double deriv[], + const solverMode& solverMode) +{ + auto locations = offsets.getLocations(stateData, deriv, solverMode, this); + const auto* exciterState = locations.diffStateLoc; + auto* derivatives = locations.destDiffLoc; + + const auto invTe = (Te != 0.0) ? (1.0 / Te) : 0.0; + const auto invTb = (Tb != 0.0) ? (1.0 / Tb) : 0.0; + const auto vErr = Vref + vBias - inputs[voltageInLocation]; + const double regulatorVoltage = [&]() { + if (opFlags[outside_vlim]) { + return opFlags[etrigger_high] ? Vrmax : Vrmin; + } + return regulatorOutput(inputs, exciterState[1]); + }(); + + derivatives[0] = (-exciterState[0] + regulatorVoltage) * invTe; + derivatives[1] = (-exciterState[1] + ((1.0 - (Ta * invTb)) * vErr)) * invTb; +} + +void ExciterSEXS::jacobianElements(const IOdata& /*inputs*/, + const stateData& stateData, + matrixData& matrix, + const IOlocs& inputLocs, + const solverMode& solverMode) +{ + if (!hasDifferential(solverMode)) { + return; + } + + auto offset = offsets.getDiffOffset(solverMode); + const auto invTe = (Te != 0.0) ? (1.0 / Te) : 0.0; + const auto invTb = (Tb != 0.0) ? (1.0 / Tb) : 0.0; + matrix.assign(offset, offset, -invTe - stateData.cj); + + if (!opFlags[outside_vlim]) { + matrix.assign(offset, offset + 1, Ka * invTe); + matrix.assignCheckCol(offset, inputLocs[voltageInLocation], -(Ka * Ta * invTb * invTe)); + } + + matrix.assign(offset + 1, offset + 1, -invTb - stateData.cj); + matrix.assignCheckCol(offset + 1, + inputLocs[voltageInLocation], + -((1.0 - (Ta * invTb)) * invTb)); +} + +void ExciterSEXS::rootTest(const IOdata& inputs, + const stateData& stateData, + double root[], + const solverMode& solverMode) +{ + auto offset = offsets.getDiffOffset(solverMode); + auto rootOffset = offsets.getRootOffset(solverMode); + const auto regulatorVoltage = regulatorOutput(inputs, stateData.state[offset + 1]); + + if (opFlags[outside_vlim]) { + root[rootOffset] = + opFlags[etrigger_high] ? (Vrmax - regulatorVoltage) : (regulatorVoltage - Vrmin); + } else { + root[rootOffset] = std::min(Vrmax - regulatorVoltage, regulatorVoltage - Vrmin) + 0.00001; + if (regulatorVoltage >= Vrmax) { + opFlags.set(etrigger_high); + } + } +} + +void ExciterSEXS::rootTrigger(coreTime time, + const IOdata& inputs, + const std::vector& rootMask, + const solverMode& solverMode) +{ + auto rootOffset = offsets.getRootOffset(solverMode); + if (rootMask[rootOffset] == 0) { + return; + } + + if (opFlags[outside_vlim]) { + alert(this, JAC_COUNT_INCREASE); + opFlags.reset(outside_vlim); + opFlags.reset(etrigger_high); + } else { + const auto regulatorVoltage = regulatorOutput(inputs, m_state[1]); + opFlags.set(outside_vlim); + if (regulatorVoltage >= Vrmax) { + opFlags.set(etrigger_high); + } else { + opFlags.reset(etrigger_high); + } + alert(this, JAC_COUNT_DECREASE); + } + + const stateData state(time, m_state.data()); + derivative(inputs, state, m_dstate_dt.data(), solverMode); +} + +change_code ExciterSEXS::rootCheck(const IOdata& inputs, + const stateData& /*stateData*/, + const solverMode& /*solverMode*/, + check_level_t /*level*/) +{ + const auto regulatorVoltage = regulatorOutput(inputs, m_state[1]); + auto ret = change_code::no_change; + + if (opFlags[outside_vlim]) { + if (opFlags[etrigger_high]) { + if (regulatorVoltage < Vrmax) { + opFlags.reset(outside_vlim); + opFlags.reset(etrigger_high); + alert(this, JAC_COUNT_INCREASE); + ret = change_code::jacobian_change; + } + } else if (regulatorVoltage > Vrmin) { + opFlags.reset(outside_vlim); + alert(this, JAC_COUNT_INCREASE); + ret = change_code::jacobian_change; + } + } else if (regulatorVoltage > Vrmax + 0.00001) { + opFlags.set(etrigger_high); + opFlags.set(outside_vlim); + alert(this, JAC_COUNT_DECREASE); + ret = change_code::jacobian_change; + } else if (regulatorVoltage < Vrmin - 0.00001) { + opFlags.reset(etrigger_high); + opFlags.set(outside_vlim); + alert(this, JAC_COUNT_DECREASE); + ret = change_code::jacobian_change; + } + + return ret; +} + +} // namespace griddyn::exciters diff --git a/src/griddyn/exciters/ExciterSEXS.h b/src/griddyn/exciters/ExciterSEXS.h new file mode 100644 index 00000000..d39bca4a --- /dev/null +++ b/src/griddyn/exciters/ExciterSEXS.h @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014-2020, Lawrence Livermore National Security + * See the top-level NOTICE for additional details. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +#include "../gridDynDefinitions.hpp" +#include "Exciter.h" +#include "core/coreDefinitions.hpp" +#include "solvers/solverMode.hpp" +#include +#include + +namespace griddyn { +namespace exciters { + + /** @brief Simplified excitation system (SEXS) model. + * + * This model implements a lead-lag regulator with output limits followed by + * a first-order field lag. + */ + class ExciterSEXS: public Exciter { + protected: + model_parameter Te = 0.5; //!< [s] exciter field time constant + model_parameter Tb = 0.46; //!< [s] lead-lag denominator time constant + + public: + explicit ExciterSEXS(const std::string& objName = "exciterSEXS_#"); + coreObject* clone(coreObject* obj = nullptr) const override; + + void dynObjectInitializeA(coreTime time0, std::uint32_t flags) override; + void dynObjectInitializeB(const IOdata& inputs, + const IOdata& desiredOutput, + IOdata& fieldSet) override; + + void set(const std::string& param, const std::string& val) override; + void set(const std::string& param, + double val, + units::unit unitType = units::defunit) override; + + stringVec localStateNames() const override; + + void residual(const IOdata& inputs, + const stateData& stateData, + double resid[], + const solverMode& solverMode) override; + void derivative(const IOdata& inputs, + const stateData& stateData, + double deriv[], + const solverMode& solverMode) override; + void jacobianElements(const IOdata& inputs, + const stateData& stateData, + matrixData& matrix, + const IOlocs& inputLocs, + const solverMode& solverMode) override; + + void rootTest(const IOdata& inputs, + const stateData& stateData, + double root[], + const solverMode& solverMode) override; + void rootTrigger(coreTime time, + const IOdata& inputs, + const std::vector& rootMask, + const solverMode& solverMode) override; + change_code rootCheck(const IOdata& inputs, + const stateData& stateData, + const solverMode& solverMode, + check_level_t level) override; + + private: + double regulatorOutput(const IOdata& inputs, const double stateX) const; + }; + +} // namespace exciters +} // namespace griddyn diff --git a/test/.clang-tidy b/test/.clang-tidy index a572c087..deb0e7b6 100644 --- a/test/.clang-tidy +++ b/test/.clang-tidy @@ -13,10 +13,12 @@ Checks: > llvm-namespace-comment, modernize*, -modernize-pass-by-value, + -modernize-use-ranges, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, clang-analyzer-*, bugprone-*, + -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, cert-*, -cert-err58-cpp, @@ -36,10 +38,12 @@ WarningsAsErrors: > llvm-namespace-comment, modernize*, -modernize-pass-by-value, + -modernize-use-ranges, -modernize-use-trailing-return-type, -modernize-avoid-c-arrays, clang-analyzer-*, bugprone-*, + -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, portability-*, readability-*, diff --git a/test/componentTests/testExciters.cpp b/test/componentTests/testExciters.cpp index 6444f589..324cf404 100644 --- a/test/componentTests/testExciters.cpp +++ b/test/componentTests/testExciters.cpp @@ -5,306 +5,188 @@ */ #include "../gtestHelper.h" +#include "core/coreDefinitions.hpp" #include "core/objectFactory.hpp" +#include "fileInput/fileInput.h" #include "gmlc/utilities/vectorOps.hpp" #include "griddyn/Generator.h" -#include -#include +#include "solvers/solverMode.hpp" #include #include #include +#include #include -// test case for coreObject object #define EXCITER_TEST_DIRECTORY GRIDDYN_TEST_DIRECTORY "/exciter_tests/" using namespace griddyn; -class ExciterTests: public gridDynSimulationTestFixture, public ::testing::Test {}; - -TEST_F(ExciterTests, RootExciterTest) -{ - std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_root_exciter.xml"); - - readerConfig::setPrintMode(0); - gds = readSimXMLFile(fileName); +namespace { - int retval = gds->dynInitialize(); - EXPECT_EQ(retval, 0); - requireState(gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); +using exciter_parameter_map = std::map>>; - std::vector st = gds->getState(); +// NOLINTNEXTLINE(misc-multiple-inheritance) +class ExciterTests: public gridDynSimulationTestFixture, public ::testing::Test {}; - gds->run(); - requireState(gridDynSimulation::gridState_t::DYNAMIC_COMPLETE); - std::vector st2 = gds->getState(); +void applyExciterParameters(coreObject* object, + const exciter_parameter_map& parameters, + const std::string& exciterName) +{ + const auto parameterIter = parameters.find(exciterName); + if (parameterIter == parameters.end()) { + return; + } - // check for stability - auto diff = gmlc::utilities::countDiffs(st, st2, 0.0001); - EXPECT_EQ(diff, 0u); + for (const auto& parameterValue : parameterIter->second) { + object->set(parameterValue.first, parameterValue.second); + } } -TEST_F(ExciterTests, BasicStabilityTest1) +// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) +Generator* loadExciterCase(ExciterTests& fixture, + coreObjectFactory* factory, + const std::string& caseFileName, + const std::string& exciterName, + const exciter_parameter_map& parameters) { - static const std::map>> parameters{ - {"basic", {{"ta", 0.2}, {"ka", 11.0}}}, - {"dc1a", {{"ta", 0.1}, {"ka", 6.0}}}, - {"dc2a", {{"ta", 0.1}, {"ka", 6.0}}}, - }; + fixture.gds = readSimXMLFile(caseFileName); + auto* generator = fixture.gds->getGen(0); + EXPECT_NE(generator, nullptr); + if (generator == nullptr) { + return nullptr; + } - std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability.xml"); + fixture.gds->consolePrintLevel = print_level::no_print; + auto* object = factory->createObject("exciter", exciterName); + EXPECT_NE(object, nullptr) << "Failed to create exciter " << exciterName; + if (object == nullptr) { + return nullptr; + } - auto cof = coreObjectFactory::instance(); + applyExciterParameters(object, parameters, exciterName); + generator->add(object); + return generator; +} - auto exclist = cof->getTypeNames("exciter"); +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +void verifyStabilityCase(ExciterTests& fixture, + const std::string& caseFileName, + const exciter_parameter_map& parameters, + double minVoltage0, + double maxVoltage0, + double minVoltage1, + double maxVoltage1, + const std::vector& skippedExcters = {}) +{ + auto factory = coreObjectFactory::instance(); + const auto exciterList = factory->getTypeNames("exciter"); - // exclist.insert(exclist.begin(), "none"); - for (auto& excname : exclist) { - if (excname.compare(0, 3, "fmi") == 0) { + for (const auto& exciterName : exciterList) { + if (exciterName.starts_with("fmi")) { + continue; + } + if (std::find(skippedExcters.begin(), skippedExcters.end(), exciterName) != + skippedExcters.end()) { continue; } - gds = readSimXMLFile(fileName); - Generator* gen = gds->getGen(0); - ASSERT_NE(gen, nullptr); - gds->consolePrintLevel = print_level::no_print; - auto obj = cof->createObject("exciter", excname); - ASSERT_NE(obj, nullptr) << "Failed to create exciter " << excname; - auto fnd = parameters.find(excname); + auto* generator = + loadExciterCase(fixture, factory.get(), caseFileName, exciterName, parameters); + ASSERT_NE(generator, nullptr); - if (fnd != parameters.end()) { - for (auto& pp : fnd->second) { - obj->set(pp.first, pp.second); - } - } + const int returnValue = fixture.gds->dynInitialize(); + fixture.requireState(gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); + EXPECT_EQ(returnValue, 0) << "Exciter " << exciterName << " dynInitialize issue"; - gen->add(obj); + const int badResidual = runResidualCheck(fixture.gds, cDaeSolverMode, false); + ASSERT_EQ(badResidual, 0) << "Exciter " << exciterName << " residual issue"; + const int badJacobian = runJacobianCheck(fixture.gds, cDaeSolverMode, false); + ASSERT_EQ(badJacobian, 0) << "Exciter " << exciterName << " Jacobian issue"; - int retval = gds->dynInitialize(); - requireState(gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); + fixture.gds->run(); + if (fixture.gds->getSimulationTime() < 30.0) { + fixture.gds->saveRecorders(); + } + ASSERT_GE(fixture.gds->getSimulationTime(), 30.0) + << "Exciter " << exciterName << " didn't complete"; + + std::vector voltages; + fixture.gds->getVoltage(voltages); + EXPECT_TRUE((voltages[0] > minVoltage0) && (voltages[0] < maxVoltage0)) + << "Exciter " << exciterName; + EXPECT_TRUE((voltages[1] > minVoltage1) && (voltages[1] < maxVoltage1)) + << "Exciter " << exciterName; + } +} - EXPECT_EQ(retval, 0) << "Exciter " << excname << " dynInitialize issue"; +} // namespace - int badresid = runResidualCheck(gds, cDaeSolverMode, false); +TEST_F(ExciterTests, RootExciterTest) +{ + const std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_root_exciter.xml"); - ASSERT_EQ(badresid, 0) << "exciter type " << excname << " resid issue"; - int badjacobian = runJacobianCheck(gds, cDaeSolverMode, false); - ASSERT_EQ(badjacobian, 0) << "exciter type " << excname << " Jacobian issue"; + readerConfig::setPrintMode(0); + gds = readSimXMLFile(fileName); - std::vector volt1; - gds->getVoltage(volt1); + const int retval = gds->dynInitialize(); + EXPECT_EQ(retval, 0); + requireState(gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); - gds->run(); + const std::vector initialState = gds->getState(); - ASSERT_GE(gds->getSimulationTime(), 30.0) - << "exciter type " << excname << " didn't complete"; - std::vector volt2; - gds->getVoltage(volt2); - EXPECT_TRUE((volt2[0] > 0.95) && (volt2[0] < 1.00)); - EXPECT_TRUE((volt2[1] > 0.95) && (volt2[1] < 1.000)); + gds->run(); + requireState(gridDynSimulation::gridState_t::DYNAMIC_COMPLETE); + const std::vector finalState = gds->getState(); - // check for stability - } + // check for stability + const auto diff = gmlc::utilities::countDiffs(initialState, finalState, 0.0001); + EXPECT_EQ(diff, 0U); } -TEST_F(ExciterTests, BasicStabilityTest2) +TEST_F(ExciterTests, BasicStabilityTest1) { - static const std::map>> parameters{ + static const exciter_parameter_map parameters{ {"basic", {{"ta", 0.2}, {"ka", 11.0}}}, {"dc1a", {{"ta", 0.1}, {"ka", 6.0}}}, {"dc2a", {{"ta", 0.1}, {"ka", 6.0}}}, }; - std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability2.xml"); - - auto cof = coreObjectFactory::instance(); - auto exclist = cof->getTypeNames("exciter"); - - // exclist.insert(exclist.begin(), "none"); - for (auto& excname : exclist) { - if (excname.compare(0, 3, "fmi") == 0) { - continue; - } - gds = readSimXMLFile(fileName); - Generator* gen = gds->getGen(0); - ASSERT_NE(gen, nullptr); - gds->consolePrintLevel = print_level::no_print; - auto obj = cof->createObject("exciter", excname); - ASSERT_NE(obj, nullptr) << "Failed to create exciter " << excname; - auto fnd = parameters.find(excname); - - if (fnd != parameters.end()) { - for (auto& pp : fnd->second) { - obj->set(pp.first, pp.second); - } - } - - gen->add(obj); - - int retval = gds->dynInitialize(); - ASSERT_EQ(gds->currentProcessState(), gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); - - EXPECT_EQ(retval, 0) << "Exciter " << excname << " dynInitialize issue"; - - int badresid = runResidualCheck(gds, cDaeSolverMode, false); - ASSERT_EQ(badresid, 0) << "Exciter " << excname << " residual issue"; - int badjacobian = runJacobianCheck(gds, cDaeSolverMode, false); - - ASSERT_EQ(badjacobian, 0) << "Exciter " << excname << " Jacobian issue"; - - std::vector volt1; - gds->getVoltage(volt1); + const std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability.xml"); + verifyStabilityCase(*this, fileName, parameters, 0.95, 1.00, 0.95, 1.000); +} - gds->run(); - if (gds->getSimulationTime() < 30.0) { - printf("exciter didn't complete %s\n", excname.c_str()); - gds->saveRecorders(); - } - ASSERT_GE(gds->getSimulationTime(), 30.0) << "Exciter " << excname << " didn't complete"; - std::vector volt2; - gds->getVoltage(volt2); - EXPECT_TRUE((volt2[0] > 1.00) && (volt2[0] < 1.05)); - EXPECT_TRUE((volt2[1] > 0.99) && (volt2[1] < 1.04)); +TEST_F(ExciterTests, BasicStabilityTest2) +{ + static const exciter_parameter_map parameters{ + {"basic", {{"ta", 0.2}, {"ka", 11.0}}}, + {"dc1a", {{"ta", 0.1}, {"ka", 6.0}}}, + {"dc2a", {{"ta", 0.1}, {"ka", 6.0}}}, + }; - // check for stability - } + const std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability2.xml"); + verifyStabilityCase(*this, fileName, parameters, 1.00, 1.05, 0.99, 1.04); } TEST_F(ExciterTests, BasicStabilityTest3) { - static const std::map>> parameters{ - //{ "basic",{ { "ta",0.2 },{ "ka",11.0 } } }, + static const exciter_parameter_map parameters{ {"dc1a", {{"ta", 0.1}, {"ka", 6.0}}}, {"dc2a", {{"ta", 0.3}, {"ka", 6.0}}}, }; - std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability3.xml"); - - auto cof = coreObjectFactory::instance(); - coreObject* obj = nullptr; - - auto exclist = cof->getTypeNames("exciter"); - - // exclist.insert(exclist.begin(), "none"); - for (auto& excname : exclist) { - if (excname.compare(0, 3, "fmi") == 0) { - continue; - } - if (excname == "dc1a") { - // TODO(phlpt): Figure out why this still fails. - continue; - } - gds = readSimXMLFile(fileName); - Generator* gen = gds->getGen(0); - ASSERT_NE(gen, nullptr); - gds->consolePrintLevel = print_level::no_print; - obj = cof->createObject("exciter", excname); - ASSERT_NE(obj, nullptr) << "Failed to create exciter " << excname; - auto fnd = parameters.find(excname); - - if (fnd != parameters.end()) { - for (auto& pp : fnd->second) { - obj->set(pp.first, pp.second); - } - } - - gen->add(obj); - - int retval = gds->dynInitialize(); - ASSERT_EQ(gds->currentProcessState(), gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); - - EXPECT_EQ(retval, 0) << "Exciter " << excname << " dynInitialize issue"; - - int badresid = runResidualCheck(gds, cDaeSolverMode, false); - - ASSERT_EQ(badresid, 0) << "Exciter " << excname << " residual issue"; - int badjacobian = runJacobianCheck(gds, cDaeSolverMode, false); - - ASSERT_EQ(badjacobian, 0) << "Exciter " << excname << " Jacobian issue"; - - std::vector volt1; - gds->getVoltage(volt1); - - gds->run(); - if (gds->getSimulationTime() < 30.0) { - printf("exciter didn't complete %s\n", excname.c_str()); - gds->saveRecorders(); - } - ASSERT_GE(gds->getSimulationTime(), 30.0) << "Exciter " << excname << " didn't complete"; - std::vector volt2; - gds->getVoltage(volt2); - EXPECT_TRUE((volt2[0] > 0.98) && (volt2[0] < 1.02)); - EXPECT_TRUE((volt2[1] > 0.97) && (volt2[1] < 1.02)); - // check for stability - } + const std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability3.xml"); + verifyStabilityCase(*this, fileName, parameters, 0.98, 1.02, 0.97, 1.02, {"dc1a", "sexs"}); } TEST_F(ExciterTests, BasicStabilityTest4) { - static const std::map>> parameters{ - //{ "basic",{ { "ta",0.2 },{ "ka",11.0 } } }, + static const exciter_parameter_map parameters{ {"dc1a", {{"ta", 0.1}, {"ka", 6.0}}}, {"dc2a", {{"ta", 0.3}, {"ka", 6.0}}}, }; - std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability4.xml"); - - auto cof = coreObjectFactory::instance(); - coreObject* obj = nullptr; - - auto exclist = cof->getTypeNames("exciter"); - - // exclist.insert(exclist.begin(), "none"); - for (auto& excname : exclist) { - if (excname.compare(0, 3, "fmi") == 0) { - continue; - } - if (excname == "dc1a") { - // TODO(phlpt): Figure out why this still fails. - continue; - } - gds = readSimXMLFile(fileName); - Generator* gen = gds->getGen(0); - ASSERT_NE(gen, nullptr); - gds->consolePrintLevel = print_level::no_print; - obj = cof->createObject("exciter", excname); - ASSERT_NE(obj, nullptr) << "Failed to create exciter " << excname; - auto fnd = parameters.find(excname); - - if (fnd != parameters.end()) { - for (auto& pp : fnd->second) { - obj->set(pp.first, pp.second); - } - } - - gen->add(obj); - - int retval = gds->dynInitialize(); - ASSERT_EQ(gds->currentProcessState(), gridDynSimulation::gridState_t::DYNAMIC_INITIALIZED); - - EXPECT_EQ(retval, 0) << "Exciter " << excname << " dynInitialize issue"; - - int badresid = runResidualCheck(gds, cDaeSolverMode, false); - - ASSERT_EQ(badresid, 0) << "Exciter " << excname << " residual issue"; - int badjacobian = runJacobianCheck(gds, cDaeSolverMode, false); - ASSERT_EQ(badjacobian, 0) << "Exciter " << excname << " Jacobian issue"; - - std::vector volt1; - gds->getVoltage(volt1); - - gds->run(); - if (gds->getSimulationTime() < 30.0) { - printf("exciter didn't complete %s\n", excname.c_str()); - gds->saveRecorders(); - } - ASSERT_GE(gds->getSimulationTime(), 30.0) << "Exciter " << excname << " didn't complete"; - std::vector volt2; - gds->getVoltage(volt2); - EXPECT_TRUE((volt2[0] > 0.98) && (volt2[0] < 1.02)); - EXPECT_TRUE((volt2[1] > 0.97) && (volt2[1] < 1.02)); - // check for stability - } + const std::string fileName = std::string(EXCITER_TEST_DIRECTORY "test_exciter_stability4.xml"); + verifyStabilityCase(*this, fileName, parameters, 0.98, 1.02, 0.97, 1.02, {"dc1a", "sexs"}); } #ifdef LOAD_CVODE