Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add branching priorities to CPLEXSHELL #1

Merged
merged 10 commits into from
Feb 21, 2020
Merged

Conversation

ruaridhw
Copy link

Fixes Pyomo#1299

Summary/Motivation:

To be able to use the branching priority capability of commercial solvers when solving MIP problems

Changes proposed in this PR:

  • Include branch_priority and branch_direction attributes of variable data
  • Read off these attributes as part of the CPLEXSHELL solve and write them to a CPLEX .ord file
  • Update the CPLEX OPL script to read these .ord file should it exist

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@ruaridhw ruaridhw changed the title 🔨 Add branching priorities to CPLEXSHELL Add branching priorities to CPLEXSHELL Feb 20, 2020
- Include `branch_priority` and `branch_direction` attributes of variable data
- Read off these attributes as part of the CPLEXSHELL solve and write them to a CPLEX .ord file
- Update the CPLEX OPL script to read these .ord file should it exist
- End-to-end tests of `opt.solve()` using branching priorities with CPLEXSHELL
- Declare `priority` and `direction` suffixes on the model instead of attributes on the variable data in order to represent branching decisions
@ruaridhw ruaridhw force-pushed the cplex_branching_priorities branch from 9bbba62 to 5e44956 Compare February 20, 2020 15:24
Copy link

@mhusbyn mhusbyn left a comment

Choose a reason for hiding this comment

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

See comments. Main bits:

  • Pulling out direction class into a more global enum and making the translation CPLEX specific
  • Splitting up _write_priorities_file (if it makes sense)

Comment on lines 116 to 120
class CPLEXBranchDirection:
default = 0
down = -1
up = 1

Copy link

Choose a reason for hiding this comment

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

This could probably be pulled out to be a 'global' enum so it can be used for all solvers that support branching priorities. The only solver specific stuff here is the translation so that can be done in each individual solver.

That way you can build your model agnostic of what solver it will run on, which I don't think would be possible with the current implementation.

Copy link

Choose a reason for hiding this comment

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

I see you have to specify a data type when declaring the Suffix. I guess you should add a 'data_type' attribute or something as well in that case that can be used.

Copy link
Author

@ruaridhw ruaridhw Feb 21, 2020

Choose a reason for hiding this comment

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

The only solver specific stuff here is the translation

Ah not quite 😉 These magic integer values themselves are CPLEX-specific. See here. Having said that, it turns out that Gurobi also just happens to share the same magic values... So I'll do the refactor!

Copy link

Choose a reason for hiding this comment

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

Where are these magic integer values being used for CLEX specific stuff? Aren't they being translated in to_str?

Copy link
Author

Choose a reason for hiding this comment

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

As discussed, they will be used in the CPLEXDirect and GurobiDirect solver implementations which have not been included here. For now, I've removed CPLEXBranchDirection and moved the translation to ORDFileSchema

Comment on lines 125 to 130
try:
return {CPLEXBranchDirection.down: "DN", CPLEXBranchDirection.up: "UP"}[
branch_direction
]
except KeyError:
return ""
Copy link

Choose a reason for hiding this comment

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

No idea if more efficient/readable but dict.get is an option here. But Python tends to favour 'easier to ask for forgiveness' over 'look before you leap' so I guess this is fine too.

Copy link
Author

Choose a reason for hiding this comment

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

But Python tends to favour 'easier to ask for forgiveness'

Yeah, that was my intention


def _write_priorities_file(self, instance) -> None:
""" Write a variable priorities file in the CPLEX ORD format. """
from pyomo.core.base import Var
Copy link

Choose a reason for hiding this comment

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

Is there a reason this is an inline import?

Copy link
Author

Choose a reason for hiding this comment

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

I was copying _warm_start() where this is a local import too... I'll try removing both and 🤞 nothing breaks

SUFFIX_PRIORITY_NAME = "priority"
SUFFIX_DIRECTION_NAME = "direction"

def _write_priorities_file(self, instance) -> None:
Copy link

Choose a reason for hiding this comment

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

NB: This has a return type (I'm assuming not Python 2.7 compliant)

Copy link
Author

Choose a reason for hiding this comment

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

Ah perfect, that's probably the reason 2.7 is breaking. Thanks!

SUFFIX_PRIORITY_NAME = "priority"
SUFFIX_DIRECTION_NAME = "direction"

def _write_priorities_file(self, instance) -> None:
Copy link

Choose a reason for hiding this comment

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

What is instance? Is it worth documenting or is it Pyomo lingo?

Copy link
Author

Choose a reason for hiding this comment

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

Yup, it's standard (see _warm_start())

ORDFileSchema.ROW(byObject[id(child_var)], priority, child_var_direction)
)

ord_file.write(ORDFileSchema.FOOTER)
Copy link

Choose a reason for hiding this comment

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

Worth splitting this method up a bit? To me it seems like there's 2-3 steps here:

  1. input preparation
  2. translation
  3. writing to the file

It looks to me like some of this logic is not CPLEX specific so if we manage to split this method up into parts it'll most likely be easier to implement extra solvers later.

This is based on my assumption that it is mainly the translation that is solver specific.

' UP x11 2\n'
'ENDATA\n'
)

Copy link

Choose a reason for hiding this comment

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

For these tests, please use the priority constant instead of magic numbers. That'll make them easier to maintain, e.g. if we decide to represent them with different numbers or something.

And there is no ambiguity of what they mean.

Copy link
Author

Choose a reason for hiding this comment

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

Updated, however I felt this was a bit too convoluted for test_use_variable_priorities()

Comment on lines 236 to 251
assert priorities_file == (
'* ENCODING=ISO-8859-1\n'
'NAME Priority Order\n'
' x1 1\n'
' DN x2 2\n'
' DN x3 2\n'
' DN x4 2\n'
' DN x5 2\n'
' DN x6 2\n'
' DN x7 2\n'
' DN x8 2\n'
' DN x9 2\n'
' DN x10 2\n'
' UP x11 2\n'
'ENDATA\n'
)
Copy link

Choose a reason for hiding this comment

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

Perhaps this assertion is neater if you read the contents of priorities_file_name before doing the final solve and comparing that against priorities_file? They should be the same right?

Copy link
Author

Choose a reason for hiding this comment

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

Sure

- Update tests to avoid usage of "magic numbers" and reference `BranchDirection` where possible
@ruaridhw ruaridhw requested a review from mhusbyn February 21, 2020 12:16
@mhusbyn mhusbyn merged commit 97baa7a into master Feb 21, 2020
ruaridhw pushed a commit that referenced this pull request Mar 4, 2020
Remove Port.Conservative, be more selective for evar bounds
begunefeoglu pushed a commit to begunefeoglu/pyomo that referenced this pull request Apr 1, 2020
merge master into pynumero_mumps
begunefeoglu pushed a commit to begunefeoglu/pyomo that referenced this pull request Apr 1, 2020
Minor formatting changes to consolidate unix & mpi drivers
ruaridhw pushed a commit that referenced this pull request May 24, 2021
Tomasz-Kluczkowski pushed a commit that referenced this pull request Sep 16, 2021
Cleaning up sensitivity_toolbox tests
Tomasz-Kluczkowski pushed a commit that referenced this pull request Sep 16, 2021
Further minor polishing fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue Branching Priorities
2 participants