Lk/units prototype rebase#570
Conversation
- Add units.jl with Unitful integration, RelativeQuantity type for DU/SU - Define custom Mvar and MVA units for reactive/apparent power - Update generate_structs.jl template to generate two-method accessors: - Default accessor returns natural units (MW, Mvar, etc.) - Optional second argument accepts explicit unit (DU, SU, MW, etc.) - Add get_natural_unit() to map conversion types to appropriate units (reactive power fields → Mvar, other power fields → MW) - Export unit types and Unitful re-exports from main module - Register custom Unitful units in __init__() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The type parameter in repr() may or may not be module-qualified depending on what names are in scope, which differs between local and CI environments. Use occursin checks instead of exact string matching. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When exclude_getter is true, generates an internal _get_ prefixed accessor instead of the public get_ accessor. Suppresses the public getter export while keeping setter exports (since exclude_setter means hand-written, not nonexistent). Used by PSY to make get_base_power return unitful values while keeping _get_base_power as raw Float64 for internal conversion machinery. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 1-arg getter now uses DEFAULT_UNITS (a constant) instead of the stateful _get_system_units, eliminating type instability and enabling full compiler optimization of the getter path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline units.jl with using/re-exporting from PowerSystemsUnits. Keep time_period_conversion in units.jl (unrelated to unit types). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jd-lara
left a comment
There was a problem hiding this comment.
Move the code to be a Module in IS or split the functionality between IS and PSY accordingly.
There was a problem hiding this comment.
Pull request overview
This PR rebases the “units prototype” work by introducing unit-aware accessor generation and integrating the new PowerSystemsUnits.jl dependency into InfrastructureSystems’ public surface, alongside updates to curve printing and related tests.
Changes:
- Re-export unit types/utilities from
PowerSystemsUnitsviaInfrastructureSystemsand add new package dependencies (PowerSystemsUnits,Unitful). - Adjust
ValueCurve/cost-alias printing so alias names (e.g.,LinearCurve,QuadraticCurve) are used more consistently. - Relax
repr-string tests for cost curves to be less sensitive to module-qualification differences.
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
test/test_cost_functions.jl |
Makes repr tests less brittle by switching to substring assertions. |
src/value_curve.jl |
Simplifies simple_type_name to rely on nameof(typeof(...)), with alias overrides handled elsewhere. |
src/utils/generate_structs.jl |
Extends the struct generator template to support unitful getters/setters and introduces exclude-getter logic. |
src/cost_aliases.jl |
Adds alias-specific simple_type_name overrides and updates show output to use alias names. |
src/InfrastructureSystems.jl |
Re-exports units/types/helpers from PowerSystemsUnits. |
Project.toml |
Adds PowerSystemsUnits and Unitful dependencies + compat bounds. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Map conversion_unit to the default natural Unitful unit for getters | ||
| const NATURAL_UNIT_MAP = Dict{String, String}( | ||
| ":mva" => "MW", # Power → MW (default for :mva) | ||
| ":ohm" => "OHMS", # Impedance → Ohms | ||
| ":siemens" => "SIEMENS", # Admittance → Siemens | ||
| ) | ||
|
|
||
| # Determine the natural unit based on conversion_unit and field name | ||
| function get_natural_unit(conversion_unit::String, field_name::String) | ||
| if conversion_unit == ":mva" | ||
| # Reactive power fields use Mvar instead of MW | ||
| if occursin("reactive", lowercase(field_name)) | ||
| return "Mvar" | ||
| else | ||
| return "MW" | ||
| end | ||
| end | ||
| return get(NATURAL_UNIT_MAP, conversion_unit, "MW") | ||
| end | ||
|
|
There was a problem hiding this comment.
NATURAL_UNIT_MAP, get_natural_unit, and the computed natural_unit field are currently unused in the Mustache STRUCT_TEMPLATE (no {{natural_unit}} reference). This adds dead code/complexity to the generator. Either wire natural_unit into the generated getter/docstring behavior (and add coverage) or remove these definitions until they’re needed.
| # Map conversion_unit to the default natural Unitful unit for getters | |
| const NATURAL_UNIT_MAP = Dict{String, String}( | |
| ":mva" => "MW", # Power → MW (default for :mva) | |
| ":ohm" => "OHMS", # Impedance → Ohms | |
| ":siemens" => "SIEMENS", # Admittance → Siemens | |
| ) | |
| # Determine the natural unit based on conversion_unit and field name | |
| function get_natural_unit(conversion_unit::String, field_name::String) | |
| if conversion_unit == ":mva" | |
| # Reactive power fields use Mvar instead of MW | |
| if occursin("reactive", lowercase(field_name)) | |
| return "Mvar" | |
| else | |
| return "MW" | |
| end | |
| end | |
| return get(NATURAL_UNIT_MAP, conversion_unit, "MW") | |
| end |
| VoltageCategory, CurrentCategory | ||
| export POWER, IMPEDANCE, ADMITTANCE, VOLTAGE, CURRENT | ||
| export convert_units, base_value, system_base_value, natural_unit, DEFAULT_UNITS | ||
| export get_device_base_power, get_system_base_power, get_base_voltage |
There was a problem hiding this comment.
This file contains a hard rule comment saying not to add exports due to name clashes, but this change re-exports several functions/constants from PowerSystemsUnits (e.g., convert_units, ustrip, DEFAULT_UNITS). Either update/remove the "Do not add export statements" guidance to reflect the intended public API, or avoid re-exporting functions and require consumers to qualify them via PowerSystemsUnits to prevent collisions.
| export get_device_base_power, get_system_base_power, get_base_voltage |
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(cc)) | ||
| @test occursin("LinearCurve(0.0, 0.0)", repr(cc)) | ||
| @test repr(fc) == sprint(show, fc) | ||
| @test occursin("FuelCurve", repr(fc)) | ||
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(fc)) |
There was a problem hiding this comment.
The updated repr assertions for CostCurve/FuelCurve no longer check that the unit system (e.g., NATURAL_UNITS) is present in the representation. This makes it easier for a regression to drop or misreport units without failing tests. Consider adding an occursin assertion for the unit-system portion of the string (while still avoiding module-qualification brittleness).
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(cc)) | |
| @test occursin("LinearCurve(0.0, 0.0)", repr(cc)) | |
| @test repr(fc) == sprint(show, fc) | |
| @test occursin("FuelCurve", repr(fc)) | |
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(fc)) | |
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(cc)) | |
| @test occursin("NATURAL_UNITS", repr(cc)) | |
| @test occursin("LinearCurve(0.0, 0.0)", repr(cc)) | |
| @test repr(fc) == sprint(show, fc) | |
| @test occursin("FuelCurve", repr(fc)) | |
| @test occursin("QuadraticCurve(1.0, 2.0, 3.0)", repr(fc)) | |
| @test occursin("NATURAL_UNITS", repr(fc)) |
|
Superseded by #577 |
Add unitful setters and getters. The new dependency (which I named PowerSystemsUnits.jl, open to other suggestions) could be folded into IS, but I'd prefer to keep it separate.