PyPSA / linopy

Linear optimization with N-D labeled arrays in Python
https://linopy.readthedocs.io
MIT License
165 stars 48 forks source link

Documentation on model debugging #109

Open willu47 opened 1 year ago

willu47 commented 1 year ago

I have been unable to find documentation specific to diagnosing issues with models.

I have found a few useful helper functions, such as model.variables.get_name_by_label() which returns the mapping from the numeric variable number found in the LP file to the variable name of the Python object.

Are there any other useful functions I should know about to help diagnose problems with an LP formulated in linopy (collating ideas here for a section in the documentation)?

Some ideas:

FabianHofmann commented 1 year ago

Good point! This is one of the most important things on the list of missing features. In meanwhile, if you use gurobi, you can use the function

m.compute_set_of_infeasible_constraints()

in case you ran into non-feasibility with the gurobi solver. But the inspection of a subset of constraints still remains mediocre. I put that on my list. Ideally, I would like people to not have to look into the LP file in case of infeasibilities, as they can be very long. So in the first step, I would focus on the data inspection of the linopy model.

willu47 commented 1 year ago

One incredibly hacky solution is to post-process the LP file with the constraint and variable names. I'm not proud of this, but it got the job done so I can compare the LP file of a test model from the original GNU MathProg implementation and the linopy. Perhaps this is a special case - porting a file, but inspection of a (small) LP file is very useful because it's one common file format almost all algebraic modelling languages can produce.

import re

<build your linopy.Model ``m``>

variable = re.compile('x[0-9]+')
constraint = re.compile('c[0-9]+')

with open('annotated_file.lp', 'w') as annotated:
    with open('original.lp', 'r') as raw_lp_file:
        for line in raw_lp_file:
            vars = variable.findall(line)
            cons = constraint.findall(line)
            for con in cons:
                label = int(con[1:])
                name = m.constraints.get_name_by_label(label)
                line = f"{name}:\n"
            for var in vars:
                label = int(var[1:])
                name = m.variables.get_name_by_label(label)
                line = line.replace(var, name)
            annotated.write(line)
FabianHofmann commented 1 year ago

Interesting workaround :) However that would not work for indexed variables / constraints, as far as I can see. It would only insert the name. But given your thoughts, we can definitely think about introducing (optional) annotations to the LP file.