Skip to content
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

multiple outage modeling updates #147

Merged
merged 51 commits into from
Feb 2, 2023
Merged

multiple outage modeling updates #147

merged 51 commits into from
Feb 2, 2023

Conversation

hdunham
Copy link
Collaborator

@hdunham hdunham commented Nov 4, 2022

No description provided.

@hdunham hdunham changed the title outage modeling patches multiple outage modeling updates Nov 4, 2022
@hdunham hdunham marked this pull request as ready for review November 21, 2022 21:13
Copy link
Collaborator

@zolanaj zolanaj left a comment

Choose a reason for hiding this comment

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

Thanks @hdunham for this. I have a couple bigger-picture questions which have less to do with your edits and more to do with how our outage simulator is structured. I think it might warrant a discussion unless I'm missing a key piece of how things work. I'll separately email a CSM manuscript that structures multiple outages in the REopt formulation but is tailored to optimizing design with outages included and doesn't perform the out-of-optimization analyses that you get from the outage simulator.

# Incentive to minimize unserved load in each outage, not just the max over outage start times
add_to_expression!(
Objective,
sum(sum(0.0001 * m[:dvUnservedLoad][s, tz, ts] for ts in 1:p.s.electric_utility.outage_durations[s]) for s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_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.

Instead of a fixed 0.001 multiple (which I think is $1/MWh here), would it make sense to use the value of lost load here? Or is this an epsilon-like value that avoids things like not discharging the battery during shorter outages so it can be used to avoid a high demand charge after the outage? If it's the latter, I'm not sure this is guaranteed to prevent this outcome (i.e., a high demand charge could still cause the undesired outcome unless the multiple is tuned well to the problem).

I think we probably want two options:

  1. minimize the lost load charge in the worst case - which uses the existing objectives, a more robust approach but might yield weird instances like the one we're trying to avoid, and
  2. minimize the expected lost load charge, which penalizes lost load in every scenario. This method would not require the robustness constraints and would add probability-weighted loss terms to the objective function.

Or is there something I'm missing in either the project outcomes (or code)?

Copy link
Collaborator Author

@hdunham hdunham Dec 28, 2022

Choose a reason for hiding this comment

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

We don't want to use the value of lost load here because that is already included in the objective (see src/core/reopt.jl line 414 and src/constraints/outage_constraints.jl). Across given outage start time steps, only the worst case outage cost is added to the objective (i.e. we are already doing your option 1). That means that in the non-worst outage scenarios, the model has no disincentive to drop load up to equal cost to the worst outage. This wouldn't matter except that the results for these non-worst outage scenarios are also reported. These results may not show the minimum cost achievable for those non-worst outage scenarios and can even vary run-to-run for the same inputs. I added the incentive here to address that. So, in the sense that 0.001 is an arbitrary tiny value to incentive a certain behavior in the solution, this is more like the latter case you described, just not for the specific purpose you described. The value is meant to be negligible relative to any other incentives in the optimization, only differentiating between otherwise equal cost solutions. It just needs to be large enough to be significant considering optimality tolerance settings.
Side note, we use expected values calculated over outage duration. The user can provide multiple outage durations and their respective probabilities. So the outage cost added to the objective is actually an expected value in one dimension (duration) and a max (so robust optimization) in the other dimension (start time).
Does that clear things up? Do you still think something should change?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see now - for each outage duration we pick the worst possible start time, and then perform a probability-weighted sum of those outages to calculate expected outage cost. This makes sense, thanks a bunch for clarifying.

Comment on lines 30 to +33
function add_dv_UnservedLoad_constraints(m,p)
# effective load balance (with slack in dvUnservedLoad)
@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[:dvUnservedLoad][s, tz, ts] >= p.s.electric_load.critical_loads_kw[tz+ts]
- sum( m[:dvMGRatedProduction][t, s, tz, ts] * p.production_factor[t, tz+ts] * p.levelization_factor[t]
m[:dvUnservedLoad][s, tz, ts] >= p.s.electric_load.critical_loads_kw[tz+ts-1]
Copy link
Collaborator

Choose a reason for hiding this comment

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

This isn't directly related to the edit here but rather the sets over which the constraint is indexed. My understanding of these sets is as follows:

  • p.s.electric_utility.scenarios may consist of one or more outages
  • p.s.electric_utility.outage_start_time_steps may have one or more start times, but the starting time steps are the same for every scenario
  • p.s.electric_utility.outage_durations[s] determines the length of every outage in a scenario s - that is, in each scenario, all outages have the same length

If I'm reading the above correctly, then the constraint here shouldn't be indexed on p.s.electric_utility.outage_time_steps, since the number of time steps could change from scenario to scenario. We would want a condition on the sets in line 32, something like ts in tz:tz+p.s.electric_utility.outage_durations[s]-1 (though, since tz is an index, I'm not sure you can do this directly in the set declaration in line 32).

This collection of sets appears in a few other places elsewhere, and I can go find them if you agree with this. But another question is if I'm correct about identical outage length, is there a reason we're including multiple outages of identical length in the same year-long instance? Or is the actual instance built according to the tuple (s,tz) in which only one outage is present? (I'm thinking a set of outages, indexed on o, might be suitable for a multi-outage instance of REopt.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Your description of the three sets is correct. The number of time steps does change from scenario to scenario, and p.s.electric_utility.outage_time_steps goes up to the longest outage duration (see src/core/electric_utility.jl). Instead of what you're suggesting, what we currently do is handle this when summing up outage costs. Looking at src/constraints/outage_constraints.jl, for each s in p.s.electric_utility.scenarios, only time steps up to the appropriate duration p.s.electric_utility.outage_durations[s] are included in the cost sum. So load can be unserved in those uncounted time steps with no cost. I'm not arguing that this approach is the best way, but I believe it does achieve the intended result. It would be interesting to test out if an implementation like what you're describing improves solve times.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, this makes sense, and after testing a couple different ways I agree this does what's intended. I added new issue #170 as a note to revisit this.

@Bill-Becker
Copy link
Collaborator

@hdunham I know the API never had the option to specify that the outage start time is at the peak load (a current webtool option), but since we are changing the webtool to use default outage start times to make the outage centered on the peak load of each of the four seasons, I was thinking we should at least have an easy option for the REopt.jl/API users to do this. Maybe a new input, boolean or string for different options (like starting on peak versus centered on peak, or seasonal peak versus yearly peak), which would enable that. This would also enable the webtool to use the same function with an endpoint (job.views function, calling a REopt.jl function in http.jl) to get the start time step. Thoughts?

@hdunham hdunham removed the request for review from rathod-b December 14, 2022 17:12
@hdunham
Copy link
Collaborator Author

hdunham commented Dec 28, 2022

@hdunham I know the API never had the option to specify that the outage start time is at the peak load (a current webtool option), but since we are changing the webtool to use default outage start times to make the outage centered on the peak load of each of the four seasons, I was thinking we should at least have an easy option for the REopt.jl/API users to do this. Maybe a new input, boolean or string for different options (like starting on peak versus centered on peak, or seasonal peak versus yearly peak), which would enable that. This would also enable the webtool to use the same function with an endpoint (job.views function, calling a REopt.jl function in http.jl) to get the start time step. Thoughts?

I added two issues for this and aim to include in the next PR updating outage modeling.
#164
NREL/REopt_API#395

@zolanaj zolanaj self-requested a review January 19, 2023 21:50
Copy link
Collaborator

@zolanaj zolanaj left a comment

Choose a reason for hiding this comment

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

@hdunham thanks a bunch for your responses and for adding issues in response to the notes from @Bill-Becker. I added a bit to the outages test and a check for outage_probabilities == 1 when it's not an empty list since I noticed during testing that an error isn't thrown when that happens. I also just merged develop into this branch. If you agree with the changes and tests pass, I think this can be merged.

@Bill-Becker Bill-Becker removed their request for review January 26, 2023 21:53
@hdunham hdunham merged commit 95e74b0 into develop Feb 2, 2023
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.

None yet

3 participants