Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
653f7a6
Initial (not working) draft of Gurobi direct MINLP walker
emma58 Jan 7, 2025
d2edb21
Adding minimum gurobipy version
emma58 Jan 8, 2025
33ce433
blackify
emma58 Jan 8, 2025
ad3c8a3
Notes on how we're actually going to have to implement it--building o…
emma58 Jan 8, 2025
7ad1794
Switching onto quadratic walker to try to write constraints correctly…
emma58 Jan 8, 2025
4dcb73e
Adding start of integration test
emma58 Jan 9, 2025
162ab75
Making the test file names make sense
emma58 Jan 9, 2025
16def08
Working version of the Gurobi walker based on the expression evaluati…
emma58 Feb 4, 2025
dbac17f
black
emma58 Feb 4, 2025
a9f6c3a
The gurobi printStats method agrees with me, but not what I get when …
emma58 Feb 4, 2025
62b93d2
Going back to an explicity exit node dispatcher because for weird thi…
emma58 Feb 7, 2025
01b3a7a
black
emma58 Feb 7, 2025
a6d01a6
Fixing a bug in the UnaryExpression handler, more tests
emma58 Feb 7, 2025
f8b8709
black
emma58 Feb 7, 2025
c362117
Merge branch 'main' into gurobi-minlp
emma58 Mar 14, 2025
8c23638
Changes I don't remember for Gurobi MINLP
emma58 May 12, 2025
c3d1350
Almost figured out how to test the Gurobi expression tree, woohoo
emma58 May 13, 2025
b53b8ef
Fixing a bug where I forgot to update the gurobipy model before I ret…
emma58 May 13, 2025
536844b
Moving all the hacky tests to use the public API for getting the nonl…
emma58 May 13, 2025
65980c9
black
emma58 May 13, 2025
ec3aeed
Merge branch 'main' into gurobi-minlp
emma58 May 14, 2025
03ba09b
Fixing a typo
emma58 May 14, 2025
767428f
Adding a fix and a test for a bug where we didn't account for NPV exp…
emma58 May 29, 2025
9eed315
Adding a fix and test for a problem calling _apply_operation for Divi…
emma58 May 29, 2025
39b7ea1
Fixing a bug with handling of fixed variables
emma58 May 29, 2025
971f324
black
emma58 May 29, 2025
4142b2f
Merge branch 'main' into gurobi-minlp
emma58 May 29, 2025
51f35ec
Adding missing imports
emma58 May 29, 2025
7609f09
BooleanVars are welcome to hang out on the model
emma58 May 29, 2025
1cda2fc
Adding support for named expressions
emma58 May 29, 2025
598a90c
black
emma58 May 29, 2025
81caf88
Adding a solution loader and a results object
emma58 May 29, 2025
e44530b
Testing results object a little
emma58 May 29, 2025
a408ceb
black
emma58 May 29, 2025
9ebea39
NFC: Cleaning up and making comment more true
emma58 May 29, 2025
5daef08
Whoops, misfiled test
emma58 May 29, 2025
e91f713
Fixing another but with how we hit _apply_operation for DivisionExpre…
emma58 May 29, 2025
461c64c
I assume there are other ways to hit that bug, don't have tests at th…
emma58 May 29, 2025
850eeca
Black
emma58 May 29, 2025
0e8056b
Merge branch 'main' into gurobi-minlp
emma58 Jun 9, 2025
26b0be3
Adding some notes and fixing a typo
emma58 Jun 10, 2025
cee0125
Merge branch 'main' into gurobi-minlp
emma58 Jul 9, 2025
0fa39fa
Merge branch 'main' into gurobi-minlp
emma58 Jul 9, 2025
2c18597
Moving to the contrib.solver SolverFactory so that we get the wrapper
emma58 Jul 9, 2025
18c3d3a
Fixing a couple tests that need update called to work
emma58 Jul 9, 2025
cc22abc
Making the absolute value tests do something
emma58 Jul 9, 2025
0b7d03a
Fixing a problem where Gurobi believes 0*nonlinear_stuff is nonlinear…
emma58 Jul 21, 2025
520b7a0
Tracking quadratics separately from general nonlinear because we need…
emma58 Aug 5, 2025
0271a12
Removing a lot of debugging
emma58 Aug 6, 2025
29f01bd
Merge branch 'main' into gurobi-minlp
emma58 Sep 26, 2025
051a598
Prevent 0 from being added to sums in the Gurobi expressions by not h…
emma58 Sep 26, 2025
585d9e4
Adding converter from nonlinear Gurobi expression trees to pyomo expr…
emma58 Sep 26, 2025
a011fa3
Finishing the named expression test that inspired the whole conversio…
emma58 Sep 26, 2025
70838ed
Removing a lot of debugging in the writer
emma58 Sep 26, 2025
e784730
Implementing conversion from Gurobi square, fixing bug where SumExpre…
emma58 Sep 26, 2025
bbb27e8
Converting a division test to the prettier way of testing nonlinear e…
emma58 Sep 26, 2025
5157c0d
Raising error for invalid numbers since I think no good can come from…
emma58 Sep 27, 2025
6a78ee5
Fixing the numpy boolean problem, but exposing a problem with express…
emma58 Sep 27, 2025
12848b7
Initial support for MINLP, with tests
ronaldvdv-gurobi Sep 29, 2025
d4e939e
Fixing a bug where we labeled constant LinearExpressions as linear ra…
emma58 Sep 29, 2025
87d2762
Removing debugging
emma58 Sep 29, 2025
3afba1d
NFC: Black has many complaints
emma58 Sep 29, 2025
410e5c6
Moving gurobi MINLP to contrib.solvers, generalizing the current Guro…
emma58 Sep 30, 2025
d133fe1
NFC: Fixing typos
emma58 Sep 30, 2025
8ca6b26
NFC: Black
emma58 Sep 30, 2025
e9e9467
Removing my homemade solution loader
emma58 Sep 30, 2025
6102cfc
Merge branch 'feature/gurobi_minlp' into gurobi-minlp
emma58 Sep 30, 2025
c59b527
Moving new tests to contrib.solvers, reverting changes in old Gurobi …
emma58 Sep 30, 2025
350e961
Updating Ronald's tests to new solver interface, adjusting a few styl…
emma58 Sep 30, 2025
5b45177
NFC: black
emma58 Sep 30, 2025
a5e3a94
Adding some more tolerance for one of the solvers tests
emma58 Sep 30, 2025
7bc0b7c
Fixing a bug where I need to explicitly define the auxiliary variable…
emma58 Sep 30, 2025
4905284
The log test actually wasn't at the global min, I agree with Gurobi
emma58 Sep 30, 2025
34be2c5
Merge branch 'main' into gurobi-minlp
emma58 Oct 1, 2025
409d32d
debuging...
emma58 Oct 1, 2025
6e20d03
Adding more tolerance to another test that is currently failing on GHA
emma58 Oct 1, 2025
9e56f83
More debugging--I can't seem to reproduce this numpy thing locally, e…
emma58 Oct 1, 2025
9651d0c
Removing debugging--no idea what's going on still
emma58 Oct 2, 2025
864b98a
Fixing a bug with older versions of numpy where we hit the wrong oper…
emma58 Oct 3, 2025
f017b6d
Unix style line endings, whoops
emma58 Oct 3, 2025
cf18d8a
Incorporating John's to_bounded_expression suggestion
emma58 Oct 3, 2025
b524169
Removing debugging imports and some outdated comments
emma58 Oct 3, 2025
a1f7c8b
NFC: Cleaning up some comments
emma58 Oct 6, 2025
383af85
Oh my goodness black
emma58 Oct 6, 2025
08390a0
Fixing an optional dependency import issue
emma58 Oct 6, 2025
bbb1da8
More black
emma58 Oct 6, 2025
8cc3995
Removing some forgotten print statements
emma58 Oct 6, 2025
2e84907
Simplifying the tests for the walker by converting back to pyomo expr…
emma58 Oct 6, 2025
bf207e5
black
emma58 Oct 6, 2025
02d37db
Removing the last writer test onto the conversion back to Pyomo expre…
emma58 Oct 7, 2025
bc1bfcd
Merge branch 'main' into gurobi-minlp
emma58 Oct 7, 2025
3de977f
Adding some tests for missing cases for pow expressions
emma58 Oct 9, 2025
f57130a
black
emma58 Oct 9, 2025
9e43496
Adding test for absolute value of constant
emma58 Oct 9, 2025
52b331b
Testing absolute value of constant
emma58 Oct 9, 2025
4b75ddb
Testing a couple error cases in the writer
emma58 Oct 9, 2025
6488881
black
emma58 Oct 9, 2025
94db0a9
Debugging Jenkins failure
emma58 Oct 9, 2025
77ef498
Switching to categorize_valid_components, fixing one test that breaks
emma58 Oct 14, 2025
adc46b7
black
emma58 Oct 14, 2025
30c2681
Merge branch 'main' into gurobi-minlp
emma58 Oct 14, 2025
984c77c
Adding Gurobi direct MINLP to the load method in plugins
emma58 Oct 15, 2025
31c9523
Centralizing check_constant implementation in repn.util
emma58 Oct 15, 2025
98b3e76
black
emma58 Oct 15, 2025
a5dd19e
Emma learns to spell accommodating
emma58 Oct 15, 2025
8e515bc
Merge branch 'main' into gurobi-minlp
emma58 Oct 15, 2025
66b76dd
Not copying result in apply_operation for sum
emma58 Oct 17, 2025
a789a18
Accommodating general variable domains
emma58 Oct 17, 2025
7ccb122
NFC: comment correction
emma58 Oct 17, 2025
917026b
NFC: comment typo
emma58 Oct 17, 2025
cacb14e
Merge branch 'main' into gurobi-minlp
jsiirola Oct 17, 2025
bf3401c
NFC: apply black
jsiirola Oct 17, 2025
2a0c67e
Sorting unrecognized components
emma58 Oct 17, 2025
ebd031f
Merge remote-tracking branch 'emmaFork/gurobi-minlp' into gurobi-minlp
emma58 Oct 17, 2025
a9d049b
Ensure the unknown component exception is deterministic
jsiirola Oct 17, 2025
dfa97dd
Prevent gurobipy from being imported with environ
jsiirola Oct 17, 2025
a69dc6c
Fix copyright statements
mrmundt Oct 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pyomo/contrib/solver/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .solvers.ipopt import Ipopt, LegacyIpoptSolver
from .solvers.gurobi_persistent import GurobiPersistent
from .solvers.gurobi_direct import GurobiDirect
from .solvers.gurobi_direct_minlp import GurobiDirectMINLP
from .solvers.highs import Highs
from .solvers.knitro.direct import KnitroDirectSolver

Expand All @@ -32,6 +33,11 @@ def load():
legacy_name="gurobi_direct_v2",
doc="Direct (scipy-based) interface to Gurobi",
)(GurobiDirect)
SolverFactory.register(
name='gurobi_direct_minlp',
legacy_name='gurobi_direct_minlp',
doc='Direct interface to Gurobi accommodating general MINLP',
)(GurobiDirectMINLP)
SolverFactory.register(
name="highs", legacy_name="highs", doc="Persistent interface to HiGHS"
)(Highs)
Expand Down
25 changes: 16 additions & 9 deletions pyomo/contrib/solver/solvers/gurobi_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def load_vars(self, vars_to_load=None, solution_number=0):
if self._grb_model.SolCount == 0:
raise NoSolutionError()

iterator = zip(self._pyo_vars, self._grb_vars.x.tolist())
iterator = zip(self._pyo_vars, map(operator.attrgetter('x'), self._grb_vars))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is fetching the values one at a time slower than getting them in a single vector? (Same question applies to get_primals, get_duals. and get_redued_costs below)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know? If it is, this is much harder to generalize because the Gurobi MINLP version doesn't have the gurobi vars as a vector--they're already a list.

if vars_to_load:
vars_to_load = ComponentSet(vars_to_load)
iterator = filter(lambda var_val: var_val[0] in vars_to_load, iterator)
Expand All @@ -130,7 +130,7 @@ def get_primals(self, vars_to_load=None, solution_number=0):
if self._grb_model.SolCount == 0:
raise NoSolutionError()

iterator = zip(self._pyo_vars, self._grb_vars.x.tolist())
iterator = zip(self._pyo_vars, map(operator.attrgetter('x'), self._grb_vars))
if vars_to_load:
vars_to_load = ComponentSet(vars_to_load)
iterator = filter(lambda var_val: var_val[0] in vars_to_load, iterator)
Expand All @@ -143,24 +143,26 @@ def get_duals(self, cons_to_load=None):
def dedup(_iter):
last = None
for con_info_dual in _iter:
if not con_info_dual[1] and con_info_dual[0][0] is last:
if not con_info_dual[1] and con_info_dual[0] is last:
continue
last = con_info_dual[0][0]
last = con_info_dual[0]
yield con_info_dual

iterator = dedup(zip(self._pyo_cons, self._grb_cons.getAttr('Pi').tolist()))
iterator = dedup(
zip(self._pyo_cons, map(operator.attrgetter('Pi'), self._grb_cons))
)
if cons_to_load:
cons_to_load = set(cons_to_load)
iterator = filter(
lambda con_info_dual: con_info_dual[0][0] in cons_to_load, iterator
lambda con_info_dual: con_info_dual[0] in cons_to_load, iterator
)
return {con_info[0]: dual for con_info, dual in iterator}
return {con_info: dual for con_info, dual in iterator}

def get_reduced_costs(self, vars_to_load=None):
if self._grb_model.Status != gurobipy.GRB.OPTIMAL:
raise NoReducedCostsError()

iterator = zip(self._pyo_vars, self._grb_vars.getAttr('Rc').tolist())
iterator = zip(self._pyo_vars, map(operator.attrgetter('Rc'), self._grb_vars))
if vars_to_load:
vars_to_load = ComponentSet(vars_to_load)
iterator = filter(lambda var_rc: var_rc[0] in vars_to_load, iterator)
Expand Down Expand Up @@ -374,7 +376,12 @@ def solve(self, model, **kwds) -> Results:
timer,
config,
GurobiDirectSolutionLoader(
gurobi_model, A, x, repn.rows, repn.columns, repn.objectives
gurobi_model,
A,
x.tolist(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why convert x to a list?

Copy link
Contributor Author

@emma58 emma58 Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of the thing you commented on above, actually. I'm making this look the same as Gurobi MINLP so that they can use an identical SolutionLoader. Gurobi MINLP doesn't have the luxury of declaring the variables as a vector (I don't think? Although if I can dynamically add to one, that's another option, maybe?), so they're a list at this point right now.

list(map(operator.itemgetter(0), repn.rows)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why de-tupalize rows? aren't both values needed by the loader?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so--the only time we need anything from this at all is to get the duals. And then you have to get the Pi attribute off each constraint. The difference is that was vectorized before too...

repn.columns,
repn.objectives,
),
)

Expand Down
Loading
Loading