Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions GridKit/Apps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(PhasorDynamics)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GridKit code should build without SUNDIALS. Consider adding this guard to prevent failing CI tests:

Suggested change
add_subdirectory(PhasorDynamics)
if(TARGET SUNDIALS::idas)
add_subdirectory(PhasorDynamics)
endif()

12 changes: 12 additions & 0 deletions GridKit/Apps/PhasorDynamics/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
add_executable(PDSim PDSim.cpp)
target_link_libraries(PDSim
PUBLIC
GridKit::phasor_dynamics_components
GridKit::solvers_dyn
GridKit::Utilities
GridKit::testing)
target_include_directories(PDSim PRIVATE
${GRIDKIT_THIRD_PARTY_DIR}/nlohmann-json/include
${GRIDKIT_THIRD_PARTY_DIR}/magic-enum/include)

install(TARGETS PDSim EXPORT gridkit-targets RUNTIME)
104 changes: 104 additions & 0 deletions GridKit/Apps/PhasorDynamics/PDSim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "PDSim.hpp"

#include <filesystem>
#include <fstream>

#include <GridKit/Model/PhasorDynamics/SystemModel.hpp>
#include <GridKit/Solver/Dynamic/Ida.hpp>
#include <GridKit/Testing/TestHelpers.hpp>
#include <GridKit/Testing/Testing.hpp>

using Log = GridKit::Utilities::Logger;

using namespace GridKit::PhasorDynamics;
using namespace GridKit::Testing;
using namespace AnalysisManager::Sundials;

using scalar_type = double;
using real_type = double;
using index_type = size_t;

int main(int argc, const char* argv[])
{
// Study file
if (argc < 2)
{
Log::error() << "No input file provided" << std::endl;
std::cout << "\n"
"Usage:\n"
" pdsim <json-input-file>\n"
"\n"
"Please provide a json input file for the study to run.\n"
"\n";
exit(1);
}

auto study = parseStudyData(argv[1]);

// Instantiate system
SystemModel<scalar_type, index_type> sys(study.model_data);
sys.allocate();

// Get access to fault 0
auto* fault = sys.getBusFault(0);

real_type dt = study.dt;

// Set up simulation
Ida<scalar_type, index_type> ida(&sys);
ida.configureSimulation();

// Run simulation, output each `dt` interval
real_type start = static_cast<real_type>(clock());

using EventType = SystemEvent::Type;
ida.initializeSimulation(0.0, false);

real_type curr_time = 0.0;
for (const auto& event : study.events)
{
// Run to event time
int nout = static_cast<int>(std::round((event.time - curr_time) / dt));
ida.runSimulation(event.time, nout);

// Set up run for event (to start at event time)
if (event.type == EventType::FAULT_ON)
{
fault->setStatus(true);
}
else if (event.type == EventType::FAULT_OFF)
{
fault->setStatus(false);
}
ida.initializeSimulation(event.time, false);
curr_time = event.time;
}

// Run to final time
int nout = static_cast<int>(std::round((study.tmax - curr_time) / dt));
ida.runSimulation(study.tmax, nout);

real_type stop = static_cast<real_type>(clock());

// Stop the variable monitor
sys.stopMonitor();

// Generate aggregate errors comparing variable output to reference solution
std::string func{"monitor file vs reference file"};
TestStatus status{func.c_str()};
if (!study.output_file.empty() && !study.reference_file.empty())
{
auto errorSet = compareCSV(study.output_file, study.reference_file);

// Print the errors
errorSet.display();

status *= errorSet.total.max_error < study.error_tol;

status.report();
}

std::cout << "\n\nComplete in " << (stop - start) / CLOCKS_PER_SEC << " seconds\n";

return status.get();
}
151 changes: 151 additions & 0 deletions GridKit/Apps/PhasorDynamics/PDSim.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#pragma once

#include <filesystem>
#include <fstream>
#include <vector>

#include <magic_enum/magic_enum.hpp>
#include <nlohmann/json.hpp>

#include <GridKit/Model/PhasorDynamics/SystemModelData.hpp>
#include <GridKit/Utilities/Logger/Logger.hpp>

namespace GridKit
{
namespace PhasorDynamics
{
namespace fs = ::std::filesystem;

struct SystemEvent
{
enum class Type
{
FAULT_ON,
FAULT_OFF
};

double time;
Type type;
std::size_t element_id;
};

struct StudyData
{
fs::path system_model_file;
double dt;
double tmax;
std::vector<SystemEvent> events;
fs::path output_file;
fs::path reference_file;
double error_tol;
SystemModelData<> model_data;
};

using json = ::nlohmann::json;
using Log = ::GridKit::Utilities::Logger;

void from_json(const json& j, StudyData& c)
{
using namespace magic_enum;

j.at("system_model_file").get_to(c.system_model_file);
j.at("dt").get_to(c.dt);
j.at("tmax").get_to(c.tmax);

for (auto& raw_event : j.at("events"))
{
auto& event = c.events.emplace_back();
raw_event.at("time").get_to(event.time);
raw_event.at("element_id").get_to(event.element_id);

auto type_str = raw_event.at("type").get<std::string>();
using EventType = SystemEvent::Type;
auto type_wrap = enum_cast<EventType>(type_str, case_insensitive);
if (!type_wrap.has_value())
{
Log::error() << "Unable to parse event type \"" << type_str << "\"\n";
}
event.type = type_wrap.value();
}

if (j.contains("output_file"))
{
j.at("output_file").get_to(c.output_file);
if (!c.output_file.is_absolute())
{
// c.output_file =
}
}

if (j.contains("reference_file"))
{
j.at("reference_file").get_to(c.reference_file);
}

c.error_tol = j.value("error_tolerance", 1.0e-4);
}

std::ifstream openFile(const fs::path& file_path)
{
if (!exists(file_path))
{
Log::error() << "File not found: " << file_path << std::endl;
}
auto fs = std::ifstream(file_path);
if (!fs)
{
Log::error() << "Failed to open file: " << file_path << std::endl;
}
return fs;
}

StudyData parseStudyData(const fs::path& file_path)
{
auto data = StudyData(json::parse(openFile(file_path)));

auto loc = file_path.parent_path();
if (!data.system_model_file.is_absolute())
{
data.system_model_file = loc / data.system_model_file;
}
if (!data.reference_file.empty())
{
if (!data.reference_file.is_absolute())
{
data.reference_file = loc / data.reference_file;
}
}

auto csv = ::GridKit::Model::VariableMonitorFormat::CSV;
data.model_data = parseSystemModelData(data.system_model_file);
std::string model_output_file;
for (const auto& sink : data.model_data.monitor_sink)
{
if (sink.format == csv && sink.delim == ",")
{
model_output_file = sink.file_name;
}
}
if (model_output_file.empty())
{
data.model_data.monitor_sink.emplace_back(data.output_file, csv);
}
else
{
if (exists(data.output_file))
{
if ((!is_symlink(data.output_file)) || (read_symlink(data.output_file) != model_output_file))
{
Log::error() << "Study output file not usable" << std::endl;
}
}
else
{
fs::create_symlink(model_output_file, data.output_file);
}
}

return data;
}
} // namespace PhasorDynamics
} // namespace GridKit
3 changes: 3 additions & 0 deletions GridKit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ add_subdirectory(Solver)
# Testing library
add_subdirectory(Testing)

# Create applications
add_subdirectory(Apps)

install(
FILES
Constants.hpp
Expand Down
2 changes: 1 addition & 1 deletion GridKit/Model/VariableMonitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ namespace GridKit
/// Output format
Format format;
/// Delimiter (used only with CSV format currently)
std::string delim;
std::string delim{","};
};

virtual ~VariableMonitorBase()
Expand Down
10 changes: 10 additions & 0 deletions GridKit/Testing/Testing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ namespace GridKit
return *this;
}

operator bool() const
{
return outcome_ == TestOutcome::PASS;
}

int get() const
{
return outcome_;
}

void skipTest()
{
outcome_ = TestOutcome::SKIP;
Expand Down
4 changes: 4 additions & 0 deletions examples/PhasorDynamics/Tiny/ThreeBus/Basic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ add_test(NAME ThreeBusBasicJson
add_test(NAME ThreeBusBasicJson_no_arg
COMMAND ThreeBusBasicJson
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

add_test(NAME ThreeBusBasic_using_app
COMMAND PDSim ${CMAKE_CURRENT_SOURCE_DIR}/ThreeBusBasic.study.json
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps call it solver or config?

Suggested change
COMMAND PDSim ${CMAKE_CURRENT_SOURCE_DIR}/ThreeBusBasic.study.json
COMMAND PDSim ${CMAKE_CURRENT_SOURCE_DIR}/ThreeBusBasic.solver.json

Similarly, we could rename ThreeBusBasic.json --> ThreeBusBasic.case.json to be more self-explanatory to the domain folk.

WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"system_model_file": "ThreeBusBasic.json",
"dt": 0.00416666666666,
"tmax": 10,
"events": [
{"time": 1, "type": "fault_on", "element_id": 0},
{"time": 1.1, "type": "fault_off", "element_id": 0}
],
"output_file": "ThreeBus_six_cycle_fault_bus1.csv",
"reference_file": "ThreeBusBasic.ref.csv",
"error_tolerance": 1e-4
}