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

Unsupported bound type MI in pulp.LpProblem.fromMPS #791

Open
5 of 6 tasks
dwr-zroy opened this issue Feb 12, 2025 · 1 comment
Open
5 of 6 tasks

Unsupported bound type MI in pulp.LpProblem.fromMPS #791

dwr-zroy opened this issue Feb 12, 2025 · 1 comment

Comments

@dwr-zroy
Copy link
Contributor

dwr-zroy commented Feb 12, 2025

Details for the issue

What did you do?

I attempted to read an .mps file using pulp.LpProblem.fromMPS.

I discovered the issue when converting problems to pulp style .json files, and an .mps file containing an MI type bound caused pulp to error. MI bounds specify that the lower bound is -inf for a variable (see note E here)

MPS File bounds section (simplified):
BOUNDS
...
 MI bnd    d_losvq_trnsf                     
 UP bnd    d_losvq_trnsf    400
...
ENDATA

What did you expect to see?

Successful file read.

What did you see instead?

IndexError raised.

In the function readMPSSetBounds, a series of checks are made against the bound type. Bound types that do not specify a bound value are caught and handled separately (currently FR, BV, and PL are caught). If the type is not caught, the bound value is read from the lines object, which is a list.

Since the MI bound type does not specify a bound value, and it is not caught by the if statements, an index error is raised since the lines list is an unexpected length.

Proposed Solution

Adding an additional clause to readMPSSetBounds in pulp.mps_lp.

def readMPSSetBounds(line, variable_dict):
    bound = line[0]
    var_name = line[2]

    def set_one_bound(bound_type, value):
        variable_dict[var_name][BOUNDS_EQUIV[bound_type]] = value

    def set_both_bounds(value_low, value_up):
        set_one_bound("LO", value_low)
        set_one_bound("UP", value_up)

    if bound == "FR":
        set_both_bounds(None, None)
        return
    elif bound == "BV":
        set_both_bounds(0, 1)
        return
    elif bound == "PL":
        # bounds equal to defaults
        return
    elif bound == "MI":
        # Lower bound -inf
        set_one_bound("LO", None)
        return

    value = float(line[3])
    if bound in ["LO", "UP"]:
        set_one_bound(bound, value)
    elif bound == "FX":
        set_both_bounds(value, value)
    return

I am uncertain if this causes side effects, as I have not tested the solution against the unit-tests myself. I have successfully used this as a "patch" to finish my conversion effort.

I couldn't find any documentation discussing the support of MI bound types, so my assumption is that they are supported. If I am incorrect, disregard this issue.

Useful extra information

What operating system are you using?

  • Windows: (11, Version 10.0.22631 Build 22631)

I'm using python version:

  • 3.11

I installed PuLP via:

  • pypi (python -m pip install pulp)
  • Other: conda (conda environment, pulp is installed via pip, no other packages)

Did you also

@pchtsp
Copy link
Collaborator

pchtsp commented Feb 13, 2025

thanks! could you make a Pull Request with this change so that we can run the unit tests? and, if you have a small .mps file with this unsupported behavior, could you add it as a unit test?

Check here for an example:

pulp/pulp/tests/test_pulp.py

Lines 1153 to 1160 in c06fe08

def test_importMPS_PL_bound(self):
"""Import MPS file with PL bound type."""
with tempfile.NamedTemporaryFile(delete=False) as h:
h.write(str.encode(EXAMPLE_MPS_PL_BOUNDS))
_, problem = LpProblem.fromMPS(h.name)
os.unlink(h.name)
self.assertIsInstance(problem, LpProblem)

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

No branches or pull requests

2 participants