FIX: pulp regression issue#907
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #907 +/- ##
=======================================
+ Coverage 85.6% 85.7% +0.1%
=======================================
Files 151 151
Lines 16310 16342 +32
=======================================
+ Hits 13967 14013 +46
+ Misses 2343 2329 -14
🚀 New features to boost your workflow:
|
|
@jGaboardi @martinfleis As a result, older, explicit constructors like pulp.LpVariable.dicts and pulp.LpConstraint(...) were completely removed or modified, throwing errors. I updated _matching.py to use a modern, version-agnostic approach:
This FORCES PuLP to build it's constraints automatically under the hood, regardless of the version installed. |
|
Thanks for this--I wasn't aware of the PuLP 4.0 revision... we will need to adjust quite a bit in pysal/spopt as well. This offers a solid solution here. In terms of strategy for libpysal, I think we should adopt the hasattr() check here, then pin the PuLP version after a few (2?) PySAL releases and remove the hasattr() check. Thoughts? |
|
yea, sounds like a good move to me. we can pin to pulp >= 4.0 after a couple of release cycles once the v4 API officially stabilizes. May I apply the same hasattr() fix for the spopt pulp fails too btw? |
|
Yes, I think so! There's not a clear migration guide yet (just a quick start for the new version), but in spopt we use LpVariable (and .value()) all over the place. Both look deprecated in 4.0... So that will definitely be a bigger set of changes, and possibly a major release with a hard change to PuLP 4.0 |
|
If we do this fix with via I am much more in favor of doing this with explicit version checking. For example, something like: from packaging.version import Version
import pulp
PULP4 = Version(pulp.__version__) > Version("4")
if PULP4:
# do `pulp==v4` logic
else:
# do `pulp==v3` logicThis may be a bit more verbose, but it is explicit as to why we have the logic and makes eventual removal easy to determine. Same for in |
|
@jGaboardi https://github.com/shubhammms/spopt/pull/1/changes The hasattr() actually also would use if/else, but the code gets wayyyy too confusing. i like your suggestion on: from packaging.version import Version
import pulp
PULP4 = Version(pulp.__version__) > Version("4")
if PULP4:
# do `pulp==v4` logicthe let's have a discussion on this on today's meet? |
|
great idea @jGaboardi, I think that's a better approach. I wasn't aware! |
| try: | ||
| import pulp | ||
| except ImportError as error: |
There was a problem hiding this comment.
Move the pulp4 = Version(pulp.__version__).major >= 4 here, these things are always on top after the imports for clarity. I would also make it a constant as per convention, so PULP_GE_4 (pulp greater or equal to 4). Also, I am not sure how this evaluates for alpha release, it may not return True.
There was a problem hiding this comment.
PULP_GE_4 with ruff ignoring -> done
but I don't think we should put pulp4 = Version(pulp.__version__).major >= 4 on top.
pulp is an optional dependency, we can't move the version check to module level- it would NameError on import for users without pulp installed.
Keeping it inside the function is intentional here.
But yes this suggestion is very much valid in spopt.
There was a problem hiding this comment.
I meant to move it right after importing pulp. Not on top of the file.
There was a problem hiding this comment.
aaaaah.
my bad. it's now done in the latest commit.
| indices=zip(row, col, strict=True), | ||
| cat="Continuous" if allow_partial_match else "Binary", | ||
| ) | ||
| match_vars = {} |
There was a problem hiding this comment.
I'm curious about the need for a for-loop here. Is there no equivalent dicts functionality in pulp4 for batch variable addition?
There was a problem hiding this comment.
LpVariable.dicts() names variables using the key directly in the string, so we'd get keys like "match_(0, 1)" instead of tuple keys (i, j).
And the rest of the code indexes match_vars[i, j] as a tuple.
Refactoring around that would add more complexity than the for-loop i think?
But let me if there's a cleaner way you have in mind..
and yeah, there IS addVariables() in pulp4, but it just takes already-created LpVariable objects, it's not a true batch creation equivalent.
The closest would still be LpVariable.dicts() + addVariables(), but that gives string keys like "match_(0,_1)" instead of the tuple keys (i, j)
There was a problem hiding this comment.
I think we need to hold off on changing this unill they stabilize the 4.0 API.
I've just pulled down 4.0.0a3, which adds a LpProblem.add_variable_dicts() method, which does what we need it to do.
Let's wait until things stabilize with the 4.0 release, and move to the equivalent functionality then Let's keep this PR open for now, but it's very likely our implementation will not change this radically.
There was a problem hiding this comment.
I second @ljwolf sentiment here and over in pysal/spopt#527. Well this ride here and allow dev CI to fail -- no harm, no foul.
There was a problem hiding this comment.
@FirePheonix -- Let's mark this as Draft so it doesn't accidentally get merged. Same for pysal/spopt#527
| ) | ||
| sense = int(not allow_partial_match) | ||
| mp += pulp.LpConstraint(summand, sense=sense, rhs=n_matches) | ||
| if sense == 1: |
There was a problem hiding this comment.
Same sentiment here as above. Did pulp4 really change the API that dramatically?
pyteston your changes. Continuous integration will be run on all PRs with GitHub Actions, but it is good practice to test changes locally prior to a making a PR.pysal/masterbranch.docstrings and
unittests?
reviewer and added relevant labels
This is a draft PR for testing if the PULP issue can be fixed with the following code change.
The error type object 'LpVariable' has no attribute 'dicts' indicates that the specific PuLP version or environment in CI is failing to expose the
dictshelper method on the LpVariable class.