Skip to content

Some tricks to help numerical issues #9

@odow

Description

@odow

The bound projection thing

So you'll probably run into an issue that took me quite a while to debug.

If you take the solution of a node in the forward pass and fix it as the incoming state in the next node, that subproblem is not guaranteed to be feasible, even if, in theory, you have relatively complete recourse.

That's because the first solve may violate tolerances by a small amount, and then presolve in the next node deduces that the problem is infeasible. (See, e.g., https://jump.dev/JuMP.jl/stable/tutorials/getting_started/tolerances/.)

This will hit you here:

# Fix primal solutions
for (j, var) in enumerate(comp_vars)
JuMP.fix(var_copy_map[var], last_primals[j], force = true)
end

There isn't a universal fix, but the most common cause of this is a solution violating the bounds on the state variables. A simple fix that catches almost every case is to project the state back onto its bounds:

https://github.com/odow/SDDP.jl/blob/aec55e2279c4abbd2a15ab1a430f6bb5a526976e/src/algorithm.jl#L182-L206

Resetting the optimizer

Another thing that is super(-ish) common is the following:

  • You have a problem running along fine for many iterations
  • Each iteration adds a cut that changes optimality but does NOT change feasibility (because your cost to go variable won't be bounded above)
  • Suddenly, the solver says your problem is unbounded/infeasible/has numerical error.

The reason is that solvers may work from an existing basis. The sequence of updates can eventually lead to numerical issues, which, if the solver started from scratch, it wouldn't encounter. One solution is to use MOI.Utilities.reset_optimizer to force a reset if you detect a non-optimal status, and then bail completely only if that can't repair the solution.

Here's how we do it:

https://github.com/odow/SDDP.jl/blob/aec55e2279c4abbd2a15ab1a430f6bb5a526976e/src/algorithm.jl#L303-L400
https://github.com/odow/SDDP.jl/blob/aec55e2279c4abbd2a15ab1a430f6bb5a526976e/src/algorithm.jl#L481-L521

This feels like a massive hack, but it works surprisingly well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions