@@ -292,19 +292,77 @@ function _has_primal_solution(node::Node)
292
292
return status in (JuMP. FEASIBLE_POINT, JuMP. NEARLY_FEASIBLE_POINT)
293
293
end
294
294
295
- function attempt_numerical_recovery (model:: PolicyGraph , node:: Node )
296
- if JuMP. mode (node. subproblem) == JuMP. DIRECT
297
- @warn (
298
- " Unable to recover in direct mode! Remove `direct = true` when " *
299
- " creating the policy graph."
300
- )
301
- else
302
- model. ext[:numerical_issue ] = true
303
- MOI. Utilities. reset_optimizer (node. subproblem)
304
- optimize! (node. subproblem)
295
+ function _has_dual_solution (node:: Node )
296
+ status = JuMP. dual_status (node. subproblem)
297
+ return status in (JuMP. FEASIBLE_POINT, JuMP. NEARLY_FEASIBLE_POINT)
298
+ end
299
+
300
+ """
301
+ set_numerical_difficulty_callback(
302
+ model::PolicyGraph,
303
+ callback::Function,
304
+ )
305
+
306
+ Set a callback function `callback(::PolicyGraph, ::Node; require_dual::Bool)`
307
+ that is run when the optimizer terminates without finding a primal solution (and
308
+ dual solution if `require_dual` is `true`).
309
+
310
+ ## Default callback
311
+
312
+ The default callback is a small variation of:
313
+ ```julia
314
+ function callback(::PolicyGraph, node::Node; require_dual::Bool)
315
+ MOI.Utilities.reset_optimizer(node.subproblem)
316
+ optimize!(node.subproblem)
317
+ return
318
+ end
319
+ ```
320
+ This callback is the default because a common issue is solvers declaring the
321
+ infeasible because of numerical issues related to the large number of cutting
322
+ planes. Resetting the subproblem---and therefore starting from a fresh problem
323
+ instead of warm-starting from the previous solution---is often enough to fix the
324
+ problem and allow more iterations.
325
+
326
+ ## Other callbacks
327
+
328
+ In cases where the problem is truely infeasible (not because of numerical issues
329
+ ), it may be helpful to write out the irreducible infeasible subsystem (IIS) for
330
+ debugging. For this use-case, use a callback as follows:
331
+ ```julia
332
+ function callback(::PolicyGraph, node::Node; require_dual::Bool)
333
+ JuMP.compute_conflict!(node.suprobblem)
334
+ status = JuMP.get_attribute(node.subproblem, MOI.ConflictStatus())
335
+ if status == MOI.CONFLICT_FOUND
336
+ iis_model, _ = JuMP.copy_conflict(node.subproblem)
337
+ print(iis_model)
305
338
end
306
- if ! _has_primal_solution (node)
307
- model. ext[:numerical_issue ] = true
339
+ return
340
+ end
341
+ SDDP.set_numerical_difficulty_callback(model, callback)
342
+ ```
343
+ """
344
+ function set_numerical_difficulty_callback (
345
+ model:: PolicyGraph ,
346
+ callback:: Function ,
347
+ )
348
+ model. ext[:numerical_difficulty_callback ] = callback
349
+ return
350
+ end
351
+
352
+ function attempt_numerical_recovery (
353
+ model:: PolicyGraph ,
354
+ node:: Node ;
355
+ require_dual:: Bool = false ,
356
+ )
357
+ model. ext[:numerical_issue ] = true
358
+ callback = get (
359
+ model. ext,
360
+ :numerical_difficulty_callback ,
361
+ default_numerical_difficulty_callback,
362
+ )
363
+ callback (model, node; require_dual)
364
+ missing_dual_solution = require_dual && ! _has_dual_solution (node)
365
+ if ! _has_primal_solution (node) || missing_dual_solution
308
366
# We use the `node.index` in the filename because two threads could both
309
367
# try to write the cuts to file at the same time. If, after writing this
310
368
# file, a second thread finds an infeasibility of the same node, it
@@ -321,6 +379,23 @@ function attempt_numerical_recovery(model::PolicyGraph, node::Node)
321
379
return
322
380
end
323
381
382
+ function default_numerical_difficulty_callback (
383
+ model:: PolicyGraph ,
384
+ node:: Node ;
385
+ kwargs... ,
386
+ )
387
+ if JuMP. mode (node. subproblem) == JuMP. DIRECT
388
+ @warn (
389
+ " Unable to recover in direct mode! Remove `direct = true` when " *
390
+ " creating the policy graph."
391
+ )
392
+ return
393
+ end
394
+ MOI. Utilities. reset_optimizer (node. subproblem)
395
+ optimize! (node. subproblem)
396
+ return
397
+ end
398
+
324
399
"""
325
400
_initialize_solver(node::Node; throw_error::Bool)
326
401
0 commit comments