Skip to content

Add MPC as an option for BESS dispatch#721

Open
lixiangk1 wants to merge 20 commits into
developfrom
heuristic-dispatch-mpc
Open

Add MPC as an option for BESS dispatch#721
lixiangk1 wants to merge 20 commits into
developfrom
heuristic-dispatch-mpc

Conversation

@lixiangk1

@lixiangk1 lixiangk1 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Please check if the PR fulfills these requirements

  • CHANGELOG.md is updated
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Any new Django model inputs have also been added to job/test/posts/all_inputs_test.json

What kind of change does this PR introduce?

(Bug fix, feature, docs update, ...)

What is the current behavior?

(You can also link to an open issue here)

What is the new behavior (if this is a feature change)?

Does this PR introduce a breaking change?

(What changes might users need to make in their application due to this PR?)

Other information:

Things to test here / in REopt.jl:

  • Net metering
  • Net billing
  • Net metering and net billing combined
  • Multiple/probabilistic outage inputs with and without min_resil_timesteps
  • Scenarios that we think could return feasible on first run and infeasible on last (after MPC)

@lixiangk1 lixiangk1 requested a review from adfarth June 23, 2026 20:37
Comment thread julia_src/http.jl
Comment thread julia_src/http.jl Outdated
Comment thread julia_src/http.jl
# Then set ElectricStorage.fixed_soc_series_fraction = MPC SOC before running the main REopt optimization. If the user does not
# specify a fixed PV/battery size, optimally size the technologies using REopt (skip MPC dispatch if optimal battery size is 0).
electric_storage = get(d, "ElectricStorage", Dict())
if get(electric_storage, "dispatch_strategy", nothing) == "daily_foresight_optimized"

@adfarth adfarth Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Add checks to get_mpc_results:

  • Checks on what techs are allowed in mpc --> error if anything other than PV and BESS
  • Warning for tiered rates
  • Warning for outages
  • Error for emissions goals or RE goals?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Added some of these checks but leaving this open for the remaining TODO there

Comment thread julia_src/mpc.jl
Comment thread julia_src/mpc.jl
horizon = 24 * time_steps_per_hour
per_iter_timeout_s = 30.0

# TODO: Should MPC handle multiple PVs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would this require a lot of additional dev on the the REopt.jl side?

Comment thread julia_src/mpc.jl Outdated
pv_prod_factor = Float64.(pv["production_factor_series"])
else
# TODO: These production factors don't consider degradation, problem?
# Does MPCPV need a degradation input to calculate the levelization factor used in the optimization?

@adfarth adfarth Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

From a REoptInputs call you could get the levelization factor:

i = REoptInputs(post)
i.levelization_factor["PV"]

But note that the user input for prod_factor_series also wouldn't account for degradation factor (I think) so these should be treated the same whether user provided or obtained from the get_pv_prod function

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@lixiangk1 in REopt.jl mpc/, it looks like levelization_factor just always get set to 1 anyway. I see a TODO there from Nick about whether or not it's needed. I think maybe we just continue to assume levelization factor = 1 for MPC runs? OR we would need to adjust that in REopt.jl

Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl
"electric_curtailed_series_kw" => Float64[],
),
"ElectricStorage" => Dict(
"storage_to_load_series_kw" => Float64[],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

once we merge the storage export PR, will need to add storage_to_grid here and test

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See below comment about changes needed in REopt.jl mpc for allowing bess export and nem or whl

Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl
tou_previous_peak_demands, monthly_previous_peak_demands, soc_init_frac)
return Dict(
"PV" => Dict(
"size_kw" => pv_kw,

@adfarth adfarth Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

so can_net_meter, can_wholesale, can_export_beyond_nem_limit, can_curtail are not inputs?
Same with ElectricTariff export_rates and other nem and whl-related inputs?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@lixiangk1 after looking at the REopt.jl mpc inputs, it looks like:

  • MPCElectricTariff has inputs: net_metering and export_rates (so, currently NOT specific to each tech)
  • If both are provided, the documentation says the model will choose between them.
  • Compensation for export beyond NEM is not accounted for in MPC
  • If ONLY PV can export, this seems relatively straightforward to implement in the API code: set up the MPC inputs based on PV can_net_meter and can_wholesale, and use i.s.electric_tariff.export_rates to populate export_rates (check if this should be negative or positive for MPC)
  • BESS export introduces a complication: if PV can export and BESS can't, I don't think the REopt.jl mpc is set up to handle this currently; I see some TODOs in the code there about not assuming all techs have the same NEM schema, and I think we'd have to implement this if introducing BESS export to mpc.

@adfarth

adfarth commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Generally curious if running hourly is in line with available versions of this capability in BESS dispatch software, or if less frequent should be considered too?

Comment thread julia_src/http.jl
Comment thread julia_src/http.jl
try
@info "Running MPC to obtain daily foresight optimized battery dispatch profile."
mpc_results = get_mpc_results(d; solver_name=solver_name)
# TODO: Cache sizing run results and avoid a second call to REopt? Are those the same results?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@lixiangk1 could you explain what you mean by "are those the same results"?

Comment thread julia_src/mpc.jl
Comment thread julia_src/http.jl
end
end

#TODO: What timeout and optimality tolerance should MPC use?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this TODO referring to the final REopt run when using MPC? If so, I think this could just use the same as what's used now for a regular REopt run?

@adfarth adfarth requested a review from Copilot June 30, 2026 22:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a Model Predictive Control (MPC) based dispatch option for BESS by introducing a Julia MPC implementation and exposing it via the API, plus wiring it into the existing /reopt flow when a new dispatch strategy is selected.

Changes:

  • Added a new rolling-horizon MPC implementation in Julia (mpc.jl) for PV + ElectricStorage dispatch.
  • Integrated MPC into the /reopt endpoint via ElectricStorage.dispatch_strategy == "daily_foresight_optimized" and added a dedicated /mpc endpoint.
  • Updated Django migrations and Julia manifest to align dependency/migration state.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
reoptjl/migrations/0118_merge_20260629_2347.py Adds a Django merge migration to reconcile migration graph.
reoptjl/migrations/0117_merge_20260530_1640.py Adds a Django merge migration to reconcile migration graph.
julia_src/mpc.jl New MPC rolling-horizon dispatch implementation and sizing pre-step.
julia_src/http.jl Wires MPC into /reopt, adds /mpc endpoint, includes mpc.jl.
julia_src/Manifest.toml Points REopt.jl dependency to a specific git source/rev.

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

Comment thread julia_src/mpc.jl
Comment thread julia_src/mpc.jl
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/mpc.jl
Comment thread julia_src/mpc.jl
Comment on lines +219 to +223
# TODO: MPC horizons and timeout are currently hard coded
time_steps_per_hour = Int(get(settings, "time_steps_per_hour", 1))
length_of_data = 8760 * time_steps_per_hour
horizon = 24 * time_steps_per_hour
per_iter_timeout_s = 30.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@lixiangk1 something to consider

Comment thread julia_src/mpc.jl Outdated
Comment thread julia_src/http.jl Outdated
Comment thread julia_src/mpc.jl
Comment thread julia_src/Manifest.toml
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