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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
35 changes: 35 additions & 0 deletions src/components.jl
Original file line number Diff line number Diff line change
@@ -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 = ('/',)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@josephmckinsey we need to enforce this somewhere in the DB and Schema for the existing versions that's fine but in the future IS4 and PSY6 we should eliminate it


"""
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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/forecasts.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions src/function_data/function_data.jl
Original file line number Diff line number Diff line change
@@ -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

"""
Expand Down
11 changes: 10 additions & 1 deletion src/internal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
6 changes: 6 additions & 0 deletions src/production_variable_cost_curve.jl
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/time_series_parser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
8 changes: 8 additions & 0 deletions src/time_series_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions src/utils/logging.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 18 additions & 0 deletions test/test_system_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Comment on lines +90 to +94
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The test mutates the global ENV and then unconditionally deletes the key in finally, which will drop any pre-existing value and can leak state to subsequent tests in the same process. Prefer Base.withenv("SIENNA_DISABLE_COMPONENT_NAME_CHECKS" => "true") do ... end (or save/restore the previous value) so the original environment is reliably restored.

Copilot uses AI. Check for mistakes.
end
end

@testset "Test masked components" begin
data = IS.SystemData()
initial_time = Dates.DateTime("2020-09-01")
Expand Down
Loading