Skip to content

Commit

Permalink
Merge branch 'main' into ac/documentation_updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kdayday authored Jan 7, 2025
2 parents 5622382 + 4df1a27 commit ce73a95
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "PowerSystems"
uuid = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd"
authors = ["Jose Daniel Lara", "Daniel Thom", "Clayton Barrows", "Sourabh Dalvi", "Dheepak Krishnamurthy"]
version = "4.4.0"
version = "4.4.1"

[deps]
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
Expand Down
7 changes: 5 additions & 2 deletions docs/src/explanation/per_unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ These three unit bases allow easy conversion between unit systems.
This allows `PowerSystems.jl` users to input data in the formats they have available,
as well as view data in the unit system that is most intuitive to them.

You can get and set the unit system setting of a `System` with [`get_units_base`](@ref)
and [`set_units_base_system!`](@ref).
You can get and set the unit system setting of a `System` with [`get_units_base`](@ref) and
[`set_units_base_system!`](@ref). To support a less stateful style of programming,
`PowerSystems.jl` provides the `Logging.with_logger`-inspired "context manager"-type
function [`with_units_base`](@ref), which sets the unit system to a particular value,
performs some action, then automatically sets the unit system back to its previous value.

Conversion between unit systems does not change
the stored parameter values. Instead, unit system conversions are made when accessing
Expand Down
12 changes: 12 additions & 0 deletions docs/src/tutorials/creating_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,18 @@ get_rating(retrieved_component)
See that now the data is now 1.0 (5.0 MVA per-unitized by the generator (i.e., the device's)
`base_power` of 5.0 MVA), which is the format we used to originally define the device.

As a shortcut to temporarily set the `System`'s unit system to a particular value, perform
some action, and then automatically set it back to what it was before, we can use
`with_units_base` and a [`do` block](https://docs.julialang.org/en/v1/manual/functions/#Do-Block-Syntax-for-Function-Arguments):

```@repl basics
with_units_base(sys, "NATURAL_UNITS") do
# Everything inside this block will run as if the unit system were NATURAL_UNITS
get_rating(retrieved_component)
end
get_units_base(sys) # Unit system goes back to previous value when the block ends
```

Recall that if you ever need to check a `System`'s settings, including the unit system being
used by all the getter functions, you can always just print the `System`:

Expand Down
1 change: 1 addition & 0 deletions src/PowerSystems.jl
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ export set_description!
export get_base_power
export get_frequency
export set_units_base_system!
export with_units_base
export to_json
export from_json
export serialize
Expand Down
54 changes: 44 additions & 10 deletions src/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -513,27 +513,61 @@ function set_units_setting!(
return
end

function _set_units_base!(system::System, settings::UnitSystem)
to_change = (system.units_settings.unit_system != settings)
to_change && (system.units_settings.unit_system = settings)
return (to_change, settings)
end

_set_units_base!(system::System, settings::String) =
_set_units_base!(system::System, UNIT_SYSTEM_MAPPING[uppercase(settings)])

"""
Sets the units base for the getter functions on the devices. It modifies the behavior of all getter functions
# Examples
```julia
set_units_base_system!(sys, "NATURAL_UNITS")
```
```julia
set_units_base_system!(sys, UnitSystem.SYSTEM_BASE)
```
"""
function set_units_base_system!(system::System, settings::String)
set_units_base_system!(system::System, UNIT_SYSTEM_MAPPING[uppercase(settings)])
function set_units_base_system!(system::System, units::Union{UnitSystem, String})
changed, new_units = _set_units_base!(system::System, units)
changed && @info "Unit System changed to $new_units"
return
end

function set_units_base_system!(system::System, settings::UnitSystem)
if system.units_settings.unit_system != settings
system.units_settings.unit_system = settings
@info "Unit System changed to $settings"
end
return
end
_get_units_base(system::System) = system.units_settings.unit_system

"""
Get the system's [unit base](@ref per_unit))
"""
function get_units_base(system::System)
return string(system.units_settings.unit_system)
return string(_get_units_base(system))
end

"""
A "context manager" that sets the [`System`](@ref)'s [units base](@ref per_unit) to the
given value, executes the function, then sets the units base back.
# Examples
```julia
active_power_mw = with_units_base(sys, UnitSystem.NATURAL_UNITS) do
get_active_power(gen)
end
# now active_power_mw is in natural units no matter what units base the system is in
```
"""
function with_units_base(f::Function, sys::System, units::Union{UnitSystem, String})
old_units = _get_units_base(sys)
_set_units_base!(sys, units)
try
f()
finally
_set_units_base!(sys, old_units)
end
end

function get_units_setting(component::T) where {T <: Component}
Expand Down
49 changes: 32 additions & 17 deletions src/models/components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,38 @@ Default behavior of a component. If there is no base_power field, assume is in t
"""
get_base_power(c::Component) = get_system_base_power(c)

function _get_multiplier(c::T) where {T <: Component}
setting = get_internal(c).units_info
if isnothing(setting)
return 1.0
elseif setting.unit_system == IS.UnitSystem.DEVICE_BASE
return 1.0
elseif setting.unit_system == IS.UnitSystem.SYSTEM_BASE
numerator = get_base_power(c)
denominator = setting.base_value
elseif setting.unit_system == IS.UnitSystem.NATURAL_UNITS
numerator = get_base_power(c)
denominator = 1.0
else
error("Undefined Conditional")
end
return numerator / denominator
end
_get_multiplier(c::T) where {T <: Component} =
_get_multiplier(c, get_internal(c).units_info)

_get_multiplier(::T, ::Nothing) where {T <: Component} =
1.0
_get_multiplier(c::T, setting::IS.SystemUnitsSettings) where {T <: Component} =
_get_multiplier(c, setting, Val(setting.unit_system))

# PERF: dispatching on the UnitSystem values instead of comparing with if/else avoids the
# performance hit associated with consulting the dictionary that backs the @scoped_enum --
# i.e., IS.UnitSystem.NATURAL_UNITS by itself isn't treated as a constant, it's a dictionary
# lookup each time.
_get_multiplier(
::T,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.DEVICE_BASE},
) where {T <: Component} =
1.0
_get_multiplier(
c::T,
setting::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.SYSTEM_BASE},
) where {T <: Component} =
get_base_power(c) / setting.base_value
_get_multiplier(
c::T,
::IS.SystemUnitsSettings,
::Val{IS.UnitSystem.NATURAL_UNITS},
) where {T <: Component} =
get_base_power(c)
_get_multiplier(::T, ::IS.SystemUnitsSettings, ::Val) where {T <: Component} =
error("Undefined Conditional")

function get_value(c::Component, value::Float64)
return _get_multiplier(c) * value
Expand Down
51 changes: 40 additions & 11 deletions src/parsers/power_models_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,14 @@ function make_hydro_gen(
ramp_agc = get(d, "ramp_agc", get(d, "ramp_10", get(d, "ramp_30", abs(d["pmax"]))))
curtailcost = HydroGenerationCost(zero(CostCurve), 0.0)

base_conversion = sys_mbase / d["mbase"]
if d["mbase"] != 0.0
mbase = d["mbase"]
else
@warn "Generator $gen_name has base power equal to zero: $(d["mbase"]). Changing it to system base: $sys_mbase"
mbase = sys_mbase
end

base_conversion = sys_mbase / mbase
return HydroDispatch(; # No way to define storage parameters for gens in PM so can only make HydroDispatch
name = gen_name,
available = Bool(d["gen_status"]),
Expand All @@ -394,7 +401,7 @@ function make_hydro_gen(
ramp_limits = (up = ramp_agc, down = ramp_agc),
time_limits = nothing,
operation_cost = curtailcost,
base_power = d["mbase"],
base_power = mbase,
)
end

Expand All @@ -405,12 +412,20 @@ function make_renewable_dispatch(
sys_mbase::Float64,
)
cost = RenewableGenerationCost(zero(CostCurve))
base_conversion = sys_mbase / d["mbase"]

if d["mbase"] != 0.0
mbase = d["mbase"]
else
@warn "Generator $gen_name has base power equal to zero: $(d["mbase"]). Changing it to system base: $sys_mbase"
mbase = sys_mbase
end

base_conversion = sys_mbase / mbase

rating = calculate_rating(d["pmax"], d["qmax"])
if rating > d["mbase"]
@warn "rating is larger than base power for $gen_name, setting to $(d["mbase"])"
rating = d["mbase"]
if rating > mbase
@warn "rating is larger than base power for $gen_name, setting to $mbase"
rating = mbase
end

generator = RenewableDispatch(;
Expand All @@ -427,7 +442,7 @@ function make_renewable_dispatch(
),
power_factor = 1.0,
operation_cost = cost,
base_power = d["mbase"],
base_power = mbase,
)

return generator
Expand All @@ -439,7 +454,14 @@ function make_renewable_fix(
bus::ACBus,
sys_mbase::Float64,
)
base_conversion = sys_mbase / d["mbase"]
if d["mbase"] != 0.0
mbase = d["mbase"]
else
@warn "Generator $gen_name has base power equal to zero: $(d["mbase"]). Changing it to system base: $sys_mbase"
mbase = sys_mbase
end

base_conversion = sys_mbase / mbase
generator = RenewableNonDispatch(;
name = gen_name,
available = Bool(d["gen_status"]),
Expand All @@ -449,7 +471,7 @@ function make_renewable_fix(
rating = float(d["pmax"]) * base_conversion,
prime_mover_type = parse_enum_mapping(PrimeMovers, d["type"]),
power_factor = 1.0,
base_power = d["mbase"],
base_power = mbase,
)

return generator
Expand Down Expand Up @@ -555,7 +577,14 @@ function make_thermal_gen(
ext["z_source"] = (r = d["r_source"], x = d["x_source"])
end

base_conversion = sys_mbase / d["mbase"]
if d["mbase"] != 0.0
mbase = d["mbase"]
else
@warn "Generator $gen_name has base power equal to zero: $(d["mbase"]). Changing it to system base: $sys_mbase"
mbase = sys_mbase
end

base_conversion = sys_mbase / mbase
thermal_gen = ThermalStandard(;
name = gen_name,
status = Bool(d["gen_status"]),
Expand All @@ -577,7 +606,7 @@ function make_thermal_gen(
ramp_limits = (up = ramp_lim, down = ramp_lim),
time_limits = nothing,
operation_cost = operation_cost,
base_power = d["mbase"],
base_power = mbase,
ext = ext,
)

Expand Down
24 changes: 16 additions & 8 deletions src/parsers/power_system_table_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -833,11 +833,14 @@ function make_cost(
cost_pairs = get_cost_pairs(gen, cost_colnames)
var_cost, fixed = create_pwinc_cost(cost_pairs)
end
parse_maybe_nothing(x) = isnothing(x) ? 0.0 : tryparse(Float64, x)
vom_cost = parse_maybe_nothing(getfield(gen, Symbol("variable_cost")))
vom_data = LinearCurve(vom_cost)

startup_cost, shutdown_cost = calculate_uc_cost(data, gen, fuel_price)

op_cost = ThermalGenerationCost(
FuelCurve(var_cost, UnitSystem.NATURAL_UNITS, fuel_price),
FuelCurve(var_cost, UnitSystem.NATURAL_UNITS, fuel_price, vom_data),
fixed * fuel_price,
startup_cost,
shutdown_cost,
Expand All @@ -855,9 +858,12 @@ function make_cost(
cost_pairs = get_cost_pairs(gen, cost_colnames)
var_cost = create_pwl_cost(cost_pairs)
startup_cost, shutdown_cost = calculate_uc_cost(data, gen, fuel_price)
parse_maybe_nothing(x) = isnothing(x) ? 0.0 : tryparse(Float64, x)
vom_cost = parse_maybe_nothing(getfield(gen, Symbol("variable_cost")))
vom_data = LinearCurve(vom_cost)

op_cost = ThermalGenerationCost(
CostCurve(var_cost, UnitSystem.NATURAL_UNITS),
CostCurve(var_cost, UnitSystem.NATURAL_UNITS, vom_data),
gen.fixed_cost,
startup_cost,
shutdown_cost,
Expand Down Expand Up @@ -901,14 +907,13 @@ function make_cost(
cost_colnames::_HeatRateColumns,
) where {T <: RenewableGen}
@warn "Heat rate parsing not valid for RenewableGen replacing with zero cost"
parse_maybe_nothing(x) = isnothing(x) ? 0.0 : tryparse(Float64, x)
vom_cost = parse_maybe_nothing(getfield(gen, Symbol("variable_cost")))
vom_data = LinearCurve(vom_cost)
var_cost = CostCurve(;
value_curve = LinearCurve(0.0),
power_units = UnitSystem.NATURAL_UNITS,
vom_cost = if isnothing(gen.variable_cost)
LinearCurve(0.0)
else
LinearCurve(gen.variable_cost)
end,
vom_cost = vom_data,
)
op_cost = RenewableGenerationCost(var_cost)
return op_cost
Expand All @@ -921,10 +926,13 @@ function make_cost(
cost_colnames::_CostPointColumns,
) where {T <: RenewableGen}
cost_pairs = get_cost_pairs(gen, cost_colnames)
parse_maybe_nothing(x) = isnothing(x) ? 0.0 : tryparse(Float64, x)
vom_cost = parse_maybe_nothing(getfield(gen, Symbol("variable_cost")))
vom_data = LinearCurve(vom_cost)
var_cost = CostCurve(;
value_curve = cost_pairs,
power_units = UnitSystem.NATURAL_UNITS,
vom_cost = isnothing(gen.variable_cost) ? 0.0 : gen.variable_cost,
vom_cost = vom_data,
)
op_cost = RenewableGenerationCost(var_cost)
return op_cost
Expand Down
8 changes: 8 additions & 0 deletions test/test_system.jl
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ end
@test get_units_base(sys) == "DEVICE_BASE"
set_units_base_system!(sys, "SYSTEM_BASE")
@test get_units_base(sys) == "SYSTEM_BASE"

gen = get_component(ThermalStandard, sys, "322_CT_6")
active_power_mw = with_units_base(sys, UnitSystem.NATURAL_UNITS) do
get_active_power(gen)
end
@test get_units_base(sys) == "SYSTEM_BASE"
set_units_base_system!(sys, UnitSystem.NATURAL_UNITS)
@test active_power_mw == get_active_power(gen)
end

@testset "Test add_time_series multiple components" begin
Expand Down

0 comments on commit ce73a95

Please sign in to comment.