diff --git a/Project.toml b/Project.toml index b44206f51..09d07dfbe 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "InfrastructureSystems" uuid = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" authors = ["Daniel Thom", "Jose Daniel Lara", "Gabriel Konar-Steenberg", "Clayton Barrows"] -version = "3.3.1" +version = "3.3.2" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" diff --git a/src/components.jl b/src/components.jl index 1bdc47f28..1b69ecc3f 100644 --- a/src/components.jl +++ b/src/components.jl @@ -1,5 +1,35 @@ const ComponentsByType = Dict{DataType, Dict{String, <:InfrastructureSystemsComponent}} +# '/' is the HDF5 path separator and cannot appear in dataset names. +# Refer to https://github.com/NREL-Sienna/PowerSystems.jl/issues/1647 for the trigger of +# this rule. The '/' character could also cause problems if we ever wanted to use the +# component name as a file or directory name. +const INVALID_COMPONENT_NAME_CHARACTERS = ('/',) + +""" +Throw an `ArgumentError` if `name` contains characters that are invalid for component names. + +Set the environment variable `SIENNA_DISABLE_COMPONENT_NAME_CHECKS=true` to bypass this +validation. +""" +function _validate_component_name(name::AbstractString) + if get(ENV, "SIENNA_DISABLE_COMPONENT_NAME_CHECKS", "") == "true" + return + end + for ch in INVALID_COMPONENT_NAME_CHARACTERS + if occursin(ch, name) + throw( + ArgumentError( + "Component name '$name' contains invalid character '$ch'. " * + "Characters $(INVALID_COMPONENT_NAME_CHARACTERS) are not allowed " * + "because they are invalid in HDF5 dataset names.", + ), + ) + end + end + return +end + "A simple container for components and time series data." struct Components <: ComponentContainer data::ComponentsByType @@ -32,6 +62,7 @@ function _add_component!( allow_existing_time_series = false, ) where {T <: InfrastructureSystemsComponent} component_name = get_name(component) + _validate_component_name(component_name) if !isconcretetype(T) throw(ArgumentError("add_component! only accepts concrete types")) end @@ -347,6 +378,9 @@ function iterate_components(components::Components) return iterate_container(components) end +""" +Return the total number of components stored. +""" function get_num_components(components::Components) return get_num_members(components) end @@ -381,6 +415,7 @@ function set_name!( name, ) where {T <: InfrastructureSystemsComponent} throw_if_not_attached(components, component) + _validate_component_name(name) if haskey(components.data[T], name) if components.data[T][name] === component return diff --git a/src/forecasts.jl b/src/forecasts.jl index 0563870e7..1afcac057 100644 --- a/src/forecasts.jl +++ b/src/forecasts.jl @@ -19,6 +19,12 @@ abstract type Forecast <: TimeSeriesData end Base.length(ts::Forecast) = get_count(ts) +""" +Supertype for all deterministic forecast time series. + +Concrete subtypes include [`Deterministic`](@ref) and +[`DeterministicSingleTimeSeries`](@ref). +""" abstract type AbstractDeterministic <: Forecast end function check_time_series_data(forecast::Forecast) diff --git a/src/function_data/function_data.jl b/src/function_data/function_data.jl index 1c9cdfee2..5af457b8d 100644 --- a/src/function_data/function_data.jl +++ b/src/function_data/function_data.jl @@ -1,3 +1,9 @@ +""" +Supertype for all function data representations used in cost modeling. + +Concrete subtypes include [`LinearFunctionData`](@ref), [`QuadraticFunctionData`](@ref), +[`PiecewiseLinearData`](@ref), and [`PiecewiseStepData`](@ref). +""" abstract type FunctionData end """ diff --git a/src/internal.jl b/src/internal.jl index d834798bf..412fbe99f 100644 --- a/src/internal.jl +++ b/src/internal.jl @@ -5,6 +5,15 @@ abstract type UnitsData end @scoped_enum(UnitSystem, SYSTEM_BASE = 0, DEVICE_BASE = 1, NATURAL_UNITS = 2,) +@doc """ +Unit system for component data values. + +# Values +- `SYSTEM_BASE`: Per-unit values on the system base power +- `DEVICE_BASE`: Per-unit values on the device base power +- `NATURAL_UNITS`: Values in natural units (e.g., MW, MVAR) +""" UnitSystem + @kwdef mutable struct SystemUnitsSettings <: UnitsData base_value::Float64 unit_system::UnitSystem @@ -24,7 +33,7 @@ Internal storage common to InfrastructureSystems types. mutable struct InfrastructureSystemsInternal <: InfrastructureSystemsType uuid::Base.UUID shared_system_references::Union{Nothing, SharedSystemReferences} - units_info::Union{Nothing, UnitsData} + units_info::Union{Nothing, SystemUnitsSettings} ext::Union{Nothing, Dict{String, Any}} end diff --git a/src/production_variable_cost_curve.jl b/src/production_variable_cost_curve.jl index 0c3e92803..8e62e2be7 100644 --- a/src/production_variable_cost_curve.jl +++ b/src/production_variable_cost_curve.jl @@ -1,3 +1,9 @@ +""" +Supertype for production variable cost curve representations, parameterized by +a [`ValueCurve`](@ref) type. + +Concrete subtypes include [`CostCurve`](@ref) and [`FuelCurve`](@ref). +""" abstract type ProductionVariableCostCurve{T <: ValueCurve} end serialize(val::ProductionVariableCostCurve) = serialize_struct(val) diff --git a/src/time_series_parser.jl b/src/time_series_parser.jl index 94a66c9f1..a248fb9a0 100644 --- a/src/time_series_parser.jl +++ b/src/time_series_parser.jl @@ -154,6 +154,13 @@ end @scoped_enum NormalizationTypes MAX = 1 +@doc """ +Types of normalization that can be applied to time series data. + +# Values +- `MAX`: Normalize by the maximum value in the time series +""" NormalizationTypes + const NormalizationFactor = Union{Float64, NormalizationTypes} function handle_normalization_factor( diff --git a/src/time_series_storage.jl b/src/time_series_storage.jl index a2cf69376..ccff1b22c 100644 --- a/src/time_series_storage.jl +++ b/src/time_series_storage.jl @@ -18,6 +18,14 @@ const DEFAULT_COMPRESSION = false @scoped_enum(CompressionTypes, BLOSC = 0, DEFLATE = 1,) +@doc """ +HDF5 compression algorithm types for time series storage. + +# Values +- `BLOSC`: Blosc compression (fast, general-purpose) +- `DEFLATE`: Deflate/zlib compression +""" CompressionTypes + """ CompressionSettings(enabled, type, level, shuffle) diff --git a/src/utils/logging.jl b/src/utils/logging.jl index 08881dce9..8edda990e 100644 --- a/src/utils/logging.jl +++ b/src/utils/logging.jl @@ -145,6 +145,11 @@ function LoggingConfiguration(config_filename) return LoggingConfiguration(; Dict(Symbol(k) => v for (k, v) in config)...) end +""" +Create a logging configuration file from the default template. + +Pass `force = true` to overwrite an existing file. +""" function make_logging_config_file(filename = "logging_config.toml"; force = false) cp(SIENNA_LOGGING_CONFIG_FILENAME, filename; force = force) println("Created $filename") diff --git a/test/test_system_data.jl b/test/test_system_data.jl index 7fdd874b4..23351e6df 100644 --- a/test/test_system_data.jl +++ b/test/test_system_data.jl @@ -77,6 +77,24 @@ end @test_throws ArgumentError IS.set_name!(data, component2, new_name) end +@testset "Test invalid component name characters" begin + data = IS.SystemData() + component_slash = IS.TestComponent("E/W", 5) + @test_throws ArgumentError IS.add_component!(data, component_slash) + + valid_component = IS.TestComponent("valid-name", 5) + IS.add_component!(data, valid_component) + @test_throws ArgumentError IS.set_name!(data, valid_component, "invalid/name") + @test IS.get_name(valid_component) == "valid-name" + + withenv("SIENNA_DISABLE_COMPONENT_NAME_CHECKS" => "true") do + data2 = IS.SystemData() + slash_component = IS.TestComponent("E/W", 5) + IS.add_component!(data2, slash_component) + @test IS.get_name(slash_component) == "E/W" + end +end + @testset "Test masked components" begin data = IS.SystemData() initial_time = Dates.DateTime("2020-09-01")