PyPSA / linopy

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

MOSEK license token retained on import #197

Closed jotaigna closed 8 months ago

jotaigna commented 8 months ago

Dear developers I have noticed a small issue when MOSEK is set up with a remote token license server. If available, linopy will add MOSEK to the available solvers list in line 57 of the current version of linopy/solvers.py. Solvers seem to be added if their python module can be successfully be imported. For MOSEK, this import has the additional step of creating an optimiser task and optimising it.

with contextlib.suppress(ImportError):
    import mosek

    with mosek.Task() as m: 
        m.optimize() #<- This checks out a license and retains it for as long as Model is in memory

    available_solvers.append("mosek")

As it stands, this has the unfortunate side effect of checking out one license token and retaining it for as long as Model is imported.

This token is not usable to solve models in the same session, because typically one would create new instances of model as

m = Model()

but this is a different instance and, at least in my testing, I have found it requires checking out its own license token. It would seem the class itself is checking out and retaining one license token. In fact if the import took out the last (or only) token left, no more optimisations can be started. Perhaps this additional step might have been conceived to double check the successful import of mosek, but I have tested commenting it out and the functionality of MOSEK seems unaffected (see below).

with contextlib.suppress(ImportError):
    import mosek

    #with mosek.Task() as m: 
    #    m.optimize() #<- This checks out a license and retains it for as long as Model is in memory

    available_solvers.append("mosek")

Happy to put a pull request with the code above, if either my suggestion is sufficient or may start a more suitable solution idk. (maybe a different handling of context managers or a try/catch, etc).

Edit: Tested with Python 3.10.2, Mosek 10.1.16 and Linopy 0.3.1

Best regards

jotaigna commented 8 months ago

A follow up comment. Forcing the mosek Env to check in all unused license features as per below also works, supporting the hypothesis that a license token is retained at import. Perhaps this is more of an issue with how the creator/desctructor for the Env() object interacts with the garbage collector? (A Mosek issue more than a Linopy issue)

with contextlib.suppress(ImportError):
    import mosek

    with mosek.Env() as m:
        t = m.Task()
        t.optimize()
        m.checkinall()

    available_solvers.append("mosek")
FabianHofmann commented 8 months ago

Hey @jotaigna! Thanks for raising the issue. This sounds good. I have updated the master branch yesterday to be able to catch a mosek error if the licence is not there. I would be very happy about a PR that merges the approaches :)

Another topic: Since you are experienced with MOSEK, do you have at anytime encountered segmentation faults when running MOSEK on Linux machines? We have to partially disable the CI for MOSEK because of these non-traceable errors...

jotaigna commented 8 months ago

I have raised a pull request #199 I have had virtually no issues with Mosek on linux machines, but happy to take a look because if the seg fault can be reproduced we can dump the problem into a file and the Mosek team will surely like to know about it and fix it.

FabianHofmann commented 8 months ago

I have raised a pull request #199 I have had virtually no issues with Mosek on linux machines, but happy to take a look because if the seg fault can be reproduced we can dump the problem into a file and the Mosek team will surely like to know about it and fix it.

Okay, good to know, thanks!