diff --git a/Project.toml b/Project.toml index d99ffc6..640a91b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,11 +1,13 @@ name = "PowerSystemsInvestmentsPortfolios" uuid = "bed98974-b02a-5e2f-9fe0-a103f8c450dd" -authors = ["Jose Daniel Lara"] +authors = ["Jose Daniel Lara", "Sourabh Dalvi"] version = "0.1.0" [deps] +DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" diff --git a/scripts/portfolio_test.jl b/scripts/portfolio_test.jl index d6696bf..51e9c3f 100644 --- a/scripts/portfolio_test.jl +++ b/scripts/portfolio_test.jl @@ -21,18 +21,16 @@ p = Portfolio(0.07) t = SupplyTechnology{ThermalStandard}( "thermal_tech", true, - ThermalStandard, PSY.ThermalFuels.COAL, PSY.PrimeMovers.ST, 0.98, # cap factor nothing, nothing, - IS.SupplementalAttributesContainer(), - IS.TimeSeriesContainer(), - IS.InfrastructureSystemsInternal(), ) PSIP.add_technology!(p, t) +IS.serialize(t) +IS.serialize(p) get_technologies(x -> (!get_available(x)), SupplyTechnology{ThermalStandard}, p) @@ -40,3 +38,4 @@ get_technologies(SupplyTechnology{ThermalStandard}, p) PSIP.remove_technology!(SupplyTechnology{ThermalStandard}, p, "thermal_tech") get_available(t) +IS.deserialize(p) diff --git a/src/PowerSystemsInvestmentsPortfolios.jl b/src/PowerSystemsInvestmentsPortfolios.jl index fea4fd0..069c3ea 100644 --- a/src/PowerSystemsInvestmentsPortfolios.jl +++ b/src/PowerSystemsInvestmentsPortfolios.jl @@ -1,27 +1,105 @@ module PowerSystemsInvestmentsPortfolios -import InfrastructureSystems +import PowerSystems # TODO: Some of these re-exports may cause name collisions with PowerSystems -import InfrastructureSystems: - to_json, - from_json, - serialize, - deserialize, +import PowerSystems: + add_time_series!, get_time_series, has_time_series, get_time_series_array, get_time_series_timestamps, get_time_series_values, get_time_series_names, + ThermalGen, + HydroGen, + RenewableGen, + Storage, + ThermalStandard, + ThermalMultiStart, + ThermalFuels, + PrimeMovers, + RenewableFix, + RenewableDispatch, + GenericBattery, + BatteryEMS, + HydroEnergyReservoir, + HydroDispatch + +import InfrastructureSystems +import InfrastructureSystems: + Components, + TimeSeriesData, + StaticTimeSeries, + Forecast, + AbstractDeterministic, + Deterministic, + Probabilistic, + SingleTimeSeries, + DeterministicSingleTimeSeries, + Scenarios, + ForecastCache, + StaticTimeSeriesCache, + InfrastructureSystemsComponent, + InfrastructureSystemsType, InfrastructureSystemsInternal, + DeviceParameter, + FlattenIteratorWrapper, + LazyDictFromIterator, + DataFormatError, + InvalidRange, + InvalidValue, + copy_time_series!, + clear_ext!, + get_type_from_serialization_data, + get_count, + get_data, + get_horizon, + get_resolution, + get_window, + get_name, + set_name!, + get_internal, + set_internal!, + get_time_series_container, + iterate_windows, + get_time_series, + has_time_series, + get_time_series_array, + get_time_series_timestamps, + get_time_series_values, + get_time_series_names, + get_scenario_count, # Scenario Forecast Exports + get_percentiles, # Probabilistic Forecast Exports + get_next_time_series_array!, + get_next_time, + get_units_info, + set_units_info!, + to_json, + from_json, + serialize, + deserialize, + get_time_series_multiple, + compare_values, CompressionSettings, CompressionTypes, + NormalizationFactor, + NormalizationTypes, + UnitSystem, + SystemUnitsSettings, + open_file_logger, + make_logging_config_file, + validate_struct, MultiLogger, LogEventTracker, - StructField + StructField, + StructDefinition + +const IS = InfrastructureSystems import PowerSystems import PrettyTables +import JSON3 +import DataStructures: OrderedDict export Portfolio export Technology @@ -52,16 +130,19 @@ const PSY = PowerSystems const IS = InfrastructureSystems include("technologies.jl") -include("demand_requirement.jl") -include("supply.jl") -include("demand_side.jl") -include("transport.jl") -include("storage.jl") +include("models/includes.jl") +include("models/serialization.jl") +# include("demand_requirement.jl") +# include("supply.jl") +# include("demand_side.jl") +# include("transport.jl") +# include("storage.jl") include("portfolio.jl") include("utils/print.jl") using DocStringExtensions +const DATA_FORMAT_VERSION = "0.1.0" @template (FUNCTIONS, METHODS) = """ $(TYPEDSIGNATURES) $(DOCSTRING) diff --git a/src/demand_requirement.jl b/src/demand_requirement.jl deleted file mode 100644 index a111187..0000000 --- a/src/demand_requirement.jl +++ /dev/null @@ -1,9 +0,0 @@ -struct DemandRequirement{T <: PSY.StaticInjection} - name::String - available::Bool - power_systems_type::Type{T} - region::Union{PSY.ACBus, PSY.AggregationTopology} - supplemental_attributes_container::IS.SupplementalAttributesContainer - time_series_container::IS.TimeSeriesContainer - internal::InfrastructureSystemsInternal -end diff --git a/src/demand_side.jl b/src/demand_side.jl deleted file mode 100644 index 75fe348..0000000 --- a/src/demand_side.jl +++ /dev/null @@ -1,9 +0,0 @@ -struct DemandSideTechnology{T <: PSY.StaticInjection} <: Technology - name::String - available::Bool - power_systems_type::Type{T} - capital_cost::IS.FunctionData - supplemental_attributes_container::IS.SupplementalAttributesContainer - time_series_container::IS.TimeSeriesContainer - internal::InfrastructureSystemsInternal -end diff --git a/src/descriptors/portfolio_structs.json b/src/descriptors/portfolio_structs.json new file mode 100644 index 0000000..ab0adee --- /dev/null +++ b/src/descriptors/portfolio_structs.json @@ -0,0 +1,297 @@ +{ + "auto_generated_structs": [ + { + "struct_name": "DemandRequirement", + "parametric": "PSY.StaticInjection", + "docstring": "This struct represents the demand requirement for a power system.", + "fields": [ + { + "name": "name", + "comment": "The name of the load demand requirement.", + "data_type": "String" + }, + { + "name": "available", + "comment": "Indicates whether the load demand is available or not in the simulation.", + "null_value": "true", + "data_type": "Bool" + }, + { + "name": "region", + "comment": "The region of the demand requirement.", + "data_type": "Union{PSY.ACBus, PSY.AggregationTopology}" + }, + { + "name": "ext", + "data_type": "Dict{String, Any}", + "null_value": "Dict{String, Any}()", + "default": "Dict{String, Any}()" + }, + { + "name": "supplemental_attributes_container", + "comment": "Container for supplemental attributes.", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, + { + "name": "time_series_container", + "comment": "internal time_series storage", + "null_value": "InfrastructureSystems.TimeSeriesContainer()", + "data_type": "InfrastructureSystems.TimeSeriesContainer", + "default": "InfrastructureSystems.TimeSeriesContainer()" + }, + { + "name": "internal", + "comment": "power system internal reference, do not modify", + "data_type": "InfrastructureSystemsInternal", + "internal_default": "InfrastructureSystemsInternal()", + "exclude_setter": true + } + ], + "supertype": "PSY.StaticInjection" + }, + { + "struct_name": "DemandSideTechnology", + "parametric": "PSY.StaticInjection", + "docstring": "This struct represents a demand side technology in a power system.", + "fields": [ + { + "name": "name", + "comment": "The name of the demand side technology.", + "data_type": "String" + }, + { + "name": "available", + "comment": "Indicates whether the technology is available or not in the simulation.", + "null_value": "true", + "data_type": "Bool" + }, + { + "name": "capital_cost", + "comment": "The capital cost of the technology.", + "data_type": "IS.FunctionData" + }, + { + "name": "ext", + "data_type": "Dict{String, Any}", + "null_value": "Dict{String, Any}()", + "default": "Dict{String, Any}()" + }, + { + "name": "supplemental_attributes_container", + "comment": "Container for supplemental attributes.", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, + { + "name": "time_series_container", + "comment": "internal time_series storage", + "null_value": "InfrastructureSystems.TimeSeriesContainer()", + "data_type": "InfrastructureSystems.TimeSeriesContainer", + "default": "InfrastructureSystems.TimeSeriesContainer()" + }, + { + "name": "internal", + "comment": "power system internal reference, do not modify", + "data_type": "InfrastructureSystemsInternal", + "internal_default": "InfrastructureSystemsInternal()", + "exclude_setter": true + } + ], + "supertype": "Technology" + }, + { + "struct_name": "StorageTechnology", + "parametric": "PSY.Storage", + "docstring": "This struct represents a storage technology in a power system.", + "fields": [ + { + "name": "name", + "comment": "The name of the storage technology.", + "data_type": "String" + }, + { + "name": "available", + "comment": "Indicates whether the technology is available or not in the simulation.", + "null_value": "true", + "data_type": "Bool" + }, + { + "name": "capital_cost", + "comment": "The capital cost of the technology.", + "data_type": "IS.FunctionData" + }, + { + "name": "battery_chemistry", + "comment": "The type of battery chemistry. Implement Chemistry Type Enums in PowerSystems.", + "data_type": "String" + }, + { + "name": "prime_mover", + "comment": "The prime mover of the storage technology.", + "data_type": "PSY.PrimeMovers" + }, + { + "name": "operational_cost", + "comment": "The operational cost of the storage technology.", + "data_type": "PSY.OperationalCost" + }, + { + "name": "ext", + "data_type": "Dict{String, Any}", + "null_value": "Dict{String, Any}()", + "default": "Dict{String, Any}()" + }, + { + "name": "supplemental_attributes_container", + "comment": "Container for supplemental attributes.", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, + { + "name": "time_series_container", + "comment": "internal time_series storage", + "null_value": "InfrastructureSystems.TimeSeriesContainer()", + "data_type": "InfrastructureSystems.TimeSeriesContainer", + "default": "InfrastructureSystems.TimeSeriesContainer()" + }, + { + "name": "internal", + "comment": "power system internal reference, do not modify", + "data_type": "InfrastructureSystemsInternal", + "internal_default": "InfrastructureSystemsInternal()", + "exclude_setter": true + } + ], + "supertype": "Technology" + }, + { + "struct_name": "SupplyTechnology", + "parametric": "PSY.Generator", + "docstring": "This struct represents a supply technology in a power system.", + "fields": [ + { + "name": "name", + "comment": "The name of the supply technology.", + "data_type": "String" + }, + { + "name": "available", + "comment": "Indicates whether the technology is available or not in the simulation.", + "null_value": "true", + "data_type": "Bool" + }, + { + "name": "fuel", + "comment": "The type of fuel used by the supply technology.", + "data_type": "PSY.ThermalFuels" + }, + { + "name": "prime_mover", + "comment": "The prime mover of the supply technology.", + "data_type": "PSY.PrimeMovers" + }, + { + "name": "capacity_factor", + "comment": "The capacity factor of the supply technology.", + "data_type": "Float64" + }, + { + "name": "capital_cost", + "comment": "The capital cost of the technology.", + "null_value": "null", + "data_type": "Union{Nothing, IS.FunctionData}" + }, + { + "name": "operational_cost", + "comment": "The operational cost of the supply technology.", + "null_value": "null", + "data_type": "Union{Nothing, PSY.OperationalCost}" + }, + { + "name": "ext", + "data_type": "Dict{String, Any}", + "null_value": "Dict{String, Any}()", + "default": "Dict{String, Any}()" + }, + { + "name": "supplemental_attributes_container", + "comment": "Container for supplemental attributes.", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, + { + "name": "time_series_container", + "comment": "internal time_series storage", + "null_value": "InfrastructureSystems.TimeSeriesContainer()", + "data_type": "InfrastructureSystems.TimeSeriesContainer", + "default": "InfrastructureSystems.TimeSeriesContainer()" + }, + { + "name": "internal", + "comment": "power system internal reference, do not modify", + "data_type": "InfrastructureSystemsInternal", + "internal_default": "InfrastructureSystemsInternal()", + "exclude_setter": true + } + ], + "supertype": "Technology" + }, + { + "struct_name": "TransportTechnology", + "parametric": "PSY.Device", + "docstring": "This struct represents a transport technology in a power system.", + "fields": [ + { + "name": "name", + "comment": "The name of the transport technology.", + "data_type": "String" + }, + { + "name": "available", + "comment": "Indicates whether the technology is available or not in the simulation.", + "null_value": "true", + "data_type": "Bool" + }, + { + "name": "capital_cost", + "comment": "The capital cost of the technology.", + "data_type": "IS.FunctionData" + }, + { + "name": "ext", + "data_type": "Dict{String, Any}", + "null_value": "Dict{String, Any}()", + "default": "Dict{String, Any}()" + }, + { + "name": "supplemental_attributes_container", + "comment": "Container for supplemental attributes.", + "null_value": "InfrastructureSystems.SupplementalAttributesContainer()", + "data_type": "InfrastructureSystems.SupplementalAttributesContainer", + "default": "InfrastructureSystems.SupplementalAttributesContainer()" + }, + { + "name": "time_series_container", + "comment": "internal time_series storage", + "null_value": "InfrastructureSystems.TimeSeriesContainer()", + "data_type": "InfrastructureSystems.TimeSeriesContainer", + "default": "InfrastructureSystems.TimeSeriesContainer()" + }, + { + "name": "internal", + "comment": "power system internal reference, do not modify", + "data_type": "InfrastructureSystemsInternal", + "internal_default": "InfrastructureSystemsInternal()", + "exclude_setter": true + } + ], + "supertype": "Technology" + } + ], + "struct_validation_descriptors": [] +} diff --git a/src/models/DemandRequirement.jl b/src/models/DemandRequirement.jl new file mode 100644 index 0000000..4855444 --- /dev/null +++ b/src/models/DemandRequirement.jl @@ -0,0 +1,79 @@ +#= +This file is auto-generated. Do not edit. +=# + +#! format: off + +""" + mutable struct DemandRequirement{T <: PSY.StaticInjection} <: PSY.StaticInjection + name::String + available::Bool + region::Union{PSY.ACBus, PSY.AggregationTopology} + ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + time_series_container::InfrastructureSystems.TimeSeriesContainer + internal::InfrastructureSystemsInternal + end + +This struct represents the demand requirement for a power system. + +# Arguments +- `name::String`: The name of the load demand requirement. +- `available::Bool`: Indicates whether the load demand is available or not in the simulation. +- `region::Union{PSY.ACBus, PSY.AggregationTopology}`: The region of the demand requirement. +- `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: Container for supplemental attributes. +- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify +""" +mutable struct DemandRequirement{T <: PSY.StaticInjection} <: PSY.StaticInjection + "The name of the load demand requirement." + name::String + "Indicates whether the load demand is available or not in the simulation." + available::Bool + "The region of the demand requirement." + region::Union{PSY.ACBus, PSY.AggregationTopology} + ext::Dict{String, Any} + "Container for supplemental attributes." + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + "internal time_series storage" + time_series_container::InfrastructureSystems.TimeSeriesContainer + "power system internal reference, do not modify" + internal::InfrastructureSystemsInternal +end + +function DemandRequirement{T}(name, available, region, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) where T <: PSY.StaticInjection + DemandRequirement{T}(name, available, region, ext, supplemental_attributes_container, time_series_container, InfrastructureSystemsInternal(), ) +end + +function DemandRequirement{T}(; name, available, region, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) where T <: PSY.StaticInjection + DemandRequirement{T}(name, available, region, ext, supplemental_attributes_container, time_series_container, internal, ) +end + +"""Get [`DemandRequirement`](@ref) `name`.""" +get_name(value::DemandRequirement) = value.name +"""Get [`DemandRequirement`](@ref) `available`.""" +get_available(value::DemandRequirement) = value.available +"""Get [`DemandRequirement`](@ref) `region`.""" +get_region(value::DemandRequirement) = value.region +"""Get [`DemandRequirement`](@ref) `ext`.""" +get_ext(value::DemandRequirement) = value.ext +"""Get [`DemandRequirement`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::DemandRequirement) = value.supplemental_attributes_container +"""Get [`DemandRequirement`](@ref) `time_series_container`.""" +get_time_series_container(value::DemandRequirement) = value.time_series_container +"""Get [`DemandRequirement`](@ref) `internal`.""" +get_internal(value::DemandRequirement) = value.internal + +"""Set [`DemandRequirement`](@ref) `name`.""" +set_name!(value::DemandRequirement, val) = value.name = val +"""Set [`DemandRequirement`](@ref) `available`.""" +set_available!(value::DemandRequirement, val) = value.available = val +"""Set [`DemandRequirement`](@ref) `region`.""" +set_region!(value::DemandRequirement, val) = value.region = val +"""Set [`DemandRequirement`](@ref) `ext`.""" +set_ext!(value::DemandRequirement, val) = value.ext = val +"""Set [`DemandRequirement`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::DemandRequirement, val) = value.supplemental_attributes_container = val +"""Set [`DemandRequirement`](@ref) `time_series_container`.""" +set_time_series_container!(value::DemandRequirement, val) = value.time_series_container = val diff --git a/src/models/DemandSideTechnology.jl b/src/models/DemandSideTechnology.jl new file mode 100644 index 0000000..7839b6b --- /dev/null +++ b/src/models/DemandSideTechnology.jl @@ -0,0 +1,79 @@ +#= +This file is auto-generated. Do not edit. +=# + +#! format: off + +""" + mutable struct DemandSideTechnology{T <: PSY.StaticInjection} <: Technology + name::String + available::Bool + capital_cost::IS.FunctionData + ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + time_series_container::InfrastructureSystems.TimeSeriesContainer + internal::InfrastructureSystemsInternal + end + +This struct represents a demand side technology in a power system. + +# Arguments +- `name::String`: The name of the demand side technology. +- `available::Bool`: Indicates whether the technology is available or not in the simulation. +- `capital_cost::IS.FunctionData`: The capital cost of the technology. +- `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: Container for supplemental attributes. +- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify +""" +mutable struct DemandSideTechnology{T <: PSY.StaticInjection} <: Technology + "The name of the demand side technology." + name::String + "Indicates whether the technology is available or not in the simulation." + available::Bool + "The capital cost of the technology." + capital_cost::IS.FunctionData + ext::Dict{String, Any} + "Container for supplemental attributes." + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + "internal time_series storage" + time_series_container::InfrastructureSystems.TimeSeriesContainer + "power system internal reference, do not modify" + internal::InfrastructureSystemsInternal +end + +function DemandSideTechnology{T}(name, available, capital_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) where T <: PSY.StaticInjection + DemandSideTechnology{T}(name, available, capital_cost, ext, supplemental_attributes_container, time_series_container, InfrastructureSystemsInternal(), ) +end + +function DemandSideTechnology{T}(; name, available, capital_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) where T <: PSY.StaticInjection + DemandSideTechnology{T}(name, available, capital_cost, ext, supplemental_attributes_container, time_series_container, internal, ) +end + +"""Get [`DemandSideTechnology`](@ref) `name`.""" +get_name(value::DemandSideTechnology) = value.name +"""Get [`DemandSideTechnology`](@ref) `available`.""" +get_available(value::DemandSideTechnology) = value.available +"""Get [`DemandSideTechnology`](@ref) `capital_cost`.""" +get_capital_cost(value::DemandSideTechnology) = value.capital_cost +"""Get [`DemandSideTechnology`](@ref) `ext`.""" +get_ext(value::DemandSideTechnology) = value.ext +"""Get [`DemandSideTechnology`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::DemandSideTechnology) = value.supplemental_attributes_container +"""Get [`DemandSideTechnology`](@ref) `time_series_container`.""" +get_time_series_container(value::DemandSideTechnology) = value.time_series_container +"""Get [`DemandSideTechnology`](@ref) `internal`.""" +get_internal(value::DemandSideTechnology) = value.internal + +"""Set [`DemandSideTechnology`](@ref) `name`.""" +set_name!(value::DemandSideTechnology, val) = value.name = val +"""Set [`DemandSideTechnology`](@ref) `available`.""" +set_available!(value::DemandSideTechnology, val) = value.available = val +"""Set [`DemandSideTechnology`](@ref) `capital_cost`.""" +set_capital_cost!(value::DemandSideTechnology, val) = value.capital_cost = val +"""Set [`DemandSideTechnology`](@ref) `ext`.""" +set_ext!(value::DemandSideTechnology, val) = value.ext = val +"""Set [`DemandSideTechnology`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::DemandSideTechnology, val) = value.supplemental_attributes_container = val +"""Set [`DemandSideTechnology`](@ref) `time_series_container`.""" +set_time_series_container!(value::DemandSideTechnology, val) = value.time_series_container = val diff --git a/src/models/StorageTechnology.jl b/src/models/StorageTechnology.jl new file mode 100644 index 0000000..d046d7e --- /dev/null +++ b/src/models/StorageTechnology.jl @@ -0,0 +1,103 @@ +#= +This file is auto-generated. Do not edit. +=# + +#! format: off + +""" + mutable struct StorageTechnology{T <: PSY.Storage} <: Technology + name::String + available::Bool + capital_cost::IS.FunctionData + battery_chemistry::String + prime_mover::PSY.PrimeMovers + operational_cost::PSY.OperationalCost + ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + time_series_container::InfrastructureSystems.TimeSeriesContainer + internal::InfrastructureSystemsInternal + end + +This struct represents a storage technology in a power system. + +# Arguments +- `name::String`: The name of the storage technology. +- `available::Bool`: Indicates whether the technology is available or not in the simulation. +- `capital_cost::IS.FunctionData`: The capital cost of the technology. +- `battery_chemistry::String`: The type of battery chemistry. Implement Chemistry Type Enums in PowerSystems. +- `prime_mover::PSY.PrimeMovers`: The prime mover of the storage technology. +- `operational_cost::PSY.OperationalCost`: The operational cost of the storage technology. +- `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: Container for supplemental attributes. +- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify +""" +mutable struct StorageTechnology{T <: PSY.Storage} <: Technology + "The name of the storage technology." + name::String + "Indicates whether the technology is available or not in the simulation." + available::Bool + "The capital cost of the technology." + capital_cost::IS.FunctionData + "The type of battery chemistry. Implement Chemistry Type Enums in PowerSystems." + battery_chemistry::String + "The prime mover of the storage technology." + prime_mover::PSY.PrimeMovers + "The operational cost of the storage technology." + operational_cost::PSY.OperationalCost + ext::Dict{String, Any} + "Container for supplemental attributes." + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + "internal time_series storage" + time_series_container::InfrastructureSystems.TimeSeriesContainer + "power system internal reference, do not modify" + internal::InfrastructureSystemsInternal +end + +function StorageTechnology{T}(name, available, capital_cost, battery_chemistry, prime_mover, operational_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) where T <: PSY.Storage + StorageTechnology{T}(name, available, capital_cost, battery_chemistry, prime_mover, operational_cost, ext, supplemental_attributes_container, time_series_container, InfrastructureSystemsInternal(), ) +end + +function StorageTechnology{T}(; name, available, capital_cost, battery_chemistry, prime_mover, operational_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) where T <: PSY.Storage + StorageTechnology{T}(name, available, capital_cost, battery_chemistry, prime_mover, operational_cost, ext, supplemental_attributes_container, time_series_container, internal, ) +end + +"""Get [`StorageTechnology`](@ref) `name`.""" +get_name(value::StorageTechnology) = value.name +"""Get [`StorageTechnology`](@ref) `available`.""" +get_available(value::StorageTechnology) = value.available +"""Get [`StorageTechnology`](@ref) `capital_cost`.""" +get_capital_cost(value::StorageTechnology) = value.capital_cost +"""Get [`StorageTechnology`](@ref) `battery_chemistry`.""" +get_battery_chemistry(value::StorageTechnology) = value.battery_chemistry +"""Get [`StorageTechnology`](@ref) `prime_mover`.""" +get_prime_mover(value::StorageTechnology) = value.prime_mover +"""Get [`StorageTechnology`](@ref) `operational_cost`.""" +get_operational_cost(value::StorageTechnology) = value.operational_cost +"""Get [`StorageTechnology`](@ref) `ext`.""" +get_ext(value::StorageTechnology) = value.ext +"""Get [`StorageTechnology`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::StorageTechnology) = value.supplemental_attributes_container +"""Get [`StorageTechnology`](@ref) `time_series_container`.""" +get_time_series_container(value::StorageTechnology) = value.time_series_container +"""Get [`StorageTechnology`](@ref) `internal`.""" +get_internal(value::StorageTechnology) = value.internal + +"""Set [`StorageTechnology`](@ref) `name`.""" +set_name!(value::StorageTechnology, val) = value.name = val +"""Set [`StorageTechnology`](@ref) `available`.""" +set_available!(value::StorageTechnology, val) = value.available = val +"""Set [`StorageTechnology`](@ref) `capital_cost`.""" +set_capital_cost!(value::StorageTechnology, val) = value.capital_cost = val +"""Set [`StorageTechnology`](@ref) `battery_chemistry`.""" +set_battery_chemistry!(value::StorageTechnology, val) = value.battery_chemistry = val +"""Set [`StorageTechnology`](@ref) `prime_mover`.""" +set_prime_mover!(value::StorageTechnology, val) = value.prime_mover = val +"""Set [`StorageTechnology`](@ref) `operational_cost`.""" +set_operational_cost!(value::StorageTechnology, val) = value.operational_cost = val +"""Set [`StorageTechnology`](@ref) `ext`.""" +set_ext!(value::StorageTechnology, val) = value.ext = val +"""Set [`StorageTechnology`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::StorageTechnology, val) = value.supplemental_attributes_container = val +"""Set [`StorageTechnology`](@ref) `time_series_container`.""" +set_time_series_container!(value::StorageTechnology, val) = value.time_series_container = val diff --git a/src/models/SupplyTechnology.jl b/src/models/SupplyTechnology.jl new file mode 100644 index 0000000..f2c8d18 --- /dev/null +++ b/src/models/SupplyTechnology.jl @@ -0,0 +1,111 @@ +#= +This file is auto-generated. Do not edit. +=# + +#! format: off + +""" + mutable struct SupplyTechnology{T <: PSY.Generator} <: Technology + name::String + available::Bool + fuel::PSY.ThermalFuels + prime_mover::PSY.PrimeMovers + capacity_factor::Float64 + capital_cost::Union{Nothing, IS.FunctionData} + operational_cost::Union{Nothing, PSY.OperationalCost} + ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + time_series_container::InfrastructureSystems.TimeSeriesContainer + internal::InfrastructureSystemsInternal + end + +This struct represents a supply technology in a power system. + +# Arguments +- `name::String`: The name of the supply technology. +- `available::Bool`: Indicates whether the technology is available or not in the simulation. +- `fuel::PSY.ThermalFuels`: The type of fuel used by the supply technology. +- `prime_mover::PSY.PrimeMovers`: The prime mover of the supply technology. +- `capacity_factor::Float64`: The capacity factor of the supply technology. +- `capital_cost::Union{Nothing, IS.FunctionData}`: The capital cost of the technology. +- `operational_cost::Union{Nothing, PSY.OperationalCost}`: The operational cost of the supply technology. +- `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: Container for supplemental attributes. +- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify +""" +mutable struct SupplyTechnology{T <: PSY.Generator} <: Technology + "The name of the supply technology." + name::String + "Indicates whether the technology is available or not in the simulation." + available::Bool + "The type of fuel used by the supply technology." + fuel::PSY.ThermalFuels + "The prime mover of the supply technology." + prime_mover::PSY.PrimeMovers + "The capacity factor of the supply technology." + capacity_factor::Float64 + "The capital cost of the technology." + capital_cost::Union{Nothing, IS.FunctionData} + "The operational cost of the supply technology." + operational_cost::Union{Nothing, PSY.OperationalCost} + ext::Dict{String, Any} + "Container for supplemental attributes." + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + "internal time_series storage" + time_series_container::InfrastructureSystems.TimeSeriesContainer + "power system internal reference, do not modify" + internal::InfrastructureSystemsInternal +end + +function SupplyTechnology{T}(name, available, fuel, prime_mover, capacity_factor, capital_cost, operational_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) where T <: PSY.Generator + SupplyTechnology{T}(name, available, fuel, prime_mover, capacity_factor, capital_cost, operational_cost, ext, supplemental_attributes_container, time_series_container, InfrastructureSystemsInternal(), ) +end + +function SupplyTechnology{T}(; name, available, fuel, prime_mover, capacity_factor, capital_cost, operational_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) where T <: PSY.Generator + SupplyTechnology{T}(name, available, fuel, prime_mover, capacity_factor, capital_cost, operational_cost, ext, supplemental_attributes_container, time_series_container, internal, ) +end + +"""Get [`SupplyTechnology`](@ref) `name`.""" +get_name(value::SupplyTechnology) = value.name +"""Get [`SupplyTechnology`](@ref) `available`.""" +get_available(value::SupplyTechnology) = value.available +"""Get [`SupplyTechnology`](@ref) `fuel`.""" +get_fuel(value::SupplyTechnology) = value.fuel +"""Get [`SupplyTechnology`](@ref) `prime_mover`.""" +get_prime_mover(value::SupplyTechnology) = value.prime_mover +"""Get [`SupplyTechnology`](@ref) `capacity_factor`.""" +get_capacity_factor(value::SupplyTechnology) = value.capacity_factor +"""Get [`SupplyTechnology`](@ref) `capital_cost`.""" +get_capital_cost(value::SupplyTechnology) = value.capital_cost +"""Get [`SupplyTechnology`](@ref) `operational_cost`.""" +get_operational_cost(value::SupplyTechnology) = value.operational_cost +"""Get [`SupplyTechnology`](@ref) `ext`.""" +get_ext(value::SupplyTechnology) = value.ext +"""Get [`SupplyTechnology`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::SupplyTechnology) = value.supplemental_attributes_container +"""Get [`SupplyTechnology`](@ref) `time_series_container`.""" +get_time_series_container(value::SupplyTechnology) = value.time_series_container +"""Get [`SupplyTechnology`](@ref) `internal`.""" +get_internal(value::SupplyTechnology) = value.internal + +"""Set [`SupplyTechnology`](@ref) `name`.""" +set_name!(value::SupplyTechnology, val) = value.name = val +"""Set [`SupplyTechnology`](@ref) `available`.""" +set_available!(value::SupplyTechnology, val) = value.available = val +"""Set [`SupplyTechnology`](@ref) `fuel`.""" +set_fuel!(value::SupplyTechnology, val) = value.fuel = val +"""Set [`SupplyTechnology`](@ref) `prime_mover`.""" +set_prime_mover!(value::SupplyTechnology, val) = value.prime_mover = val +"""Set [`SupplyTechnology`](@ref) `capacity_factor`.""" +set_capacity_factor!(value::SupplyTechnology, val) = value.capacity_factor = val +"""Set [`SupplyTechnology`](@ref) `capital_cost`.""" +set_capital_cost!(value::SupplyTechnology, val) = value.capital_cost = val +"""Set [`SupplyTechnology`](@ref) `operational_cost`.""" +set_operational_cost!(value::SupplyTechnology, val) = value.operational_cost = val +"""Set [`SupplyTechnology`](@ref) `ext`.""" +set_ext!(value::SupplyTechnology, val) = value.ext = val +"""Set [`SupplyTechnology`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::SupplyTechnology, val) = value.supplemental_attributes_container = val +"""Set [`SupplyTechnology`](@ref) `time_series_container`.""" +set_time_series_container!(value::SupplyTechnology, val) = value.time_series_container = val diff --git a/src/models/TransportTechnology.jl b/src/models/TransportTechnology.jl new file mode 100644 index 0000000..54f86aa --- /dev/null +++ b/src/models/TransportTechnology.jl @@ -0,0 +1,79 @@ +#= +This file is auto-generated. Do not edit. +=# + +#! format: off + +""" + mutable struct TransportTechnology{T <: PSY.Device} <: Technology + name::String + available::Bool + capital_cost::IS.FunctionData + ext::Dict{String, Any} + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + time_series_container::InfrastructureSystems.TimeSeriesContainer + internal::InfrastructureSystemsInternal + end + +This struct represents a transport technology in a power system. + +# Arguments +- `name::String`: The name of the transport technology. +- `available::Bool`: Indicates whether the technology is available or not in the simulation. +- `capital_cost::IS.FunctionData`: The capital cost of the technology. +- `ext::Dict{String, Any}` +- `supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer`: Container for supplemental attributes. +- `time_series_container::InfrastructureSystems.TimeSeriesContainer`: internal time_series storage +- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify +""" +mutable struct TransportTechnology{T <: PSY.Device} <: Technology + "The name of the transport technology." + name::String + "Indicates whether the technology is available or not in the simulation." + available::Bool + "The capital cost of the technology." + capital_cost::IS.FunctionData + ext::Dict{String, Any} + "Container for supplemental attributes." + supplemental_attributes_container::InfrastructureSystems.SupplementalAttributesContainer + "internal time_series storage" + time_series_container::InfrastructureSystems.TimeSeriesContainer + "power system internal reference, do not modify" + internal::InfrastructureSystemsInternal +end + +function TransportTechnology{T}(name, available, capital_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), ) where T <: PSY.Device + TransportTechnology{T}(name, available, capital_cost, ext, supplemental_attributes_container, time_series_container, InfrastructureSystemsInternal(), ) +end + +function TransportTechnology{T}(; name, available, capital_cost, ext=Dict{String, Any}(), supplemental_attributes_container=InfrastructureSystems.SupplementalAttributesContainer(), time_series_container=InfrastructureSystems.TimeSeriesContainer(), internal=InfrastructureSystemsInternal(), ) where T <: PSY.Device + TransportTechnology{T}(name, available, capital_cost, ext, supplemental_attributes_container, time_series_container, internal, ) +end + +"""Get [`TransportTechnology`](@ref) `name`.""" +get_name(value::TransportTechnology) = value.name +"""Get [`TransportTechnology`](@ref) `available`.""" +get_available(value::TransportTechnology) = value.available +"""Get [`TransportTechnology`](@ref) `capital_cost`.""" +get_capital_cost(value::TransportTechnology) = value.capital_cost +"""Get [`TransportTechnology`](@ref) `ext`.""" +get_ext(value::TransportTechnology) = value.ext +"""Get [`TransportTechnology`](@ref) `supplemental_attributes_container`.""" +get_supplemental_attributes_container(value::TransportTechnology) = value.supplemental_attributes_container +"""Get [`TransportTechnology`](@ref) `time_series_container`.""" +get_time_series_container(value::TransportTechnology) = value.time_series_container +"""Get [`TransportTechnology`](@ref) `internal`.""" +get_internal(value::TransportTechnology) = value.internal + +"""Set [`TransportTechnology`](@ref) `name`.""" +set_name!(value::TransportTechnology, val) = value.name = val +"""Set [`TransportTechnology`](@ref) `available`.""" +set_available!(value::TransportTechnology, val) = value.available = val +"""Set [`TransportTechnology`](@ref) `capital_cost`.""" +set_capital_cost!(value::TransportTechnology, val) = value.capital_cost = val +"""Set [`TransportTechnology`](@ref) `ext`.""" +set_ext!(value::TransportTechnology, val) = value.ext = val +"""Set [`TransportTechnology`](@ref) `supplemental_attributes_container`.""" +set_supplemental_attributes_container!(value::TransportTechnology, val) = value.supplemental_attributes_container = val +"""Set [`TransportTechnology`](@ref) `time_series_container`.""" +set_time_series_container!(value::TransportTechnology, val) = value.time_series_container = val diff --git a/src/models/includes.jl b/src/models/includes.jl new file mode 100644 index 0000000..615220b --- /dev/null +++ b/src/models/includes.jl @@ -0,0 +1,30 @@ +include("DemandRequirement.jl") +include("DemandSideTechnology.jl") +include("StorageTechnology.jl") +include("SupplyTechnology.jl") +include("TransportTechnology.jl") + +export get_available +export get_battery_chemistry +export get_capacity_factor +export get_capital_cost +export get_ext +export get_fuel +export get_name +export get_operational_cost +export get_prime_mover +export get_region +export get_supplemental_attributes_container +export get_time_series_container +export set_available! +export set_battery_chemistry! +export set_capacity_factor! +export set_capital_cost! +export set_ext! +export set_fuel! +export set_name! +export set_operational_cost! +export set_prime_mover! +export set_region! +export set_supplemental_attributes_container! +export set_time_series_container! diff --git a/src/models/serialization.jl b/src/models/serialization.jl new file mode 100644 index 0000000..4321045 --- /dev/null +++ b/src/models/serialization.jl @@ -0,0 +1,142 @@ +const _ENCODE_AS_UUID_A = ( + Union{Nothing, PSY.Arc}, + Union{Nothing, PSY.Area}, + Union{Nothing, PSY.Bus}, + Union{Nothing, PSY.LoadZone}, + Union{Nothing, PSY.DynamicInjection}, + Union{Nothing, PSY.StaticInjection}, + Vector{PSY.Service}, + PSY.ThermalGen, + PSY.Storage, + PSY.StaticLoad, + PSY.RenewableGen, +) + +const _ENCODE_AS_UUID_B = ( + PSY.Arc, + PSY.Area, + PSY.Bus, + PSY.LoadZone, + PSY.DynamicInjection, + PSY.StaticInjection, + Vector{PSY.Service}, + PSY.ThermalGen, + PSY.Storage, + PSY.StaticLoad, + PSY.RenewableGen, +) + +@assert length(_ENCODE_AS_UUID_A) == length(_ENCODE_AS_UUID_B) + +should_encode_as_uuid(val) = any(x -> val isa x, _ENCODE_AS_UUID_B) +# TODO: how does this work? +should_encode_as_uuid(::Type{T}) where {T} = any(x -> T <: x, _ENCODE_AS_UUID_A) + +function IS.serialize(technology::T) where {T <: Technology} + @debug "serialize" _group = IS.LOG_GROUP_SERIALIZATION technology T + data = Dict{String, Any}() + for name in fieldnames(T) + val = serialize_uuid_handling(getfield(technology, name)) + if name == :ext + if !IS.is_ext_valid_for_serialization(val) + error( + "technology type=$T name=$(get_name(technology)) has a value in its " * + "ext field that cannot be serialized.", + ) + end + end + data[string(name)] = val + end + + IS.add_serialization_metadata!(data, T) + + # This is a temporary workaround until these types are not parameterized. + data[IS.METADATA_KEY][IS.CONSTRUCT_WITH_PARAMETERS_KEY] = true + + return data +end + +function serialize_uuid_handling(val) + if should_encode_as_uuid(val) + if val isa Array + value = IS.get_uuid.(val) + elseif val === nothing + value = nothing + else + value = IS.get_uuid(val) + end + else + value = val + end + + return serialize(value) +end + +function IS.deserialize(::Type{T}, data::Dict, tech_cache::Dict) where {T <: Technology} + @debug "deserialize Technology" _group = IS.LOG_GROUP_SERIALIZATION T data + vals = Dict{Symbol, Any}() + @show data, tech_cache + for (name, type) in zip(fieldnames(T), fieldtypes(T)) + field_name = string(name) + if haskey(data, field_name) + val = data[field_name] + else + continue + end + if val isa Dict && haskey(val, IS.METADATA_KEY) + vals[name] = deserialize_uuid_handling( + IS.get_type_from_serialization_metadata(IS.get_serialization_metadata(val)), + val, + tech_cache, + ) + else + @show val, field_name + vals[name] = deserialize_uuid_handling(type, val, tech_cache) + end + end + type = IS.get_type_from_serialization_metadata(data[IS.METADATA_KEY]) + return type(; vals...) +end + +function IS.deserialize(::Type{Technology}, data::Dict) + error("This form of IS.deserialize is not supported for Devices") + return +end + +""" +Deserialize the value, converting UUIDs to technologies where necessary. +""" +function deserialize_uuid_handling(field_type, val, tech_cache) + @debug "deserialize_uuid_handling" _group = IS.LOG_GROUP_SERIALIZATION field_type val + if val === nothing + value = val + elseif should_encode_as_uuid(field_type) + if field_type <: Vector + _vals = field_type() + for _val in val + uuid = deserialize(Base.UUID, _val) + tech = tech_cache[uuid] + push!(_vals, tech) + end + value = _vals + else + uuid = deserialize(Base.UUID, val) + tech = tech_cache[uuid] + value = tech + end + elseif field_type <: Technology + value = IS.deserialize(field_type, val, tech_cache) + elseif field_type <: Union{Nothing, Technology} + value = IS.deserialize(field_type.b, val, tech_cache) + elseif field_type <: InfrastructureSystemsType + value = deserialize(field_type, val) + elseif field_type isa Union && field_type.a <: Nothing && !(field_type.b <: Union) + # Nothing has already been handled. Apply the second type as long as there isn't a + # third. Julia appears to always put the Nothing in field a. + value = deserialize(field_type.b, val) + else + value = deserialize(field_type, val) + end + + return value +end diff --git a/src/portfolio.jl b/src/portfolio.jl index a7b0fe5..5d7dfb3 100644 --- a/src/portfolio.jl +++ b/src/portfolio.jl @@ -4,6 +4,12 @@ const PORTFOLIO_KWARGS = const DEFAULT_DISCOUNT_RATE = 0.07 const DEFAULT_AGGREGATION = PSY.ACBus +const PORTFOLIO_STRUCT_DESCRIPTOR_FILE = joinpath( + dirname(pathof(PowerSystemsInvestmentsPortfolios)), + "descriptors", + "portfolio_structs.json", +) + mutable struct PortfolioMetadata <: IS.InfrastructureSystemsType name::Union{Nothing, String} description::Union{Nothing, String} @@ -542,24 +548,343 @@ function has_technology( return IS.has_component(T, portfolio.data.components, name) end -function IS.serialize(portfolio::Portfolio) - return +function IS.serialize(portfolio::T) where {T <: Portfolio} + data = Dict{String, Any}() + data["data_format_version"] = DATA_FORMAT_VERSION + for field in fieldnames(T) + # Exclude bus_numbers because they will get rebuilt during deserialization. + # Exclude time_series_directory because the portfolio may get deserialized on a + # different portfolio. + if field != :bus_numbers && field != :time_series_directory + data[string(field)] = serialize(getfield(portfolio, field)) + end + end + + return data +end + +function IS.deserialize(::Type{Portfolio}, filename::AbstractString, kwargs...) + raw = open(filename) do io + JSON3.read(io, Dict) + end + + if raw["data_format_version"] != DATA_FORMAT_VERSION + pre_read_conversion!(raw) + end + + # These file paths are relative to the system file. + directory = dirname(filename) + for file_key in ("time_series_storage_file",) + if haskey(raw["data"], file_key) && !isabspath(raw["data"][file_key]) + raw["data"][file_key] = joinpath(directory, raw["data"][file_key]) + end + end + # return raw + return from_dict(Portfolio, raw; kwargs...) end -function IS.deserialize( +""" +Clear any value stored in ext. +""" +clear_ext!(sys::Portfolio) = IS.clear_ext!(sys.internal) + +function from_dict( ::Type{Portfolio}, - filename::AbstractString; + raw::Dict{String, Any}; time_series_read_only=false, time_series_directory=nothing, + config_path=PORTFOLIO_STRUCT_DESCRIPTOR_FILE, kwargs..., ) - portfolio = nothing + # Read any field that is defined in Portfolio but optional for the constructors and not + # already handled here. + + handled = ( + "aggregation", + "discount_rate", + "data", + "investment_schedule", + "time_series_directory", + "time_series_container", + "metadata", + "internal", + ) + parsed_kwargs = Dict{Symbol, Any}() + for field in setdiff(keys(raw), handled) + parsed_kwargs[Symbol(field)] = raw[field] + end + + # The user can override the serialized runchecks value by passing a kwarg here. + if haskey(kwargs, :runchecks) + parsed_kwargs[:runchecks] = kwargs[:runchecks] + end + + # units = IS.deserialize(SystemUnitsSettings, raw["units_settings"]) + data = IS.deserialize( + IS.SystemData, + raw["data"]; + time_series_read_only=time_series_read_only, + time_series_directory=time_series_directory, + validation_descriptor_file=config_path, + ) + metadata = get(raw, "metadata", Dict()) + name = get(metadata, "name", nothing) + description = get(metadata, "description", nothing) + internal = IS.deserialize(InfrastructureSystemsInternal, raw["internal"]) + aggregation = PSY.ACBus + discount_rate = raw["discount_rate"] + investment_schedule = raw["investment_schedule"] + portfolio = Portfolio( + aggregation, + discount_rate, + data, + investment_schedule, + internal, + # name = name, + # description = description, + # parsed_kwargs..., + ) + + if raw["data_format_version"] != DATA_FORMAT_VERSION + pre_deserialize_conversion!(raw, portfolio) + end + + ext = get_ext(portfolio) + ext["deserialization_in_progress"] = true + try + deserialize_components!(portfolio, raw["data"]) + finally + pop!(ext, "deserialization_in_progress") + isempty(ext) && clear_ext!(portfolio) + end + + # if !get_runchecks(portfolio) + # @warn "The System was deserialized with checks disabled, and so was not validated." + # end + + if raw["data_format_version"] != DATA_FORMAT_VERSION + post_deserialize_conversion!(portfolio, raw) + end + return portfolio end -function deserialize_components!(portfolio::Portfolio, raw) end +function deserialize_components!(sys::Portfolio, raw) + # Convert the array of components into type-specific arrays to allow addition by type. + data = Dict{Any, Vector{Dict}}() + + for component in raw["components"] + type = IS.get_type_from_serialization_data(component) + components = get(data, type, nothing) + if components === nothing + components = Vector{Dict}() + data[type] = components + end + push!(components, component) + end + + # Maintain a lookup of UUID to component because some component types encode + # composed types as UUIDs instead of actual types. + component_cache = Dict{Base.UUID, Technology}() + + # Add each type to this as we parse. + parsed_types = Set() + + function is_matching_type(type, types) + return any(x -> type <: x, types) + end + + function deserialize_and_add!(; + skip_types=nothing, + include_types=nothing, + post_add_func=nothing, + ) + for (type, components) in data + type in parsed_types && continue + if !isnothing(skip_types) && is_matching_type(type, skip_types) + continue + end + if !isnothing(include_types) && !is_matching_type(type, include_types) + continue + end + for component in components + handle_deserialization_special_cases!(component, type) + comp = deserialize(type, component, component_cache) + add_technology!(sys, comp) + component_cache[IS.get_uuid(comp)] = comp + if !isnothing(post_add_func) + post_add_func(comp) + end + end + push!(parsed_types, type) + end + end + + deserialize_and_add!() +end + +""" +Allow types to implement handling of special cases during deserialization. + +# Arguments + + - `component::Dict`: The component serialized as a dictionary. + - `::Type`: The type of the technology. +""" +handle_deserialization_special_cases!(component::Dict, ::Type{<:Technology}) = nothing function _is_deserialization_in_progress(portfolio::Portfolio) ext = get_ext(portfolio) return get(ext, "deserialization_in_progress", false) end + +""" +Serializes a portfolio to a JSON file and saves time series to an HDF5 file. + +# Arguments + + - `portfolio::Portfolio`: portfolio + - `filename::AbstractString`: filename to write + +# Keyword arguments + + - `user_data::Union{Nothing, Dict} = nothing`: optional metadata to record + - `pretty::Bool = false`: whether to pretty-print the JSON + - `force::Bool = false`: whether to overwrite existing files + - `check::Bool = false`: whether to run portfolio validation checks + +Refer to [`check_component`](@ref) for exceptions thrown if `check = true`. +""" +function IS.to_json( + portfolio::Portfolio, + filename::AbstractString; + user_data=nothing, + pretty=false, + force=false, + runchecks=false, +) + # TODO: add checks for portfolio and technology + # if runchecks + # check(portfolio) + # check_technologies(portfolio) + # end + + IS.prepare_for_serialization_to_file!(portfolio.data, filename; force=force) + data = to_json(portfolio; pretty=pretty) + open(filename, "w") do io + write(io, data) + end + + mfile = joinpath(dirname(filename), splitext(basename(filename))[1] * "_metadata.json") + _serialize_portfolio_metadata_to_file(portfolio, mfile, user_data) + @info "Serialized Portfolio to $filename" + + return +end + +function _serialize_portfolio_metadata_to_file(portfolio::Portfolio, filename, user_data) + name = get_name(portfolio) + description = get_description(portfolio) + resolution = get_time_series_resolution(portfolio).value + metadata = OrderedDict( + "name" => isnothing(name) ? "" : name, + "description" => isnothing(description) ? "" : description, + "time_series_resolution_milliseconds" => resolution, + "component_counts" => IS.get_component_counts_by_type(portfolio.data), + "time_series_counts" => IS.get_time_series_counts_by_type(portfolio.data), + ) + if !isnothing(user_data) + metadata["user_data"] = user_data + end + + open(filename, "w") do io + JSON3.pretty(io, metadata) + end + + @info "Serialized Portfolio metadata to $filename" +end + +""" +If assign_new_uuids = true, generate new UUIDs for the portfolio and all components. + +Warning: time series data is not restored by this method. If that is needed, use the normal +process to construct the portfolio from a serialized JSON file instead, such as with +`Portfolio("portfolio.json")`. +""" +function IS.from_json( + io::Union{IO, String}, + ::Type{Portfolio}; + runchecks=true, + assign_new_uuids=false, + kwargs..., +) + data = JSON3.read(io, Dict) + # These objects could be removed in to_json(portfolio). Doing it here will allow us to + # keep that JSON string fully consistent with time series and potentially use it in the + # future. + for component in data["data"]["components"] + if haskey(component, "time_series_container") + empty!(component["time_series_container"]) + end + end + + portfolio = from_dict(Portfolio, data; kwargs...) + _post_deserialize_handling( + portfolio; + runchecks=runchecks, + assign_new_uuids=assign_new_uuids, + ) + return portfolio +end + +function _post_deserialize_handling( + portfolio::Portfolio; + runchecks=true, + assign_new_uuids=false, +) + # runchecks && check(portfolio) + if assign_new_uuids + IS.assign_new_uuid!(portfolio) + for component in get_components(Technology, portfolio) + assign_new_uuid!(portfolio, component) + end + for component in + IS.get_masked_components(InfrastructureSystemsComponent, portfolio.data) + assign_new_uuid!(portfolio, component) + end + # Note: this does not change UUIDs for time series data because they are + # shared with components. + end +end + +function Portfolio(file_path::AbstractString; assign_new_uuids=false, kwargs...) + ext = splitext(file_path)[2] + if lowercase(ext) in [".m", ".raw"] + pm_kwargs = Dict(k => v for (k, v) in kwargs if !in(k, PORTFOLIO_KWARGS)) + sys_kwargs = Dict(k => v for (k, v) in kwargs if in(k, PORTFOLIO_KWARGS)) + return System(PowerModelsData(file_path; pm_kwargs...); sys_kwargs...) + elseif lowercase(ext) == ".json" + unsupported = setdiff(keys(kwargs), PORTFOLIO_KWARGS) + !isempty(unsupported) && error("Unsupported kwargs = $unsupported") + runchecks = get(kwargs, :runchecks, true) + time_series_read_only = get(kwargs, :time_series_read_only, false) + time_series_directory = get(kwargs, :time_series_directory, nothing) + config_path = get(kwargs, :config_path, PORTFOLIO_STRUCT_DESCRIPTOR_FILE) + portfolio = deserialize( + Portfolio, + file_path; + # time_series_read_only = time_series_read_only, + # runchecks = runchecks, + # time_series_directory = time_series_directory, + # config_path = config_path, + ) + _post_deserialize_handling( + portfolio; + runchecks=runchecks, + assign_new_uuids=assign_new_uuids, + ) + return portfolio + else + throw(DataFormatError("$file_path is not a supported file type")) + end +end diff --git a/src/storage.jl b/src/storage.jl deleted file mode 100644 index 7039971..0000000 --- a/src/storage.jl +++ /dev/null @@ -1,12 +0,0 @@ -struct StorageTechnology{T <: PSY.Storage} <: Technology - name::String - available::Bool - power_systems_type::Type{T} - capital_cost::IS.FunctionData - battery_chemistry::String # Implement Chemistry Type Enums in PowerSystems - prime_mover::PSY.PrimeMovers - operational_cost::PSY.OperationalCost - supplemental_attributes_container::IS.SupplementalAttributesContainer - time_series_container::IS.TimeSeriesContainer - internal::InfrastructureSystemsInternal -end diff --git a/src/supply.jl b/src/supply.jl deleted file mode 100644 index 567372b..0000000 --- a/src/supply.jl +++ /dev/null @@ -1,13 +0,0 @@ -struct SupplyTechnology{T <: PSY.Generator} <: Technology - name::String - available::Bool - power_systems_type::Type{T} - fuel::PSY.ThermalFuels - prime_mover::PSY.PrimeMovers - capacity_factor::Float64 - capital_cost::Union{Nothing, IS.FunctionData} - operational_cost::Union{Nothing, PSY.OperationalCost} - supplemental_attributes_container::IS.SupplementalAttributesContainer - time_series_container::IS.TimeSeriesContainer - internal::InfrastructureSystemsInternal -end diff --git a/src/transport.jl b/src/transport.jl deleted file mode 100644 index 06ddcfb..0000000 --- a/src/transport.jl +++ /dev/null @@ -1,9 +0,0 @@ -struct TransportTechnology{T <: PSY.Device} <: Technology - name::String - available::Bool - power_systems_type::Type{T} - capital_cost::IS.FunctionData - supplemental_attributes_container::IS.SupplementalAttributesContainer - time_series_container::IS.TimeSeriesContainer - internal::InfrastructureSystemsInternal -end diff --git a/test/Project.toml b/test/Project.toml index 4d450b6..5de27b8 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -3,6 +3,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" PowerSystemsInvestmentsPortfolios = "bed98974-b02a-5e2f-9fe0-a103f8c450dd" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] julia = "^1.6"