Skip to content

Fix EnergyReservoirStorage units and ps/qs sign - #76

Open
luke-kiernan wants to merge 3 commits into
lk/transformer-rating-system-basefrom
lk/storage-system-base
Open

luke-kiernan wants to merge 3 commits into
lk/transformer-rating-system-basefrom
lk/storage-system-base

Conversation

@luke-kiernan

Copy link
Copy Markdown
Contributor

Stacked on #75; review that first. This PR's diff is the one commit on top of it.

Problem

make_storage! multiplied every system-per-unit storage field by the raw thermal_rating, which is itself still system per-unit, and used that same number as base_power. This was ported deliberately from PSCB's make_generic_battery for parity. On case5_strg.m, the only storage case in PowerSystemsTestData (PSB's matpower_case5_strg_sys), every storage power and energy value came out 100x too small: the file's 100 MW / 100 MWh batteries came out as 1 MVA / 1 MWh.

ps/qs had two more problems:

  • Never per-unitized. PowerModels documents ps/qs in MW/MVAr in mpc.storage and treats them as per-unit internally; for example, the power-mismatch calculation adds ps to pd/pg. But _make_per_unit! never rescaled them, here or upstream. That gap never showed because ps = qs = 0 in every case on hand.
  • Wrong sign. PowerModels defines ps/qs as power withdrawn (positive when charging). PSY's EnergyReservoirStorage.active_power/reactive_power are injections: PowerFlows.jl adds them to the bus injections, uses (0, output_active_power_limits.max) as the power-flow limits, and exports storage as a generator with PG = active_power.

Fix

  • _make_per_unit!/_make_mixed_units! now rescale storage ps/qs along with the other storage fields.
  • make_storage! converts every storage power and energy field with sys_mbase, sets base_power = sys_mbase, and negates ps/qs.
  • Tests: the case5_strg.m test now checks the natural values from the file. Before, it only compared rating with base_power, which hid the 100x error. The synthetic storage tests are rewritten to match, and there's a new round-trip test for the ps/qs per-unit conversion.

Compatibility

This changes output for every MATPOWER case with storage: on case5_strg, storage values grow 100x. There's no API change. PowerModelsData dicts now carry ps/qs in per-unit, like the other storage fields. The PSS/E path is unaffected, because it produces no storage.

Open question

base_power: should it be sys_mbase (this PR) or the battery's own natural thermal_rating (100 MVA on case5_strg)? PowerModels storage has no device base of its own. The natural-unit output is the same either way; only COMPONENT_BASE documents differ. thermal_rating can be 0, so using it would need a fallback.

🤖 Generated with Claude Code

luke-kiernan and others added 2 commits October 14, 2026 18:56
calculate_gen_rating already returns device-base pu; the extra
`rating * base_conversion` (inherited from PSCB) scaled the rating by
sys_mbase / mbase whenever MBASE != SBASE. The "larger than base power"
clamp compared that device-pu rating against mbase in MVA; it now compares
against 1.0 pu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
_make_per_unit! leaves storage energy/ratings/qmin/qmax in system-base pu,
but make_storage! (inherited from PSCB) multiplied them by the raw,
system-pu thermal_rating and used that as base_power too. On case5_strg.m a
100 MW / 100 MWh battery came out as 1 MVA / 1 MWh. Convert with sys_mbase,
which is also the base_power now.

_make_per_unit!/_make_mixed_units! never rescaled storage ps/qs, though
PowerModels documents them in MW/MVAr and treats them as pu internally
(e.g. the bus power-mismatch calculation). Rescale them with the other
storage fields. PowerModels defines ps/qs as power withdrawn, while PSY's
active_power/reactive_power are injections, so negate them.

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

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@luke-kiernan
luke-kiernan marked this pull request as ready for review October 15, 2026 14:53
@luke-kiernan
luke-kiernan requested review from hannahchubin and jd-lara and a balanced review from Copilot and removed request for Copilot and jd-lara October 15, 2026 16:33
@luke-kiernan

luke-kiernan commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

This changes output for every MATPOWER case with storage: on case5_strg, storage values grow 100x.

While this is a behavioral change, it's pretty unambiguous that this is an improvement. Right now, the values for the storage component's fields are in the 0.1-1 MW range; with this change, they're in the 10-100 MW range, which seems much more plausible.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Storage reactive bounds retain the wrong sign convention, and renewable rating clamping truncates valid capacity.

2 open findings
What changed in this PR

Corrects MATPOWER storage units and power signs in OpenAPI output, continuing the system-base conversion corrections from #75.

Changes:

  • Rescales storage ps/qs and converts storage quantities using the system base.
  • Removes duplicate renewable rating conversion and changes rating clamping.
  • Updates storage and renewable regression tests.
File Description
test/​test_parse_matpower.jl Adds storage ps/qs conversion round-trip coverage.
test/​test_openapi_generation.jl Updates storage values and renewable rating expectations.
src/​pm_io/​data.jl Rescales storage ps/qs between unit systems.
src/​openapi/​generation.jl Changes storage scaling, signs, and renewable ratings.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/openapi/generation.jl
Comment on lines +332 to +334
if rating > 1.0
@warn "rating is larger than base power for $gen_name, setting to $mbase"
rating = mbase
rating = 1.0

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.

I believe this is intentional and a pre-existing design thing

Comment thread src/openapi/generation.jl Outdated
@jd-lara
jd-lara requested review from annacasavant and removed request for jd-lara October 15, 2026 16:51
@luke-kiernan luke-kiernan self-assigned this Oct 15, 2026
qmin/qmax bound qs, which is withdrawal; the injection-sign reactive_power
needs bounds [-qmax, -qmin].

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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