NoFeasibleSolutionError is nonspecific
Summary
The NoFeasibleSolutionError should be renamed and changed to return information about why no solution could be returned.
Rationale
Multiple times in the process of updating IDAES and its subsidiary projects to use the new solver interface, I have encountered the NoFeasibleSolutionError. Every time it has left me scratching my head about what has gone wrong. Its name makes it sound like the solver either terminated due to reaching the maximum number of iterations or converged to a point of infeasibility. However, that has never been the case. It's always something to do with the .nl writer. Either the .nl writer has proved the problem to be infeasible (with or without the linear presolve) or, for example, a solver was called to solve a block without any constraints.
In all of these cases, the .nl writer has discovered useful information about the problem, but this information is not conveyed to the user. It leaves them frustrated and looking for documentation about what that Exception means. (I have not found documentation that explains it.) This can be remedied by passing along information about why the .nl writer failed to the user.
Is what you want in the termination condition? The NoFeasibleSolutionError is only meant to say that the user requested a solution be loaded back into the model, but that couldn't be done. The solution_status just has 4 possible values (noSolution, infeasible, feasible, and optimal. Note that in this case, infeasible is meant to mean that the variable values violate some of the constraints. This has no indication of why the solver terminated though (e.g., could be an iteration limit). The termination_condition is what is meant to convey why the solver terminated. This has possible values like convergenceCriteriaSatisfied, maxTimeLimit, provenInfeasible, iterationLimit, etc.
This does not help determine if something happened during presolve or actually within ipopt, but it still might have some of what you need?
I checked the Ipopt interface, and the termination_condition will be provenInfeasible if the NL writer determines that the problem is infeasible. However, I think that is the only special handling we do for things that go wrong before sending the problem to Ipopt...
Maybe we should go through the NL writer and look for other types of exceptions. The NLWriterInfo could hold some kind of status which could be passed along through the Ipopt interface.
@jsiirola and I were discussing this in a different context, and it seems like renaming it to just NoSolutionError would be clearer: I agree that it's somehow jarring because the word "feasible" seems to suggest a mathematical truth when really all that has happened is that you the user asked for a solution, and for reasons explained elsewhere, Pyomo is not going to give you one.
I checked the Ipopt interface, and the
termination_conditionwill beprovenInfeasibleif the NL writer determines that the problem is infeasible. However, I think that is the only special handling we do for things that go wrong before sending the problem to Ipopt...
So is the intended workflow for the user to always have load_solution=False, then manually load the solution later? Once an exception is raised, the information from termination_condition is lost. The biggest problem is when a solve command is buried five or six layers deep in IDAES initialization---it is extremely difficult to reconstruct the state of the NLP when the NoFeasibleSolutionError is raised. I suppose we could create a wrapper for solvers returned by get_solver to pass load_solution=False by default, then parse the solution_status in order to create a more informative error message. However, that raises the question about why Pyomo isn't taking care of that in the first place.
for reasons explained elsewhere, Pyomo is not going to give you one.
Where are these reasons actually explained? I certainly couldn't find an explanation when searching either online or the Pyomo ReadTheDocs.
These are great questions... I suggest we discuss further at the dev call tomorrow.