Skip to content

Lk/units domain agnostic is4#577

Open
luke-kiernan wants to merge 16 commits intoIS4from
lk/units-domain-agnostic-is4
Open

Lk/units domain agnostic is4#577
luke-kiernan wants to merge 16 commits intoIS4from
lk/units-domain-agnostic-is4

Conversation

@luke-kiernan
Copy link
Copy Markdown
Contributor

Continuation of #574. I need to add the power_units field as a type parameter on the CostCurve, but then this will be ready to merge.

luke-kiernan and others added 11 commits April 21, 2026 12:41
- 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>
- Purge power-domain knowledge from generate_structs template
  (NATURAL_UNIT_MAP / get_natural_unit removed); template emits
  untyped `units` parameter for downstream packages to specialize.
- Declare bare `get_value` / `set_value` interface; methods are
  now provided by domain packages (e.g. PowerSystems).
- Drop PowerSystemsUnits and Unitful deps so IS has no knowledge
  of power-system-specific unit types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New relative_units.jl vendors AbstractRelativeUnit, DU/SU/NU, and
  RelativeQuantity; power-specific types stay in PSY.
- Template drops the 1-arg getter emission — callers must specify units.
  Emits a `display_units_arg(f, ::Type{T})` trait so consumers can dispatch
  on whether a (getter, struct) pair takes units.
- `_make_time_array` now calls `multiplier(owner, SU)` so
  `scaling_factor_multiplier = get_max_active_power` keeps working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the existing exclude_setter behavior so the public name is registered
for export even when the generator emits a _-prefixed internal accessor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 67.93893% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.75%. Comparing base (94161b0) to head (414048d).
⚠️ Report is 39 commits behind head on IS4.

Files with missing lines Patch % Lines
src/cost_aliases.jl 20.00% 16 Missing ⚠️
src/production_variable_cost_curve.jl 83.33% 9 Missing ⚠️
src/relative_units.jl 81.57% 7 Missing ⚠️
src/utils/print_pt_v2.jl 0.00% 4 Missing ⚠️
src/utils/print_pt_v3.jl 0.00% 4 Missing ⚠️
src/utils/generate_structs.jl 66.66% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##              IS4     #577      +/-   ##
==========================================
- Coverage   82.04%   81.75%   -0.29%     
==========================================
  Files          74       75       +1     
  Lines        6238     6391     +153     
==========================================
+ Hits         5118     5225     +107     
- Misses       1120     1166      +46     
Flag Coverage Δ
unittests 81.75% <67.93%> (-0.29%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/InfrastructureSystems.jl 44.44% <ø> (ø)
src/time_series_interface.jl 96.24% <100.00%> (ø)
src/utils/test.jl 71.87% <100.00%> (-1.46%) ⬇️
src/value_curve.jl 94.84% <100.00%> (+0.96%) ⬆️
src/utils/generate_structs.jl 86.88% <66.66%> (-1.15%) ⬇️
src/utils/print_pt_v2.jl 0.00% <0.00%> (ø)
src/utils/print_pt_v3.jl 88.65% <0.00%> (-1.93%) ⬇️
src/relative_units.jl 81.57% <81.57%> (ø)
src/production_variable_cost_curve.jl 79.34% <83.33%> (-0.66%) ⬇️
src/cost_aliases.jl 50.00% <20.00%> (-7.98%) ⬇️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

luke-kiernan and others added 5 commits April 24, 2026 14:50
Add simple_type_name overrides for the five TimeSeries*Curve aliases and
use them in Base.show instead of typeof(vc), so output is e.g.
TimeSeriesLinearCurve(...) rather than the full parametric
TimeSeriesInputOutputCurve{TimeSeriesFunctionData{LinearFunctionData}}(...).
Brings these aliases to parity with the non-TS aliases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add U <: AbstractUnitSystem as a second type parameter on
ProductionVariableCostCurve, replacing the runtime power_units field.
get_power_units returns U(). Eliminates Val-wrapping in downstream
unit-dependent dispatch.

Introduces AbstractUnitSystem as a common supertype of
AbstractRelativeUnit and NaturalUnit. Constructors default
U = NaturalUnit; serialize/deserialize encode U under "power_units".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AnyCostCurve{T}, AnyFuelCurve{T}, AnyProductionVariableCostCurve{T}:
  existential UnionAll aliases for downstream code at isa sites or
  method signatures where the U parameter is irrelevant.
- _unit_system_instance now accepts both new type-name strings
  ("NaturalUnit") and legacy enum-value strings ("NATURAL_UNITS") so
  existing serialized fixtures keep deserializing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cost coefficients convert as the inverse of the power-value ratio
(squared for quadratic terms). Bases pass in as Float64 args so IS
needs no component interface stubs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers identity, all 6 cross-system conversions, exponent for quadratic,
round-trip identity, and the negative-exponent (power-value-ratio) case
used by IOM piecewise x-coord rescaling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@luke-kiernan luke-kiernan marked this pull request as ready for review April 27, 2026 20:31
@jd-lara jd-lara requested review from Copilot and daniel-thom April 27, 2026 20:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR continues the “domain-agnostic units” work by introducing relative unit-system markers (DU/SU/NU), updating printing/display paths to support unit-aware getters, and refactoring production-variable cost curves to encode power_units at the type level (with corresponding serialization + test updates).

Changes:

  • Add domain-agnostic unit markers (DeviceBaseUnit, SystemBaseUnit, NaturalUnit) plus RelativeQuantity arithmetic/helpers.
  • Refactor CostCurve/FuelCurve (and their supertype) to carry power_units as a type parameter; update show/serialization accordingly.
  • Update PrettyTables component display and time-series scaling-factor application to optionally pass a units marker to getters/multipliers; adjust tests for new unit markers and printing.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/test_time_series_function_data.jl Updates CostCurve construction/assertions to use new unit-marker instances.
test/test_relative_units.jl Adds coverage for RelativeQuantity construction, arithmetic, comparisons, display, and cost coefficient conversion.
test/test_make_convex.jl Updates unit expectations for convexification results to new unit markers.
test/test_cost_functions.jl Makes printing tests resilient to qualification changes and updates expected unit-marker formatting/order.
src/value_curve.jl Simplifies simple_type_name and relies on alias-specific overrides for display names.
src/utils/test.jl Adds 2-arg getter overloads so test scaling multipliers can accept a units marker.
src/utils/print_pt_v3.jl Passes an optional units argument to getters based on display_units_arg.
src/utils/print_pt_v2.jl Same as v3, for PrettyTables v2 compatibility.
src/utils/generate_structs.jl Updates generator template to emit unit-aware 2-arg getters when conversion is needed and adds display_units_arg trait methods.
src/time_series_interface.jl Changes scaling-factor application to call multipliers with a units marker (SU).
src/relative_units.jl Introduces unit marker types, RelativeQuantity, convert_cost_coefficient, and the display_units_arg trait default.
src/production_variable_cost_curve.jl Refactors cost curves to carry power_units as a type parameter; adds (de)serialization logic and updated show methods.
src/cost_aliases.jl Restores/relocates is_cost_alias default and adds simple_type_name overrides for alias printing consistency.
src/InfrastructureSystems.jl Declares/exports the unit conversion interface hooks (get_value, set_value) and includes relative_units.jl.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +953 to +956
# Scaling-factor multipliers (e.g. `get_max_active_power`) are unit-aware
# accessors from downstream packages; pass `SU` so the result is in the
# system base that consumers of the time series expect.
return ta .* multiplier(owner, SU)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

_make_time_array now always calls multiplier(owner, SU), but the public API/docs for scaling_factor_multiplier (and existing callers) treat it as a 1-arg function multiplier(owner). This will throw a MethodError for any multiplier that hasn’t been updated to accept a units marker. Consider keeping backward compatibility by calling the 2-arg form only when applicable (or try/catch MethodError and fall back to 1-arg), and update the docstrings accordingly so the required signature is unambiguous.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For plotting or printing, people like to see natural units. Add additional argument?

elseif name in ("DEVICE_BASE", "DeviceBaseUnit")
return DeviceBaseUnit()
end
T = getproperty(@__MODULE__, Symbol(name))
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

_unit_system_instance uses getproperty(@__MODULE__, Symbol(name)) for non-legacy strings. If name is unknown, this will throw a low-level UndefVarError/type has no field rather than a clear ArgumentError explaining valid unit markers. Consider checking isdefined(@__MODULE__, Symbol(name)) (or wrapping in try/catch) and throwing an ArgumentError with the supported values to improve debuggability and error reporting for corrupted/hand-edited serialized data.

Suggested change
T = getproperty(@__MODULE__, Symbol(name))
sym = Symbol(name)
if !isdefined(@__MODULE__, sym)
throw(
ArgumentError(
"Unsupported power_units value \"$name\". Supported values include " *
"\"NATURAL_UNITS\", \"SYSTEM_BASE\", \"DEVICE_BASE\", " *
"\"NaturalUnit\", \"SystemBaseUnit\", and \"DeviceBaseUnit\".",
),
)
end
T = getproperty(@__MODULE__, sym)

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +23
@test_throws Exception 0.6 * IS.DU + 0.4 * IS.SU
@test_throws Exception 0.6 * IS.DU == 0.4 * IS.SU
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

These assertions expect a very broad Exception. Since DU/SU mixing currently fails due to missing methods, it should deterministically raise a MethodError. Tightening @test_throws to MethodError makes the test more precise and avoids accidentally passing on unrelated failures.

Suggested change
@test_throws Exception 0.6 * IS.DU + 0.4 * IS.SU
@test_throws Exception 0.6 * IS.DU == 0.4 * IS.SU
@test_throws MethodError 0.6 * IS.DU + 0.4 * IS.SU
@test_throws MethodError 0.6 * IS.DU == 0.4 * IS.SU

Copilot uses AI. Check for mistakes.
Comment thread src/relative_units.jl
@@ -0,0 +1,165 @@
###############################
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Move contents of this file to a submodule.

I'm also slightly bothered by the fact that you can do 1 SU (relative unit) but not 1 NU (dispatch flag only).

Comment on lines +953 to +956
# Scaling-factor multipliers (e.g. `get_max_active_power`) are unit-aware
# accessors from downstream packages; pass `SU` so the result is in the
# system base that consumers of the time series expect.
return ta .* multiplier(owner, SU)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For plotting or printing, people like to see natural units. Add additional argument?

`power_units` at construction is the singleton instance `U()` (default `NaturalUnit()`).
"""
@kwdef struct FuelCurve{T <: ValueCurve} <: ProductionVariableCostCurve{T}
struct FuelCurve{T <: ValueCurve, U <: AbstractUnitSystem} <:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Need units on the y axis too: might be gallons, MMBTU, Joules...

@luke-kiernan
Copy link
Copy Markdown
Contributor Author

Also delete print version 2. pending on openAPI/schema integration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants