-
Notifications
You must be signed in to change notification settings - Fork 67
Add MPC as an option for BESS dispatch #721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
b4827d1
55b3d1c
bfcef2a
3e695b3
40d32db
dd45a05
3d259af
855f189
e25fadd
7d4cfaa
9fb665d
63968f0
7518e36
9c255ef
01c0140
569b894
13bbbed
2fc5065
919d30c
0a557b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ DotEnv.load!() | |
| const test_nrel_developer_api_key = ENV["NREL_DEVELOPER_API_KEY"] | ||
|
|
||
| ENV["NREL_DEVELOPER_EMAIL"] = "reopt@nlr.gov" | ||
| include("mpc.jl") | ||
|
|
||
| include("os_solvers.jl") | ||
|
|
||
|
|
@@ -64,13 +65,43 @@ function reopt(req::HTTP.Request) | |
| ENV["NREL_DEVELOPER_API_KEY"] = test_nrel_developer_api_key | ||
| delete!(d, "api_key") | ||
| end | ||
|
|
||
| settings = d["Settings"] | ||
| solver_name = get(settings, "solver_name", "HiGHS") | ||
| if solver_name == "Xpress" && !(xpress_installed=="True") | ||
| solver_name = "HiGHS" | ||
| @warn "Changing solver_name from Xpress to $solver_name because Xpress is not installed. Next time | ||
| Specify Settings.solver_name = 'HiGHS' or 'Cbc' or 'SCIP'" | ||
| end | ||
|
|
||
| # ---- API-only battery heuristic dispatch strategy: "daily_foresight_optimized" ---- | ||
| # When ElectricStorage.dispatch_strategy == "daily_foresight_optimized", if needed, first run REopt to get optimal sizing of PV and battery. | ||
| # Then run the MPC rolling-horizon loop to get a SOC profile (skip MPC dispatch if optimal battery size is 0). | ||
| # Then set ElectricStorage.fixed_soc_series_fraction = MPC SOC and fix PV and BESS sizing before running the main REopt optimization. | ||
| electric_storage = get(d, "ElectricStorage", Dict()) | ||
| if get(electric_storage, "dispatch_strategy", nothing) == "daily_foresight_optimized" | ||
| 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? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"? |
||
| if get(mpc_results, "skip_mpc", false) == true | ||
| @info "Cannot execute daily_foresight_optimized battery dispatch because optimal battery size is 0. Setting dispatch strategy to 'optimized'." | ||
| d["ElectricStorage"]["dispatch_strategy"] = "optimized" | ||
| else | ||
| soc = mpc_results["ElectricStorage"]["soc_series_fraction"] | ||
| d["ElectricStorage"]["fixed_soc_series_fraction"] = soc | ||
| d["ElectricStorage"]["dispatch_strategy"] = "custom_soc" | ||
| end | ||
| catch e | ||
| @error "MPC pre-solve failed" exception=(e, catch_backtrace()) | ||
| return HTTP.Response(500, JSON.json(Dict( | ||
| "error" => "MPC pre-solve failed: " * sprint(showerror, e), | ||
| "reopt_version" => string(pkgversion(reoptjl)), | ||
| ))) | ||
| end | ||
| end | ||
|
|
||
| #TODO: What timeout and optimality tolerance should MPC use? | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| timeout_seconds = pop!(settings, "timeout_seconds") | ||
| optimality_tolerance = pop!(settings, "optimality_tolerance") | ||
| solver_attributes = SolverAttributes(timeout_seconds, optimality_tolerance) | ||
|
|
@@ -810,6 +841,64 @@ function job_no_xpress(req::HTTP.Request) | |
| return HTTP.Response(500, JSON.json(error_response)) | ||
| end | ||
|
|
||
| """ | ||
| mpc(req::HTTP.Request) | ||
|
|
||
| HTTP endpoint for rolling-horizon Model Predictive Control (MPC) dispatch optimization. | ||
|
|
||
| This endpoint performs a full-year rolling-horizon MPC dispatch for PV + ElectricStorage systems, | ||
| optimizing daily dispatch using a 24-hour look-ahead window. Runs the MPC dispatch loop via | ||
| `get_mpc_results!` and returns dispatch results and cost metrics as JSON. | ||
|
|
||
| Arguments: | ||
| req::HTTP.Request: REopt inputs dictionary | ||
|
|
||
| Returns JSON dictionary containing: | ||
| - MPC: Metadata (time_steps_per_hour, horizon_time_steps) | ||
| - PV: Size and dispatch series (to load, storage, grid, curtailed) | ||
| - ElectricStorage: Sizes and state-of-charge series | ||
| - ElectricUtility: Grid dispatch series and emissions | ||
| - ElectricLoad: Load profile used | ||
| - ElectricTariff: Energy and demand costs, peak demands by month/ratchet | ||
| - status: "optimal" | ||
| - reopt_version: Version of REopt.jl used | ||
|
|
||
| """ | ||
| function mpc(req::HTTP.Request) | ||
|
adfarth marked this conversation as resolved.
|
||
| d = JSON.parse(String(req.body)) | ||
| error_response = Dict() | ||
| results = Dict() | ||
| try | ||
| if !isempty(get(d, "api_key", "")) | ||
| ENV["NREL_DEVELOPER_API_KEY"] = pop!(d, "api_key") | ||
| else | ||
| ENV["NREL_DEVELOPER_API_KEY"] = test_nrel_developer_api_key | ||
| delete!(d, "api_key") | ||
| end | ||
|
|
||
| settings = d["Settings"] | ||
| solver_name = get(settings, "solver_name", "HiGHS") | ||
| if solver_name == "Xpress" && !(xpress_installed=="True") | ||
| solver_name = "HiGHS" | ||
| @warn "Changing solver_name from Xpress to $solver_name because Xpress is not installed. Next time | ||
| Specify Settings.solver_name = 'HiGHS' or 'Cbc' or 'SCIP'" | ||
| end | ||
|
|
||
| results = get_mpc_results!(d; solver_name=solver_name) | ||
| catch e | ||
| @error "MPC failed" exception=(e, catch_backtrace()) | ||
| error_response["error"] = sprint(showerror, e) | ||
| error_response["reopt_version"] = string(pkgversion(reoptjl)) | ||
| end | ||
| GC.gc() | ||
| if isempty(error_response) | ||
| @info "MPC ran successfully." | ||
| return HTTP.Response(200, JSON.json(results)) | ||
| else | ||
| return HTTP.Response(500, JSON.json(error_response)) | ||
| end | ||
| end | ||
|
|
||
| # define REST endpoints to dispatch to "service" functions | ||
| const ROUTER = HTTP.Router() | ||
|
|
||
|
|
@@ -820,6 +909,7 @@ else | |
| end | ||
| HTTP.register!(ROUTER, "POST", "/reopt", reopt) | ||
| HTTP.register!(ROUTER, "POST", "/erp", erp) | ||
| HTTP.register!(ROUTER, "POST", "/mpc", mpc) | ||
|
adfarth marked this conversation as resolved.
|
||
| HTTP.register!(ROUTER, "POST", "/ghpghx", ghpghx) | ||
| HTTP.register!(ROUTER, "GET", "/chp_defaults", chp_defaults) | ||
| HTTP.register!(ROUTER, "GET", "/avert_emissions_profile", avert_emissions_profile) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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