Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add with_units_base feature #1232

Merged
merged 3 commits into from
Dec 26, 2024
Merged
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
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
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
Loading