Skip to content

[PEP] Next Steps for Solver Interface Redesign #3688

@mrmundt

Description

@mrmundt

Summary

This is actually just a checklist of the tasks that we decided need to be done before we are comfortable porting over other interfaces.

  • Finish the split of available and license_is_valid
  • Update SolutionLoader to add in loader functions, change naming, convert to NotImplemented, etc. [Depends on #3698] Rework SolutionLoader #3701
    • Change get_primals to get_vars
    • add better support for multiple solutions
      • get_solution_ids, get_number_of_solutions
    • add a method called load_solution that loads everything
    • add a load_import_suffixes method
    • return NotImplemented instead of raising an exception when something is not supported
  • Assign "Generation" property to solvers - @mrmundt - Create API version attribute for different solver generations #3699
  • Comprehensive test suite (different types of models and explanation of their purpose) - @michaelbynum (@mrmundt will help with structure but doesn't know what "comprehensive" means w.r.t. models)
  • Standardized tests checking populating the results object for all solver interfaces - @mrmundt

Notes from 8/8/2025


IPOPT solver exit codes: Do we want to revisit what we support?

#868

IPOPT in particular: whatever the solver gave us, we are going to return the raw solver message as opposed to our own strings.

We think it is best to follow the guideline that, in the cases of solvers like IPOPT that have a lot of possible return values, we will check for/process the common ones (80/20 rule), and all others we will return raw/unaltered and allow users to inspect it.

We will map the result to SolverStatus & TerminationCondition. We will also return whatever the solver gives us in extra info (e.g., return code, solver message, etc). I don't know if we also decided to make "solver message" (or whatever) a standardized results field

Non-persistent options: is that fixed? I think it is but I'm not sure

#798

Gurobi file writing: are we writing files? If so, would we still see this issue?

#1567

Mipgap on Results object: should we put it?

#2167

Ambiguity here. Are they just asking us to report what the solver reported, sure, if we want to promise that we parse it all the time. We could put it in the extra_info and let them figure it out. From a pedantic point of view, what do we even report back? There are multiple ways to do it.

If the solver returns a relative gap at termination, we should report that back to the user, and be clear in documentation that that is what we are doing.

2362: Can Michael look over this again and see what is still valid / if we need to make some changes in the new interfaces?

#2362

Pyomo command with new interfaces: how do we want this to work?

Related which is why I thought about it: #2703

Michaels Notes - Remaining Items - 8/7/25

Split available and license_available/acquire_license/release_license?

John: Would like available to be able to be called on a class and on an instance; remove config from this.

For the notes. I propose the following API:

def available(self, recheck=False):

Return the best-guess of the availability

The '_availability_cache" should be a class attribute

If you care about guaranteeing that you have a license, the solver interface must implement acquire_license() and release_license()

It would make sense to leave available the way it is right now (returns a tuple). Separately is the issue for scripters / license solvers that you'd like to be able to acquire a license and not give it back yet / lock it (e.g., persistent interfaces).

For people running lots of solves in a loop, the time to checkout the license over and over can be substantial, but that only applies for interfaces that allow persistence in some manner.

Would not be part of the standard solver API, but would have a standard API.

Part of this question is about caching. Available should cache, for sure, with an option to ignore the cache

MICHAEL HAS BEEN CONVINCED. No takesie-backsies!

Add tests to ensure all solvers are populating the necessary results fields

Add tests to ensure all solvers are respecting the necessary config options

Extract model change detection from solver interfaces

Update documentation

AutoUpdateConfig lives elsewhere now

Add warmstart enum (see notes above)

Solver io? Just different names for the solvers? "highs_persistent", "highs_lp", "highs_mps", etc.? Piggyback on config?

Register all under different names; generic name (e.g., "gurobi") that we think is the most recommended interface at the moment

EASY Helper function for "Here are all the possible options for this solver!" that they can call

Suffix support

We have this right meow: https://github.com/Pyomo/pyomo/blob/main/pyomo/contrib/solver/common/base.py#L494

That is an AMPL-ism / NL-file thing.

We end up using suffixes to annotate lots of stuff, like duals.

This is normally baked into the solver interface or the compiler/writer.

Was only useful if you didn't specify a dual suffix but you told us that on the command, we would go make one for you. If load_solutions=False, we shouldn't touch your model; otherwise, we shouldn't touch your data, because that's evil.

We can make this go away and leave how suffixes are dealt with to the individual solver interfaces.

Maybe a solver should go check that there aren't any export suffixes? (Apparently source of a current argument with Doug)

GET RID OF IT!

Logfile

Console log is in the results object; logfile is ambiguous; we now have tee that does so many fancy things; working_dir lets people find files in an obvious location anyway

GET RID OF IT!

Are there other changes we want in the interface?

Is there any reason to wait longer before starting to port other solver interfaces?

Miranda: I think available and license stuff is important to do before we poke at more.

John: SolutionLoader is intended to be a superset of possible outputs that you could want to load into your model so that the way to load said outputs is standardized across all solvers that have them. Default will be to return, not raise, NotImplemented

Youssouf suggests having some sort of support_* functions in the SolutionLoader that check if something is not implemented

Interfaces should "record their generation" - #3641 (comment); we need to implement this thing because it's important

Group Discussion – Current Status – 8/8/2025

Attendees: Bethany, Youssouf, Rachael, Michael, Emma, Norma, Miranda, John, David

Michael starts by showing examples of how the new interface currently works

Solve_example: highlights the config object options and new results printout; Michael covers the new options individually and what they do/mean/differ from old options

Suggestions from that chat

Emma: "print(results) doesn't work anymore; minor nit but annoying"

Emma: "Have we talked about iteration limit also being generalized?"

John: "We should consider setting timing_info.timer visibility to Advanced; same with solution_loader"

John: "HierarchicalTimer needs to stop alphabetizing the sections (and leave them in decl order)"

Emma: "BranchAndBound should have node_limit, solution_limit, and things like that."

Miranda: "I want us to follow the guideline here that it should be 'more true than not' that solvers of this type generically use these config options. If only one solver has solution_limit, that doesn't make sense in the base class; it makes sense in the derived class."

Results_ex: highlights a common workflow to get primals, duals, rc from a results object

John: Some optimization engines, their solution is actually a set of solutions (multi-objective), and what gets turned back is the non-dominated front. Is there an extension that would work for allowing population-based? One approach could be get_solution_ids , set_active_solution (in SolutionLoaderBase). Or we could pass solution IDs to load_vars, get_primals themselves. I like the idea of a solution_id over a solution_number.

Youssouf says that passing it into the load_vars, get_primals makes more sense (because we need to pass the id to the solver instance)

Couldn't contrib.alternative_solutions use this same infrastructure? (Yes)

John: solution attribute cache. There could be a lot of reasons to store a solution in a results object (e.g., a final incumbent, an attribute on the result). Another could be found by heuristic, found by bounding, nth incumbent found. Annotated database of solutions with attributes.

get_solution_info that is a flexible dict that could contain any plethora of things that you might want to define for a solution

You want to be able to search both by "tell me about this solution" and "tell me solutions that have this attribute",

Does solution_status actually belong in the Results object?

Miranda says yes; Emma pushes back and says maybe it should be something like "final_solution_status" (or the equivalent concept of that); another one might make sense in the solution loader, though, so you can see individual statuses

Options_ex: highlights class, instance, and solve-specific passing of options

John: We have also decided to eventually make a "global config" file for people who want to set persistent across everything options

Emma brings up a point about metasolvers and how to handle options for that

Miranda warns that we should not over-engineer in advance.

Bethany asks about validating solver options; Miranda answers that it is somewhat solver-specific (e.g., IPOPT does a check for some of the options); John then says that, to the best that we can, though, we are not going to mess with those and let the solver handle it.

Tee_ex: highlights all the cool different (and extremely flexible) ways that we can use tee now (yay John for overhauling all of this).

Timing_ex: highlights how to use and customize the timer

Side-note: sticking extra stuff on results objects by changing default visibility (e.g., IPOPT + solver_log)

Persistent_ex: highlights specific features for persistent solvers, particularly how neatly you can use the timer in conjunction with it

Michael goes over the documentation on RTD

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions