diff --git a/Project.toml b/Project.toml index 7e2b825f..2e70000e 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "0.4.0" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/src/io.jl b/src/io.jl index a5ed963b..00e3a3f9 100644 --- a/src/io.jl +++ b/src/io.jl @@ -1,5 +1,7 @@ export create_parameters_and_sets_from_file, - create_graph, save_solution_to_file, compute_rp_partitions + create_graph, save_solution_to_file, compute_rp_partitions, read_esdl, read_esdl_assets + +using EzXML """ parameters, sets = create_parameters_and_sets_from_file(input_folder) @@ -372,3 +374,70 @@ function compute_rp_partitions(df, elements, time_steps_per_rp) return rp_partitions end + +""" + read_esdl(file_path; instance_name) + +Read an ESDL file to construct the flow graph and link the related +assets and flows from the specification + +An ESDL file can contain multiple instances. If multiple instances +are present in the file, a specific instance has to be selected. +""" +function read_esdl(file_path; instance_name = nothing) + esdl_assets = read_esdl_assets(file_path; instance_name = instance_name) + + # Gather all in-ports + id_to_index = Dict{String,Int}() + for (to_id, asset) in enumerate(esdl_assets) + for port in eachelement(asset) + if port.name != "port" || port["xsi:type"] != "esdl:InPort" + continue + end + id_to_index[port["id"]] = to_id + end + end + + # Create graph based on out-ports and previously gathered in-ports + graph = Graphs.DiGraph(length(esdl_assets)) + for (from_id, asset) in enumerate(esdl_assets) + for port in eachelement(asset) + if port.name != "port" || port["xsi:type"] != "esdl:OutPort" + continue + end + flows = eachsplit(port["connectedTo"], " ") + for flow in flows + if !haskey(id_to_index, flow) + # throw(ErrorException("No InPort with id '$flow' was found")) + println("No InPort with id '$flow' was found, ignoring...") + continue + end + to_id = id_to_index[flow] + Graphs.add_edge!(graph, from_id, to_id) + end + end + end + + return graph +end + +function read_esdl_assets(file_path; instance_name = nothing) + doc = readxml(file_path) + doc_root = root(doc) + if countelements(doc_root) == 0 || firstelement(doc_root).name != "instance" + throw(ErrorException("no instance was found in given ESDL file")) + elseif isnothing(instance_name) + if countelements(doc_root) > 1 + throw( + ErrorException( + "multiple instances found in file, but no instance_name was given", + ), + ) + else + instance_name = firstelement(doc_root)["name"] + end + end + + # Use XPath expression to find all assets under the instance with name `instance_name` + return findall("//instance[@name='$instance_name']//asset", doc) +end diff --git a/test/esdl/no_instance.esdl b/test/esdl/no_instance.esdl new file mode 100644 index 00000000..fa32cec3 --- /dev/null +++ b/test/esdl/no_instance.esdl @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/esdl/one_instance.esdl b/test/esdl/one_instance.esdl new file mode 100644 index 00000000..11175efe --- /dev/null +++ b/test/esdl/one_instance.esdl @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/esdl/two_instances.esdl b/test/esdl/two_instances.esdl new file mode 100644 index 00000000..765d0ae5 --- /dev/null +++ b/test/esdl/two_instances.esdl @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/runtests.jl b/test/runtests.jl index aafb892c..87cdb1e1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,7 @@ using Test # Folders names const INPUT_FOLDER = joinpath(@__DIR__, "inputs") const OUTPUT_FOLDER = joinpath(@__DIR__, "outputs") +const ESDL_FOLDER = joinpath(@__DIR__, "esdl") # Add run all test files in test folder include("test-io.jl") diff --git a/test/test-io.jl b/test/test-io.jl index 12a73d3b..293a54d7 100644 --- a/test/test-io.jl +++ b/test/test-io.jl @@ -57,3 +57,39 @@ end @test_throws AssertionError TEM._parse_rp_partition(Val(:math), "3x4", 1:14) end end + +@testset "ESDL parsing" begin + @testset "instance detection error handling" begin + @testset "no instance" begin + @test_throws ErrorException read_esdl_assets( + joinpath(ESDL_FOLDER, "no_instance.esdl"), + ) + end + + @testset "two instances" begin + @test_throws ErrorException read_esdl_assets( + joinpath(ESDL_FOLDER, "two_instances.esdl"), + ) + end + end + + @testset "instance asset parsing" begin + @testset "unnamed single instance" begin + assets = read_esdl_assets(joinpath(ESDL_FOLDER, "one_instance.esdl")) + @test length(assets) == 8 + end + + @testset "named instance out of multiple" begin + flat_assets = read_esdl_assets( + joinpath(ESDL_FOLDER, "two_instances.esdl"); + instance_name = "Flat", + ) + @test length(flat_assets) == 4 + nested_assets = read_esdl_assets( + joinpath(ESDL_FOLDER, "two_instances.esdl"); + instance_name = "Nested", + ) + @test length(nested_assets) == 10 + end + end +end