Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b4827d1
Merge remote-tracking branch 'origin/fixed-soc' into heuristic-dispat…
lixiangk1 May 30, 2026
55b3d1c
Add initial MPC implementation
lixiangk1 Jun 1, 2026
bfcef2a
Update MPC modeling
lixiangk1 Jun 11, 2026
3e695b3
Update MPC variable names
lixiangk1 Jun 12, 2026
40d32db
Remove print statement
lixiangk1 Jun 12, 2026
dd45a05
Skip MPC dispatch if battery is not optimally sized
lixiangk1 Jun 13, 2026
3d259af
Update Manifest.toml
adfarth Jun 24, 2026
855f189
validation and help text
adfarth Jun 29, 2026
e25fadd
rm solver_name from get_mpc_results inputs
adfarth Jun 29, 2026
7d4cfaa
condense input processing
adfarth Jun 29, 2026
9fb665d
rmv lat long validation
adfarth Jun 29, 2026
63968f0
Merge branch 'develop' into heuristic-dispatch-mpc
adfarth Jun 29, 2026
7518e36
get pv prod from REopt run too
adfarth Jun 29, 2026
9c255ef
set pv prod for final run
adfarth Jun 29, 2026
01c0140
update pv prod in final REopt run
adfarth Jun 29, 2026
569b894
Create 0118_merge_20260629_2347.py
adfarth Jun 29, 2026
13bbbed
add back in TODO
adfarth Jun 29, 2026
2fc5065
pv prod and solver
adfarth Jul 1, 2026
919d30c
utf-8
adfarth Jul 1, 2026
0a557b2
consistent response
adfarth Jul 1, 2026
0e2efc0
fix inputs processing
adfarth Jul 9, 2026
41a68ab
add inputs to models.py
adfarth Jul 9, 2026
e2eb18a
add bess export io
adfarth Jul 15, 2026
3f6d57e
Update custom_table_config.py
adfarth Jul 15, 2026
756df19
fix settings and pv processing
adfarth Jul 16, 2026
e470e9c
update todos
adfarth Jul 16, 2026
4151422
account for nem and whl
adfarth Jul 17, 2026
181a316
updt branch
adfarth Jul 17, 2026
ac1b149
update REopt
adfarth Jul 17, 2026
41468b1
update for demand rates
adfarth Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Classify the change according to the following categories:
##### Removed
### Patches

## heuristic-dispatch-option
### Minor udpates
#### Added
- Add **ElectricStorage** inputs field **dispatch_options** with heuristic options

## fixed-soc
### Minor udpates
#### Added
Expand Down
6 changes: 4 additions & 2 deletions julia_src/Manifest.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 88 additions & 0 deletions julia_src/http.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -64,13 +65,42 @@ 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"

@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

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"?

@adfarth adfarth Jul 16, 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.

@lixiangk1 to confirm if we can remove this todo

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

timeout_seconds = pop!(settings, "timeout_seconds")
optimality_tolerance = pop!(settings, "optimality_tolerance")
solver_attributes = SolverAttributes(timeout_seconds, optimality_tolerance)
Expand Down Expand Up @@ -810,6 +840,63 @@ 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)
Comment thread
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

solver_name = get(get(d, "Settings", Dict()), "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()

Expand All @@ -820,6 +907,7 @@ else
end
HTTP.register!(ROUTER, "POST", "/reopt", reopt)
HTTP.register!(ROUTER, "POST", "/erp", erp)
HTTP.register!(ROUTER, "POST", "/mpc", mpc)
Comment thread
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)
Expand Down
Loading
Loading