Skip to content

Commit

Permalink
Merge pull request #1232 from NREL-Sienna/gks/with_units_base
Browse files Browse the repository at this point in the history
Add `with_units_base` feature
  • Loading branch information
jd-lara authored Dec 26, 2024
2 parents 774695e + 6e8e2f2 commit ac0a147
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 12 deletions.
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

2 comments on commit ac0a147

@jd-lara
Copy link
Member Author

Choose a reason for hiding this comment

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

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/122036

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v4.4.1 -m "<description of version>" ac0a147bcbffe5f043bbc0cf102f7ea5e546ccd3
git push origin v4.4.1

Please sign in to comment.