Skip to content

Conversation

@hdunham
Copy link
Collaborator

@hdunham hdunham commented Feb 5, 2025

TODO:

  • bit more documentation
  • move test into runtests.jl
  • check on consistency of using grid_charge_efficiency vs rectifier_efficiency_fraction

Added

  • Add option to model DC-coupled PV and ElectricStorage
    • Add PV input dc_coupled_with_storage and ElectricStorage input dc_coupled, which both default to false
    • ElectricStorage inputs inverter_efficiency_fraction and rectifier_efficiency_fraction define the DC-coupled inverter and PV input inf_eff is ignored
    • Add sets in Techs struct (dc_coupled_with_storage and ac_coupled_with_storage) and StorageTypes struct (dc_coupled and ac_coupled)
    • Modify constraints to account for AC and DC coupling

Removed

  • Don't expose ElectricStorage fields charge_efficiency, discharge_efficiency, and grid_charge_efficiency. It can lead to unexpected behavior when users provide these instead of the inputs they are calculated from (inverter_efficiency_fraction, rectifier_efficiency_fraction, and internal_efficiency_fraction).

was subtracting prod from load wrong; shortened outage
@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps, ts in p.s.electric_utility.outage_time_steps],
m[:dvStoragePower]["ElectricStorage"] >= m[:dvMGDischargeFromStorage][s, tz, ts]
+ sum(m[:dvMGProductionToStorage][t, s, tz, ts] for t in p.techs.ac_coupled_with_storage)
+ sum(m[:dvMGProductionToStorage][t, s, tz, ts] for t in p.techs.dc_coupled_with_storage) * p.s.storage.attr["ElectricStorage"].inverter_efficiency_fraction #TODO: remove term or leave for future flexibility?
Copy link
Collaborator

Choose a reason for hiding this comment

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

If "ElectricStorage" in not in p.s.storage.types.dc_coupled, why would we need this here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is flow from dc coupled techs out inverter to ac coupled storage, which is currently is not allowed. With multiple storage and adding a bool input to say whether this flow is allowed, this term would be relevant, which is why I wrote that TODO.

# Dispatch to and from MG electrical storage is no greater than power capacity
if "ElectricStorage" in p.s.storage.types.dc_coupled
@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps, ts in p.s.electric_utility.outage_time_steps],
m[:dvStoragePower]["ElectricStorage"] >= m[:dvMGDischargeFromStorage][s, tz, ts] / p.s.storage.attr["ElectricStorage"].inverter_efficiency_fraction
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I'm confused about why the if statement is needed here, whereas in many of the other constraints you were able to avoid if statements by adding flows indexed on ac_coupled to flows index on dc_coupled. And, why does the discharge from storage need to be divided by inverter_efficiency_fraction for the dc-coupled case? Shouldn't the discharge from storage be treated the same between ac- and dc-coupled cases, because in both cases the discharge passes through an inverter (and isn't that already captured in dvMGDischargeFromStorage)?

I could be misunderstanding some of this, but just checking!

# Future development could make this an option by adding bool inputs an updating load balance/battery dispatch constraints to include these flows
if "ElectricStorage" in p.s.storage.types.dc_coupled
# Don't let AC coupled elec techs charge DC coupled battery.
for ts in 1:p.s.site.min_resil_time_steps
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, if you have a run with wind (AC production) and PV that is dc-coupled with storage, the wind cannot charge the battery? Do you have some test cases in this realm in runtests?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes that's the current set up. Could add an input bool to allow this though. Noting this as a test to add.

- m[:dvMGCurtail][t, s, tz, ts]
for t in p.techs.dc_coupled_with_storage
)
# (rectifier direction, though currently dvMGProductionToStorage for AC coupled techs must be 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is the constraint above the only thing limiting this? Is this typical operational behavior for a system with DC-coupled PV + BESS and an AC-producing tech like wind or CHP?

end

# Future development could make this an option by adding bool inputs an updating load balance/battery dispatch constraints to include these flows
if b in p.s.storage.types.dc_coupled
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment from me here as in "outage_constraints.jl"

@adfarth
Copy link
Collaborator

adfarth commented Apr 1, 2025

General comment on things to document:

  • AC-producing techs cannot currently charge a BESS that is DC coupled with PV. Maybe make a note on what dev would be needed to change this?
  • Notes in code on whether dv's are DC or AC units
  • Description of overall configuration as it compares to current config (PV and BESS have separate inverter systems)
  • Notes on how to consider modifying costs (lower PV costs, raise ElectricStorage costs when DC-Coupled)

r["size_kwh"] = round(value(m[Symbol("dvStorageEnergy"*_n)][b]), digits=2)
r["size_kw"] = round(value(m[Symbol("dvStoragePower"*_n)][b]), digits=2)

if b in p.s.storage.types.dc_coupled
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add dc_coupled_inverter_size_kw to the results markdown section above?

@@ -23,7 +23,10 @@ function add_electric_storage_results(m::JuMP.AbstractModel, p::REoptInputs, d::
r = Dict{String, Any}()
Copy link
Collaborator

@adfarth adfarth Nov 19, 2025

Choose a reason for hiding this comment

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

@hdunham is it accurate to say that the ElectricStorage size_kw (and dvStoragePower) is AC if dc_coupled is false and is DC if dc_coupled is true? Or is it always AC? Could you add whichever is true to the help text above?

@constraint(m, [b in p.s.storage.types.elec, ts in p.time_steps_without_grid],
m[Symbol("dvOpResFromBatt"*_n)][b,ts] <= m[Symbol("dvStoragePower"*_n)][b] - m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency
m[Symbol("dvOpResFromBatt"*_n)][b,ts] <=
m[Symbol("dvStoragePower"*_n)][b] * (b in p.s.storage.types.dc_coupled ? p.s.storage.attr[b].inverter_efficiency_fraction : 1.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just to confirm my understanding here, dvStoragePower is AC if dc_coupled is false but is DC otherwise and thus needs to be converted from DC to AC with inverter_efficiency_fraction?

m[:ProductionToLoadOR][t,ts]) * (1 - p.techs_operating_reserve_req_fraction[t])
m[Symbol("dvOpResFromTechs"*_n)][t,ts] <= (
p.production_factor[t, ts] * p.levelization_factor[t] * m[Symbol("dvSize"*_n)][t]
* (t in p.techs.dc_coupled_with_storage ? p.s.storage.attr["ElectricStorage"].rectifier_efficiency_fraction : 1.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should also be inverter_ and not rectifier_ efficiency?

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.

3 participants