From f019cc3fb0ef19c41812780d068d302b7ef879bd Mon Sep 17 00:00:00 2001 From: Bertrand Rix Date: Wed, 9 Oct 2024 09:41:42 +0200 Subject: [PATCH] Dataframe based network interface (#2) Explore network using julia dataframes and get network metadata. Signed-off-by: Bertrand Rix --- .github/workflows/dev-ci.yml | 6 +- Project.toml | 1 + build_local.jl | 30 ++--- cpp/powsybljl-cpp/powsybl_jl.cpp | 117 +++++++++++++++++++ cpp/pypowsybl | 2 +- src/Network.jl | 188 +++++++++++++++++++++++++++++++ src/NetworkCreationUtils.jl | 65 +++++++++++ src/Powsybl.jl | 6 + test/print_network.jl | 18 +++ 9 files changed, 417 insertions(+), 16 deletions(-) create mode 100644 src/Network.jl create mode 100644 src/NetworkCreationUtils.jl create mode 100644 test/print_network.jl diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index ad44ddc..6c97159 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -47,4 +47,8 @@ jobs: - name: Test run: | - julia test/print_version.jl \ No newline at end of file + julia test/print_version.jl + + - name: Test + run: | + julia test/print_network.jl \ No newline at end of file diff --git a/Project.toml b/Project.toml index 0e891bd..0c72430 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.1" [deps] CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" Powsybl_jll = "b8c81e45-bfcc-5af0-87df-fd7619bc5515" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" [compat] julia = "1.6.7" diff --git a/build_local.jl b/build_local.jl index e8f9b71..cf9691d 100644 --- a/build_local.jl +++ b/build_local.jl @@ -8,20 +8,25 @@ sources = [ julia_versions = [VERSION] +platform = HostPlatform() + +@info string("Downloading pypowsybl java binaries for ", platform.tags["os"]) +if platform.tags["os"] == "linux" + Base.download("https://github.com/powsybl/pypowsybl/releases/download/v1.7.0/binaries-v1.7.0-linux.zip", "cpp/powsybl-java.zip") +elseif platform.tags["os"] == "windows" + Base.download("https://github.com/powsybl/pypowsybl/releases/download/v1.7.0/binaries-v1.7.0-windows.zip", "cpp/powsybl-java.zip") +elseif platform.tags["os"] == "macos" + Base.download("https://github.com/powsybl/pypowsybl/releases/download/v1.7.0/binaries-v1.7.0-darwin.zip", "cpp/powsybl-java.zip") +else + throw("Unsupported platform with os " * platform.tags["os"]) +end + + script = raw""" cd $WORKSPACE/srcdir # Get binary for powsybl-java, generated with GraalVm -if [[ "${target}" == *-mingw* ]]; then - wget https://github.com/powsybl/pypowsybl/releases/download/vBinariesDeployment/binaries-vBinariesDeployment-windows.zip -O powsybl-java.zip -fi -if [[ "${target}" == *-linux-* ]]; then - wget https://github.com/powsybl/pypowsybl/releases/download/vBinariesDeployment/binaries-vBinariesDeployment-linux.zip -O powsybl-java.zip -fi -if [[ "${target}" == *-apple-* ]]; then - wget https://github.com/powsybl/pypowsybl/releases/download/vBinariesDeployment/binaries-vBinariesDeployment-darwin.zip -O powsybl-java.zip -fi -unzip powsybl-java.zip -d $prefix +unzip cpp/powsybl-java.zip -d $prefix # Build powsybl-cpp API cd $WORKSPACE/srcdir/cpp/ && mkdir build && cd build @@ -34,9 +39,6 @@ cmake -DCMAKE_BUILD_TYPE=Release ../cpp/powsybljl-cpp -DJulia_PREFIX=$prefix -DJ cmake --build . --target install --config Release """ -platforms = [HostPlatform()] -@show platforms - products = [ LibraryProduct(["math", "libmath"], :libmath) LibraryProduct(["pypowsybl-java", "libpypowsybl-java"], :libpypowsybl_java) @@ -49,5 +51,5 @@ dependencies = [ Dependency("libjulia_jll") ] -build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; +build_tarballs(ARGS, name, version, sources, script, [platform], products, dependencies; preferred_gcc_version=v"12", julia_compat="1.6") diff --git a/cpp/powsybljl-cpp/powsybl_jl.cpp b/cpp/powsybljl-cpp/powsybl_jl.cpp index 2ca2d32..842933e 100644 --- a/cpp/powsybljl-cpp/powsybl_jl.cpp +++ b/cpp/powsybljl-cpp/powsybl_jl.cpp @@ -3,14 +3,131 @@ #include "jlcxx/jlcxx.hpp" #include "powsybl-cpp.h" +// Necessary to compile to map struct with no constructor ? +template <> struct jlcxx::IsMirroredType : std::false_type {}; +template <> struct jlcxx::IsMirroredType : std::false_type {}; + +void logFromJava(int level, long timestamp, char* loggerName, char* message) { + //TODO Redirect log properly to julia logger +} + JLCXX_MODULE define_module_powsybl(jlcxx::Module& mod) { + mod.add_type("JavaHandle"); + + mod.add_bits("ElementType", jlcxx::julia_type("CppEnum")); + mod.set_const("BUS", element_type::BUS); + mod.set_const("BUS_FROM_BUS_BREAKER_VIEW", element_type::BUS_FROM_BUS_BREAKER_VIEW); + mod.set_const("LINE", element_type::LINE); + mod.set_const("TWO_WINDINGS_TRANSFORMER", element_type::TWO_WINDINGS_TRANSFORMER); + mod.set_const("THREE_WINDINGS_TRANSFORMER", element_type::THREE_WINDINGS_TRANSFORMER); + mod.set_const("GENERATOR", element_type::GENERATOR); + mod.set_const("LOAD", element_type::LOAD); + mod.set_const("BATTERY", element_type::BATTERY); + mod.set_const("SHUNT_COMPENSATOR", element_type::SHUNT_COMPENSATOR); + mod.set_const("NON_LINEAR_SHUNT_COMPENSATOR_SECTION", element_type::NON_LINEAR_SHUNT_COMPENSATOR_SECTION); + mod.set_const("LINEAR_SHUNT_COMPENSATOR_SECTION", element_type::LINEAR_SHUNT_COMPENSATOR_SECTION); + mod.set_const("DANGLING_LINE", element_type::DANGLING_LINE); + mod.set_const("TIE_LINE", element_type::TIE_LINE); + mod.set_const("LCC_CONVERTER_STATION", element_type::LCC_CONVERTER_STATION); + mod.set_const("VSC_CONVERTER_STATION", element_type::VSC_CONVERTER_STATION); + mod.set_const("STATIC_VAR_COMPENSATOR", element_type::STATIC_VAR_COMPENSATOR); + mod.set_const("SWITCH", element_type::SWITCH); + mod.set_const("VOLTAGE_LEVEL", element_type::VOLTAGE_LEVEL); + mod.set_const("SUBSTATION", element_type::SUBSTATION); + mod.set_const("BUSBAR_SECTION", element_type::BUSBAR_SECTION); + mod.set_const("HVDC_LINE", element_type::HVDC_LINE); + mod.set_const("RATIO_TAP_CHANGER_STEP", element_type::RATIO_TAP_CHANGER_STEP); + mod.set_const("PHASE_TAP_CHANGER_STEP", element_type::PHASE_TAP_CHANGER_STEP); + mod.set_const("RATIO_TAP_CHANGER", element_type::RATIO_TAP_CHANGER); + mod.set_const("PHASE_TAP_CHANGER", element_type::PHASE_TAP_CHANGER); + mod.set_const("REACTIVE_CAPABILITY_CURVE_POINT", element_type::REACTIVE_CAPABILITY_CURVE_POINT); + mod.set_const("OPERATIONAL_LIMITS", element_type::OPERATIONAL_LIMITS); + mod.set_const("MINMAX_REACTIVE_LIMITS", element_type::MINMAX_REACTIVE_LIMITS); + mod.set_const("ALIAS", element_type::ALIAS); + mod.set_const("IDENTIFIABLE", element_type::IDENTIFIABLE); + mod.set_const("INJECTION", element_type::INJECTION); + mod.set_const("BRANCH", element_type::BRANCH); + mod.set_const("TERMINAL", element_type::TERMINAL); + mod.set_const("SUB_NETWORK", element_type::SUB_NETWORK); + + mod.add_bits("FilterAttributes", jlcxx::julia_type("CppEnum")); + mod.set_const("ALL_ATTRIBUTES", filter_attributes_type::ALL_ATTRIBUTES); + mod.set_const("DEFAULT_ATTRIBUTES", filter_attributes_type::DEFAULT_ATTRIBUTES); + mod.set_const("SELECTION_ATTRIBUTES", filter_attributes_type::SELECTION_ATTRIBUTES); + auto preJavaCall = [](pypowsybl::GraalVmGuard* guard, exception_handler* exc){ }; auto postJavaCall = [](){ }; pypowsybl::init(preJavaCall, postJavaCall); + auto fptr = &::logFromJava; + pypowsybl::setupLoggerCallback(reinterpret_cast(fptr)); mod.method("get_version_table", &pypowsybl::getVersionTable, "Get an ASCII table with all PowSybBl modules version"); mod.method("set_java_library_path", [] (std::string const& path) { pypowsybl::setJavaLibraryPath(path); }, "Set java.library.path JVM property"); + + mod.method("close_powsybl", [] () { + pypowsybl::closePypowsybl(); + }, "Closes powsybl module"); + + mod.method("load", [] (std::string const& s) { + std::map defaultParameters; + std::vector postProcessors; + pypowsybl::JavaHandle network = pypowsybl::loadNetwork(s, defaultParameters, postProcessors, nullptr); + return network; + }, "Load a network from a file"); + + mod.method("create_network", [] (std::string const& name, std::string const& id) { + return pypowsybl::createNetwork(name, id); + }, "create an example network"); + + mod.method("get_network_import_formats", [] () { + return pypowsybl::getNetworkImportFormats(); + }, "Get available import format"); + + mod.add_type("SeriesType") + .method("name", [](series& s) { return std::string(s.name); }) + .method("index", [](series& s) { return (bool) s.index; }) + .method("type", [](series& s) { return s.type; }) + .method("as_double_array", [](series& s) { + return jlcxx::ArrayRef(static_cast(s.data.ptr), s.data.length); + }) + .method("as_int_array", [](series& s) { + return jlcxx::ArrayRef(static_cast(s.data.ptr), s.data.length); + }) + .method("as_string_array", [](series& s) { + return pypowsybl::toVector((array *) & s.data); + }); + + mod.add_type("NetworkMetadata") + .method("id", [](pypowsybl::JavaHandle handle) { + return std::string(pypowsybl::getNetworkMetadata(handle)->id); + }) + .method("name", [](pypowsybl::JavaHandle handle) { + return std::string(pypowsybl::getNetworkMetadata(handle)->name); + }) + .method("source_format", [](pypowsybl::JavaHandle handle) { + return std::string(pypowsybl::getNetworkMetadata(handle)->source_format); + }) + .method("forecast_distance", [](pypowsybl::JavaHandle handle) { + return pypowsybl::getNetworkMetadata(handle)->forecast_distance; + }) + .method("case_date", [](pypowsybl::JavaHandle handle) { + return pypowsybl::getNetworkMetadata(handle)->case_date; + }); + + mod.add_type("SeriesArray") + .method("as_array", [](pypowsybl::SeriesArray& seriesArray) { + jlcxx::Array data{ }; + for(int i=0; i < seriesArray.length(); ++i) { + data.push_back(seriesArray.begin()[i]); + } + return data; + }); + + mod.method("create_network_elements_series_array", [] (pypowsybl::JavaHandle handle, element_type type, std::vector const& attributes, filter_attributes_type filter_attributes, bool nominal_apparent_power, double per_unit) { + return pypowsybl::createNetworkElementsSeriesArray(handle, type, filter_attributes, attributes, nullptr, nominal_apparent_power, per_unit); + }, "Get a series"); + } diff --git a/cpp/pypowsybl b/cpp/pypowsybl index eafe449..cd5fea4 160000 --- a/cpp/pypowsybl +++ b/cpp/pypowsybl @@ -1 +1 @@ -Subproject commit eafe449f246343b5d9aedb9eb2e2757cd9dd76e1 +Subproject commit cd5fea41bbfb2897fd71a6e63b2d07a465055699 diff --git a/src/Network.jl b/src/Network.jl new file mode 100644 index 0000000..1d1df22 --- /dev/null +++ b/src/Network.jl @@ -0,0 +1,188 @@ +module Network + using ..Powsybl + using CxxWrap + using DataFrames + + const nominal_apparent_power = 100.0 + const per_unit = false + + mutable struct NetworkHandle + handle::Powsybl.JavaHandle + id::String + name::String + source_format::String + forecast_distance::Int32 + case_date::Float64 + end + + function get_network_metadata(network::NetworkHandle) + return Powsybl.get_network_metadata(network.handle) + end + + function get_elements(network::NetworkHandle, type::Powsybl.ElementType, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + filter_attributes = Powsybl.DEFAULT_ATTRIBUTES + if all_attributes + filter_attributes = Powsybl.ALL_ATTRIBUTES + elseif !isempty(attributes) + filter_attributes = Powsybl.SELECTION_ATTRIBUTES + end + + if all_attributes && !isempty(attributes) + throw("parameters \"all_attributes\" and \"attributes\" are mutually exclusive") + end + series_array = Powsybl.create_network_elements_series_array(network.handle, type, StdVector{StdString}(attributes), filter_attributes, per_unit, nominal_apparent_power) + return create_dataframe_from_series_array(series_array[]) + end + + function get_buses(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.BUS, all_attributes, attributes) + end + + function get_bus_breaker_view_buses(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.BUS_FROM_BUS_BREAKER_VIEW, all_attributes, attributes) + end + + function get_generators(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.GENERATOR, all_attributes, attributes) + end + + function get_batteries(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.BATTERY, all_attributes, attributes) + end + + function get_lines(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.LINE, all_attributes, attributes) + end + + function get_2_windings_transformers(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.TWO_WINDINGS_TRANSFORMER, all_attributes, attributes) + end + + function get_3_windings_transformers(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.THREE_WINDINGS_TRANSFORMER, all_attributes, attributes) + end + + function get_shunt_compensators(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.SHUNT_COMPENSATOR, all_attributes, attributes) + end + + function get_non_linear_shunt_compensator_sections(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.NON_LINEAR_SHUNT_COMPENSATOR_SECTION, all_attributes, attributes) + end + + function get_linear_shunt_compensator_sections(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.LINEAR_SHUNT_COMPENSATOR_SECTION, all_attributes, attributes) + end + + function get_dangling_lines(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.DANGLING_LINE, all_attributes, attributes) + end + + function get_tie_lines(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.TIE_LINE, all_attributes, attributes) + end + + function get_lcc_converter_stations(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.LCC_CONVERTER_STATION, all_attributes, attributes) + end + + function get_vsc_converter_stations(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.VSC_CONVERTER_STATION, all_attributes, attributes) + end + + function get_static_var_compensators(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.STATIC_VAR_COMPENSATOR, all_attributes, attributes) + end + + function get_voltage_levels(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.VOLTAGE_LEVEL, all_attributes, attributes) + end + + function get_busbar_sections(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.BUSBAR_SECTION, all_attributes, attributes) + end + + function get_substations(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.SUBSTATION, all_attributes, attributes) + end + + function get_hvdc_lines(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.HVDC_LINE, all_attributes, attributes) + end + + function get_switches(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.SWITCH, all_attributes, attributes) + end + + function get_ratio_tap_changer_steps(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.RATIO_TAP_CHANGER_STEP, all_attributes, attributes) + end + + function get_phase_tap_changer_steps(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.PHASE_TAP_CHANGER_STEP, all_attributes, attributes) + end + + function get_ratio_tap_changers(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.RATIO_TAP_CHANGER, all_attributes, attributes) + end + + function get_phase_tap_changers(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.PHASE_TAP_CHANGER, all_attributes, attributes) + end + + function get_reactive_capability_curve_points(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.REACTIVE_CAPABILITY_CURVE_POINT, all_attributes, attributes) + end + + function get_aliases(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.ALIAS, all_attributes, attributes) + end + + function get_identifiables(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.IDENTIFIABLE, all_attributes, attributes) + end + + function get_injections(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.INJECTION, all_attributes, attributes) + end + + function get_branches(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.BRANCH, all_attributes, attributes) + end + + function get_terminals(network::NetworkHandle, all_attributes::Bool = false, attributes::Vector{String} = Vector{String}()) + return get_elements(network, Powsybl.TERMINAL, all_attributes, attributes) + end + + function create_dataframe_from_series_array(array::Powsybl.SeriesArray) + myArray = Powsybl.as_array(array) + df = DataFrame() + for serie in myArray + type = Powsybl.type(serie) + name = Powsybl.name(serie) + if type == 0 + data = Powsybl.as_string_array(serie) + elseif type == 1 + data = Powsybl.as_double_array(serie) + elseif type == 2 + data = Powsybl.as_int_array(serie) + else + continue + end + df[!, name]=data + end + return df + end + + function load(network_file::String)::NetworkHandle + handle = Powsybl.load(network_file) + return NetworkHandle(Powsybl.load(network_file), + Powsybl.id(handle), + Powsybl.name(handle), + Powsybl.source_format(handle), + Powsybl.forecast_distance(handle), + Powsybl.case_date(handle)) + end + + include("NetworkCreationUtils.jl") +end \ No newline at end of file diff --git a/src/NetworkCreationUtils.jl b/src/NetworkCreationUtils.jl new file mode 100644 index 0000000..09b6c67 --- /dev/null +++ b/src/NetworkCreationUtils.jl @@ -0,0 +1,65 @@ +function _create_network(name::String, network_id::String = "") + handle = Powsybl.create_network(name, network_id) + return Network.NetworkHandle(handle, + Powsybl.id(handle), + Powsybl.name(handle), + Powsybl.source_format(handle), + Powsybl.forecast_distance(handle), + Powsybl.case_date(handle)) +end + +function create_empty(network_id::String = "") + return _create_network("empty", network_id) +end + +function create_ieee9(network_id::String = "") + return _create_network("ieee9", network_id) +end + +function create_ieee14(network_id::String = "") + return _create_network("ieee14", network_id) +end + +function create_ieee30(network_id::String = "") + return _create_network("ieee30", network_id) +end + +function create_ieee57(network_id::String = "") + return _create_network("ieee57", network_id) +end + +function create_ieee118(network_id::String = "") + return _create_network("ieee118", network_id) +end + +function create_ieee300(network_id::String = "") + return _create_network("ieee300", network_id) +end + +function create_eurostag_tutorial_example1(network_id::String = "") + return _create_network("eurostag_tutorial_example1", network_id) +end + +function create_eurostag_tutorial_example1_with_power_limits(network_id::String = "") + return _create_network("eurostag_tutorial_example1_with_power_limits", network_id) +end + +function create_four_substations_node_breaker(network_id::String = "") + return _create_network("four_substations_node_breaker", network_id) +end + +function create_four_substations_node_breaker_with_extensions(network_id::String = "") + return _create_network("four_substations_node_breaker_with_extensions", network_id) +end + +function create_micro_grid_be(network_id::String = "") + return _create_network("micro_grid_be", network_id) +end + +function create_micro_grid_nl(network_id::String = "") + return _create_network("micro_grid_nl", network_id) +end + +function create_metrix_tutorial_six_buses(network_id::String = "") + return _create_network("metrix_tutorial_six_buses", network_id) +end \ No newline at end of file diff --git a/src/Powsybl.jl b/src/Powsybl.jl index 333d78b..937669a 100644 --- a/src/Powsybl.jl +++ b/src/Powsybl.jl @@ -5,5 +5,11 @@ module Powsybl function __init__() @initcxx set_java_library_path(dirname(Powsybl_jll.libmath_path)) + atexit(close) end + + function close() + close_powsybl() + end + include("Network.jl") end \ No newline at end of file diff --git a/test/print_network.jl b/test/print_network.jl new file mode 100644 index 0000000..e32c56d --- /dev/null +++ b/test/print_network.jl @@ -0,0 +1,18 @@ +using Powsybl +using Test + +network = Powsybl.Network.create_ieee9() + +@test network.id == "ieee9cdf" +@test network.name == "ieee9cdf" +@test network.source_format == "IEEE-CDF" +@test network.forecast_distance == 0 +@test network.case_date ≈ 1.240704e9 + +lines = Powsybl.Network.get_lines(network) +@test names(lines) == ["id", "name", "r", "x", "g1", "b1", "g2", + "b2", "p1", "q1", "i1", "p2", "q2", "i2", "voltage_level1_id", + "voltage_level2_id", "bus1_id", "bus2_id"] + +@test lines[:, "id"] == ["L7-8-0", "L9-8-0", "L7-5-0", "L9-6-0", "L5-4-0", "L6-4-0"] +@test lines[:, "bus1_id"] == ["VL2_1", "VL3_1", "VL2_1", "VL3_1", "VL5_0", "VL6_0"]