Skip to content

Commit 6b2f58e

Browse files
authored
Merge pull request #50 from lanl-ansi/bugfix_49
Fix #50 and clean up code.
2 parents 617ef5c + f513df9 commit 6b2f58e

20 files changed

+464
-299
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Staged
44
- Fixed bug in iterative restoration solution generation (#44)
5+
- Add const for supported components for repairs `restoration_comps`
6+
- Update `get_repairable_items`, `get_damaged_items`, `clear_damage_indicator` to use `restoration_comps`
57

68
## v0.5.0
79
- Update to new function name convention of PowerModels v0.17 details in PR #40 (breaking)

Project.toml

-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ version = "0.5.0"
66
[deps]
77
InfrastructureModels = "2030c09a-7f63-5d83-885d-db604e0e9cc0"
88
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
9-
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
109
Memento = "f28f55f0-a522-5efc-85c2-fe41dfb9b2d9"
1110
PowerModels = "c36e90e8-916a-50a6-bd94-075b64ef4655"
1211

1312
[compat]
1413
InfrastructureModels = "~0.5"
1514
JuMP = "~0.19.1, ~0.20, ~0.21"
16-
MathOptInterface = "~0.8, ~0.9"
1715
Memento = "~0.10, ~0.11, ~0.12, ~0.13, ~1.0, ~1.1"
1816
PowerModels = "~0.17"
1917
julia = "^1"

src/core/data.jl

+23-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
2+
"supported components for restoration"
3+
const restoration_comps = ["bus" "gen" "storage" "branch"]
4+
5+
6+
""
17
function count_repairable_items(network::Dict{String, Any})
28
if _IM.ismultinetwork(network)
39
repairable_count = Dict{String,Any}("nw" => Dict{String,Any}())
410
repairable_set = get_repairable_items(network)
5-
repairable_count["nw"] = Dict{String,Any}(nw => sum(length(comp_ids) for (comp_type,comp_ids) in repariable_network) for (nw, repariable_network) in repairable_set["nw"] )
11+
repairable_count["nw"] = Dict{String,Any}(nw => sum(length(comp_ids) for (comp_name,comp_ids) in repariable_network) for (nw, repariable_network) in repairable_set["nw"] )
612
else
713
repairable_set = get_repairable_items(network)
8-
return repairable_count = sum(length(comp_ids) for (comp_type,comp_ids) in repairable_set)
14+
return repairable_count = sum(length(comp_ids) for (comp_name,comp_ids) in repairable_set)
915
end
1016
return repairable_count
1117
end
@@ -28,7 +34,8 @@ end
2834
""
2935
function _get_repairable_items(network::Dict{String,Any})
3036
repairs = Dict{String, Vector{String}}()
31-
for (comp_name, status_key) in _PM.pm_component_status
37+
for comp_name in restoration_comps
38+
status_key = _PM.pm_component_status[comp_name]
3239
repairs[comp_name] = []
3340
for (comp_id, comp) in get(network, comp_name, Dict())
3441
if haskey(comp, status_key) && comp[status_key] != _PM.pm_component_status_inactive[comp_name] && haskey(comp, "damaged") && comp["damaged"] == 1
@@ -61,10 +68,10 @@ function count_damaged_items(network::Dict{String, Any})
6168
if _IM.ismultinetwork(network)
6269
damaged_count = Dict{String,Any}("nw" => Dict{String,Any}())
6370
damaged_set = get_damaged_items(network)
64-
damaged_count["nw"] = Dict{String,Any}(nw => sum(length(comp_ids) for (comp_type,comp_ids) in damage_network) for (nw, damage_network) in damaged_set["nw"] )
71+
damaged_count["nw"] = Dict{String,Any}(nw => sum(length(comp_ids) for (comp_name,comp_ids) in damage_network) for (nw, damage_network) in damaged_set["nw"] )
6572
else
6673
damaged_set = get_damaged_items(network)
67-
damaged_count = sum(length(comp_ids) for (comp_type,comp_ids) in damaged_set)
74+
damaged_count = sum(length(comp_ids) for (comp_name,comp_ids) in damaged_set)
6875
end
6976
return damaged_count
7077
end
@@ -87,11 +94,12 @@ end
8794
""
8895
function _get_damaged_items(network::Dict{String,Any})
8996
comp_list = Dict{String, Array{String,1}}()
90-
for (comp_type, comp_status) in _PM.pm_component_status
91-
comp_list[comp_type] = []
92-
for (comp_id, comp) in network[comp_type]
97+
for comp_name in restoration_comps
98+
status_key = _PM.pm_component_status[comp_name]
99+
comp_list[comp_name] = []
100+
for (comp_id, comp) in network[comp_name]
93101
if haskey(comp, "damaged") && comp["damaged"] == 1
94-
push!(comp_list[comp_type], comp_id)
102+
push!(comp_list[comp_name], comp_id)
95103
end
96104
end
97105
end
@@ -149,9 +157,9 @@ end
149157

150158
""
151159
function _set_component_inactive!(network::Dict{String,Any}, comp_list::Dict{String, Array{String,1}})
152-
for (comp_type, comp_ids) in comp_list
160+
for (comp_name, comp_ids) in comp_list
153161
for comp_id in comp_ids
154-
network[comp_type][comp_id][_PM.pm_component_status[comp_type]] = _PM.pm_component_status_inactive[comp_type]
162+
network[comp_name][comp_id][_PM.pm_component_status[comp_name]] = _PM.pm_component_status_inactive[comp_name]
155163
end
156164
end
157165
end
@@ -170,7 +178,8 @@ end
170178

171179

172180
function _clear_damage_indicator!(network::Dict{String,Any})
173-
for (comp_name, status_key) in _PM.pm_component_status
181+
for comp_name in restoration_comps
182+
status_key = _PM.pm_component_status[comp_name]
174183
for (i, comp) in get(network, comp_name, Dict())
175184
if haskey(comp, "damaged")
176185
comp["damaged"] = 0
@@ -349,6 +358,7 @@ function replicate_restoration_network(sn_data::Dict{String,<:Any}, count::Int,
349358
Memento.error(_PM._LOGGER, "replicate_restoration_network can only be used on single networks")
350359
end
351360

361+
#TODO make deepcopy to prevent altering input network
352362
clean_status!(sn_data)
353363
propagate_damage_status!(sn_data)
354364

@@ -403,7 +413,7 @@ function replicate_restoration_network(sn_data::Dict{String,<:Any}, count::Int,
403413
return mn_data
404414
end
405415

406-
""
416+
"propagates the damaged indicator from buses to indicent branches, generators, shunts, and storage"
407417
function propagate_damage_status!(data::Dict{String,<:Any})
408418
if _IM.ismultinetwork(data)
409419
for (i,nw_data) in data["nw"]

src/core/export.jl

-19
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,3 @@ for sym in names(@__MODULE__, all=true)
2121
@eval export $sym
2222
end
2323

24-
# the follow items are also exported for user-friendlyness when calling
25-
# `using PowerModelsRestoration`
26-
27-
# so that users do not need to import JuMP to use a solver with PowerModels
28-
import JuMP: with_optimizer
29-
export with_optimizer
30-
31-
import MathOptInterface: TerminationStatusCode
32-
export TerminationStatusCode
33-
34-
import MathOptInterface: ResultStatusCode
35-
export ResultStatusCode
36-
37-
for status_code_enum in [TerminationStatusCode, ResultStatusCode]
38-
for status_code in instances(status_code_enum)
39-
@eval import MathOptInterface: $(Symbol(status_code))
40-
@eval export $(Symbol(status_code))
41-
end
42-
end

src/util/iterative_restoration.jl

+124-38
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
"solve restoration using iterative period length"
1414
function run_iterative_restoration(network, model_constructor, optimizer; repair_periods=2, kwargs...)
1515
if _IM.ismultinetwork(network)
16-
Memento.error(_PM._LOGGER, "iterative restoration does not support multinetwork starting conditions")
16+
Memento.error(_PM._LOGGER, "Iterative Restoration does not support multinetwork starting conditions")
1717
end
1818

1919
Memento.info(_PM._LOGGER, "Iterative Restoration Algorithm starting...")
20+
time_start = time()
2021

2122
## Run initial MLD problem
2223
Memento.info(_PM._LOGGER, "begin baseline Maximum Load Delivery")
@@ -38,7 +39,7 @@ function run_iterative_restoration(network, model_constructor, optimizer; repair
3839

3940
Memento.info(_PM._LOGGER, "begin Iterative Restoration")
4041
result_iterative = _run_iterative_sub_network(network, model_constructor, optimizer; repair_periods=repair_periods, kwargs...)
41-
merge_solution!(result_iterative, result_mld)
42+
_merge_solution!(result_iterative, result_mld)
4243

4344
# this sets the status of components that were removed by
4445
# propagate_damage_status!, set_component_inactive!, propagate_topology_status!, ...
@@ -95,43 +96,108 @@ function run_iterative_restoration(network, model_constructor, optimizer; repair
9596
end
9697
end
9798

99+
result_iterative["solve_time"] = time()-time_start
98100
return result_iterative
99101
end
100102

101103

102104

103105
function _run_iterative_sub_network(network, model_constructor, optimizer; repair_periods=2, kwargs...)
104-
105106
## Set up network data files
106107
restoration_network = replicate_restoration_network(network, count=repair_periods)
107108

108109
## Run ROP problem with lower bound on restoration cardinality and partial load restoration
109110
restoration_solution = _run_rop_ir(restoration_network, model_constructor, optimizer, kwargs...)
110111

111-
clean_status!(restoration_solution["solution"])
112-
_PM.update_data!(restoration_network, restoration_solution["solution"]) # will update, loads, storage, etc....
113-
update_damage_status!(restoration_network) #set status of items before/after repairs
114-
delete!(restoration_network["nw"],"0")
112+
## Was the network solved?
113+
if restoration_solution["termination_status"]!= _PM.OPTIMAL && restoration_solution["termination_status"]!= _PM.LOCALLY_SOLVED
114+
Memento.warn(_PM._LOGGER, "subnetwork i was not solved, returning current solution")
115+
terminate_problem = true
116+
else
117+
terminate_problem = false
118+
end
119+
120+
clean_solution!(restoration_solution)
121+
_PM.update_data!(restoration_network, restoration_solution["solution"])
122+
clean_status!(restoration_network)
123+
_process_repair_status!(restoration_network)
124+
125+
## do all repairs occur in one network?
126+
repairs = _get_item_repairs(restoration_network)
127+
terminate_recursion = count(!isempty(nw_repairs) for (nw,nw_repairs) in repairs)==1
128+
if terminate_recursion
129+
## All repairs acccur in same time period: either no impact on load served, or required for feasbility
130+
## Rerun with with period for every device, with repairs fixed to final time period
131+
## This ensures that dispatches will be viable (especially storage) for the duration of the repair set
132+
133+
# save solve_time for accumulation
134+
solve_time = restoration_solution["solve_time"]
135+
136+
# make a repaired devices list for processing
137+
repaired_devices = Dict(comp_name=>String[] for comp_name in restoration_comps)
138+
for (nw_id, list) in repairs
139+
for (comp_name, comp_id) in list
140+
push!(repaired_devices[comp_name],comp_id)
141+
end
142+
end
143+
repair_count=sum(length(comp_ids) for (comp_name,comp_ids) in repaired_devices)
144+
145+
restoration_network = replicate_restoration_network(network, count=repair_count)
146+
147+
# set all repaired components inactive before final time period
148+
for net_id in 0:repair_count-1
149+
set_component_inactive!(restoration_network["nw"]["$(net_id)"], repaired_devices)
150+
end
151+
# clear damage indicator for final time period
152+
for comp_name in restoration_comps
153+
for (comp_id,comp) in restoration_network["nw"]["$(repair_count)"][comp_name]
154+
comp["damaged"]=0
155+
end
156+
end
157+
158+
# run 'restoration' on this network to solve powerflow
159+
restoration_solution = _run_rop_ir(restoration_network, model_constructor, optimizer, kwargs...)
160+
restoration_solution["solve_time"]+=solve_time
161+
162+
# Was the network solved?
163+
if restoration_solution["termination_status"]!= _PM.OPTIMAL && restoration_solution["termination_status"]!= _PM.LOCALLY_SOLVED
164+
Memento.warn(_PM._LOGGER, "subnetwork i was not solved, returning current solution")
165+
terminate_problem = true
166+
else
167+
terminate_problem = false
168+
end
169+
170+
clean_solution!(restoration_solution)
171+
_PM.update_data!(restoration_network, restoration_solution["solution"])
172+
clean_status!(restoration_network)
173+
_process_repair_status!(restoration_network)
174+
end
115175

176+
# copy network metadata, remove network data. There should be a better way of doing this.
116177
subnet_solution_set = deepcopy(restoration_solution)
117178
subnet_solution_set["solution"]["nw"] = Dict{String,Any}()
179+
# do not run restoration on network "0"
180+
delete!(restoration_network["nw"],"0")
118181

119-
for(nw_id, network) in sort(Dict{Int,Any}([(parse(Int, k), v) for (k,v) in restoration_network["nw"]]))
120-
if count_repairable_items(network) > 1
121-
Memento.info(_PM._LOGGER, "sub_network $(nw_id) has $(count_damaged_items(network)) damaged items and $(count_repairable_items(network)) repairable items")
182+
for(nw_id, net) in sort(Dict{Int,Any}([(parse(Int, k), v) for (k,v) in restoration_network["nw"]]))
183+
net["time_elapsed"] = network["time_elapsed"] # is this the correct way to reset time-elapsed for each network?
184+
if count_repairable_items(net) > 1 && !terminate_problem && !terminate_recursion
185+
# Run another layer of recursion
186+
Memento.info(_PM._LOGGER, "sub_network $(nw_id) has $(count_damaged_items(net)) damaged items and $(count_repairable_items(net)) repairable items")
122187

123188
Memento.info(_PM._LOGGER, "Starting sub network restoration")
189+
# copy global keys to create single networks
124190
for k in keys(restoration_network)
125191
if k != "nw"
126-
network[k] = restoration_network[k]
192+
net[k] = restoration_network[k]
127193
end
128-
network["multinetwork"] = false
129194
end
195+
net["multinetwork"] = false
130196

131197
Memento.info(_PM._LOGGER, "Start recursive call")
132-
subnet_solution = _run_iterative_sub_network(network, model_constructor, optimizer; repair_periods=repair_periods, kwargs...)
198+
subnet_solution = _run_iterative_sub_network(net, model_constructor, optimizer; repair_periods=repair_periods, kwargs...)
133199

134-
## Rename solution nw_ids appropriately
200+
# Rename solution nw_ids appropriately
135201
last_network = isempty(subnet_solution_set["solution"]["nw"]) ? 0 : maximum(parse.(Int,keys(subnet_solution_set["solution"]["nw"])))
136202
temp_solution = deepcopy(subnet_solution)
137203
temp_solution["solution"]["nw"] = Dict{String,Any}()
@@ -140,14 +206,13 @@ function _run_iterative_sub_network(network, model_constructor, optimizer; repai
140206
temp_solution["solution"]["nw"]["$(last_network+parse(Int,id))"] = net
141207
end
142208
end
143-
merge_solution!(subnet_solution_set, temp_solution)
144-
209+
_merge_solution!(subnet_solution_set, temp_solution)
145210
else
146-
211+
# Final recursion layer, place network in solution set
147212
last_network = isempty(subnet_solution_set["solution"]["nw"]) ? 0 : maximum(parse.(Int,keys(subnet_solution_set["solution"]["nw"])))
148-
subnet_solution_set["solution"]["nw"]["$(last_network+1)"] = network
213+
subnet_solution_set["solution"]["nw"]["$(last_network+1)"] = net
149214

150-
Memento.info(_PM._LOGGER, "sub_network $(nw_id) has $(count_damaged_items(network)) damaged items and $(count_repairable_items(network)) repairable items")
215+
Memento.info(_PM._LOGGER, "sub_network $(nw_id) has $(count_damaged_items(net)) damaged items and $(count_repairable_items(net)) repairable items")
151216
Memento.info(_PM._LOGGER, "sub_network does not need restoration sequencing")
152217
end
153218
end
@@ -157,7 +222,7 @@ end
157222

158223

159224
"Merge solution dictionaries and accumulate solvetime and objective"
160-
function merge_solution!(solution1, solution2)
225+
function _merge_solution!(solution1, solution2)
161226
Memento.info(_PM._LOGGER, "networks $(keys(solution2["solution"]["nw"])) finished with status $(solution2["termination_status"])")
162227

163228
solution1["termination_status"] = max(solution1["termination_status"],solution2["termination_status"])
@@ -171,31 +236,53 @@ function merge_solution!(solution1, solution2)
171236
end
172237
end
173238

239+
function _get_item_repairs(mn_data)
240+
if !_IM.ismultinetwork(mn_data)
241+
Memento.error(_PM._LOGGER, "get_item_repairs requires multinetwork.")
242+
end
174243

175-
" Update damage status for each time period based on whether the device has already been repaired"
176-
function update_damage_status!(mn_data)
177-
if _IM.ismultinetwork(mn_data)
178-
for (nw_id, network) in mn_data["nw"]
179-
for (comp_type, comp_status) in _PM.pm_component_status
180-
for (comp_id, comp) in network[comp_type]
181-
if nw_id != "0" #not items are repaired in "0", do not check in previous network for a change
182-
if comp[comp_status] != _PM.pm_component_status_inactive[comp_type] && # if comp is active
183-
mn_data["nw"]["$(parse(Int,nw_id)-1)"][comp_type][comp_id][comp_status] != _PM.pm_component_status_inactive[comp_type] # if comp was previously active
184-
if haskey(comp,"damaged") && comp["damaged"] == 1
185-
# therefore the comp was repaired in a former time_step, should be considered undamaged
186-
Memento.info(_PM._LOGGER, "$(comp_type) $(comp_id) was repaired before step $(nw_id). Setting damged state to 0.")
187-
comp["damaged"] = 0
188-
end
189-
end
244+
repairs = Dict{String,Array{Tuple{String,String},1}}(nw=>[] for nw in keys(mn_data["nw"]))
245+
for (nw_id, network) in mn_data["nw"]
246+
for comp_name in restoration_comps
247+
status_key = _PM.pm_component_status[comp_name]
248+
for (comp_id, comp) in network[comp_name]
249+
if nw_id != "0" #not items are repaired in "0", do not check in previous network for a change
250+
if comp[status_key] != _PM.pm_component_status_inactive[comp_name] && # if comp is active
251+
mn_data["nw"]["$(parse(Int,nw_id)-1)"][comp_name][comp_id][status_key] == _PM.pm_component_status_inactive[comp_name] # if comp was previously inactive
252+
push!(repairs[nw_id], (comp_name,comp_id))
190253
end
191254
end
192255
end
193256
end
194-
else
195-
Memento.error(_PM._LOGGER, "update_damage_status required multinetwork to identify is a device has been previously repaired.")
196257
end
258+
259+
return repairs
197260
end
198261

262+
263+
"Remove damage status if a device has already been repaired"
264+
function _process_repair_status!(mn_data)
265+
if !_IM.ismultinetwork(mn_data)
266+
Memento.error(_PM._LOGGER, "get_item_repairs requires multinetwork")
267+
end
268+
269+
repairs = _get_item_repairs(mn_data)
270+
for (nw_repair,items) in repairs
271+
for (comp_name,comp_id) in items
272+
for nw_network in keys(mn_data["nw"])
273+
if nw_repair < nw_network # is it still damaged in the time period the repair occurs
274+
comp = mn_data["nw"][nw_network][comp_name][comp_id]
275+
if haskey(comp,"damaged") && comp["damaged"]==1
276+
Memento.info(_PM._LOGGER, "$(comp_name) $(comp_id) was repaired at step $(nw_repair). Setting damaged state to 0 in network $(nw_network).")
277+
comp["damaged"]=0
278+
end
279+
end
280+
end
281+
end
282+
end
283+
end
284+
285+
199286
""
200287
function _run_rop_ir(file, model_constructor, optimizer; kwargs...)
201288
return _PM.run_model(file, model_constructor, optimizer, _build_rop_ir; multinetwork=true,
@@ -224,7 +311,6 @@ function _build_rop_ir(pm::_PM.AbstractPowerModel)
224311
_PM.variable_shunt_admittance_factor(pm, nw=n, relax=true)
225312

226313
constraint_restoration_cardinality_ub(pm, nw=n)
227-
constraint_restoration_cardinality_lb(pm, nw=n)
228314

229315
constraint_model_voltage_damage(pm, nw=n)
230316

0 commit comments

Comments
 (0)