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 SAS API interface #729

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Add SAS API interface #729

wants to merge 14 commits into from

Conversation

bolyu
Copy link

@bolyu bolyu commented Feb 2, 2024

Add API interface of SAS solvers.

@CLAassistant
Copy link

CLAassistant commented Feb 2, 2024

CLA assistant check
All committers have signed the CLA.

Copy link
Collaborator

@pchtsp pchtsp left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution. Please find some comments. I haven't checked everything but this should already be a good start.

I've also seen that the two python packages are available through pypi.

  1. Can you add the solver to the github CI? example:
    - name: Install gurobipy
    ?
  2. Can you add the solver to the testing suite? example:
    class MOSEKTest(BaseSolverTest.PuLPTest):
    ?

msg=msg,
)
self._sas = saspy.SASsession()
self.warmStart=warmStart
Copy link
Collaborator

Choose a reason for hiding this comment

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

warmstart can be passed to LpSolver and then becomes available inside self.optionsDict. See how it's used in other solvers. No need to assign it to the object.

self.warmStart=warmStart
# Only named options are allowed in SAS solvers.
self.solverOptions = {key:value for key, value in solverParams.items()}
def available(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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


return (f"{name}-pulp.{n}" for n in args)

def _delete_tmp_files(self, *args):
Copy link
Collaborator

Choose a reason for hiding this comment

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

there already exists a delete_tmp_files method that does almost exactly this. No need to create a new one. The same goes for _create_tmp_files

decompsubprob_str = self._create_statement_str("decompsubprob")
rootnode_str = self._create_statement_str("rootnode")

if lp.isMIP():
Copy link
Collaborator

Choose a reason for hiding this comment

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

simpler:

if lp.isMIP() and not self.mip:
    warnings.warn("SAS94 will solve the relaxed problem of the MILP instance.")

primalin=tmpMst,
postfix=postfix,
)
self.solverOptions["primalin"] = f"primalin{postfix}"
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's not a good practice to edit the solverOptions attribute. You should avoid editing during solve. It should be stateless.

self._delete_tmp_files(tmpMps, tmpMst)

# Store log and ODS output
self._log = res["LOG"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I do not see this property being defined anywhere: _log. Why do we need to store it inside self?

self._log = res["LOG"]
if self.msg:
print(self._log)
self._macro = dict(
Copy link
Collaborator

Choose a reason for hiding this comment

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

same goes for _macro. is it needed after the solving? why?

Copy link
Author

Choose a reason for hiding this comment

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

_macro is used to store some information returned by SAS solvers. It has information like primal and dual infeasibility, and SAS solution status. It might be interesting to SAS users.

raise PulpSolverError("PuLP: Error ({err_name}) \
while trying to solve the instance: {name}".format(
err_name=self._macro.get("STATUS", "ERROR"), name=lp.name))
status = self._read_solution(lp, primal_out, dual_out)
Copy link
Collaborator

Choose a reason for hiding this comment

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

what if there are no values available? should we read the soluton still?

Copy link
Author

Choose a reason for hiding this comment

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

If no values available, the solution will be NaN.

except FileNotFoundError:
pass

def _write_sol(self, filename, vs):
Copy link
Collaborator

Choose a reason for hiding this comment

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

there are many methods that are common between the two solvers. Could we just inherit one from the other and get all those methods?

raise PulpSolverError("SASCAS : Objective sense should be min or max.")

status = None
with redirect_stdout(SASLogWriter(self.msg)) as self._log_writer:
Copy link
Collaborator

Choose a reason for hiding this comment

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

this part has many indentations and ifs. Can it be refactored so that it's easier to read? for example breaking it into other methods. re-using the code with different arguments, etc.

@pchtsp
Copy link
Collaborator

pchtsp commented Mar 21, 2024

Hello, I solved the conflicts with master. Now the tests are failing because you need to run the black linter as I previously said:

https://coin-or.github.io/pulp/develop/contribute.html#applying-the-black-linter-formatter

Copy link
Collaborator

@pchtsp pchtsp left a comment

Choose a reason for hiding this comment

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

Thanks.

I've left more comments again. You need to get your code to run in the pulp CI, so fix the black linter first.

If someone else from SAS can help you reviewing the python code before asking for a review here, that would be awesome. I do not have too much time to give the amount of feedback/ improvements that is needed for this PR.


def _create_statement_str(self, statement):
"""Helper function to create the strings for the statements of the proc OPTLP/OPTMILP code."""
stmt = self.solverOptions.pop(statement, None)
Copy link
Collaborator

Choose a reason for hiding this comment

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

please do not edit this property, it should be static. There are many places where it's edited in the code.

except:
return False

def toDict(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

(1) you have this function duplicated.
(2) you should not just copy the inherit method, you should call the LpSolver_CMD.toDict method and then add the functionality that you need.
(3) What is cas object and how do you expect to serialize it?

return True

def sasAvailable(self):
if self.cas == None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

self.cas is None instead

@phchri
Copy link

phchri commented Sep 16, 2024

I improved the PuLP interface with lessons learned from other interfaces, added the "with" options that other interfaces support (and a test for it) and improved performance of reading the output. @bolyu: Please review and test the changes.

@bolyu
Copy link
Author

bolyu commented Sep 16, 2024

I have reviewed and tested the changes. The updates look good to me.

@bolyu bolyu requested a review from pchtsp September 18, 2024 16:18
@pchtsp
Copy link
Collaborator

pchtsp commented Sep 19, 2024

thanks to both. The CI tests are still failing. see below. Are you sure the code works when there's no SAS solver around?
If no SAS solver is available, the solver should return available()=False, which doesn't seem to be the case because the libraries are installed.

 SAS process has terminated unexpectedly. Pid State= (2219, 64000)
Traceback (most recent call last):

  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/pulp/apis/sas_api.py", line 233, in __init__
    self.sas = saspy.SASsession(**self._saspy_options)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/saspy/sasbase.py", line 590, in __init__
    self._io = SASsessionSTDIO(sascfgname=self.sascfg.name, sb=self, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/saspy/sasiostdio.py", line 291, in __init__
    self._startsas()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/saspy/sasiostdio.py", line 512, in _startsas
    ll = self.submit("options svgtitle='svgtitle'; options validvarname=any validmemname=extend; ods graphics on;", "text")
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/saspy/sasiostdio.py", line 985, in submit
    raise SASIOConnectionTerminated(Exception)
saspy.sasexceptions.SASIOConnectionTerminated: No SAS process attached. SAS process has terminated unexpectedly.
<class 'Exception'>

@phchri
Copy link

phchri commented Sep 19, 2024

My bad, I assumed your machines would not have the SAS packages installed. I'll come up with another way of doing this next week.

@phchri
Copy link

phchri commented Sep 23, 2024

@bolyu I updated the interface classes, please review and verify the code on your side.

pulp/apis/sas_api.py Show resolved Hide resolved
pulp/apis/sas_api.py Outdated Show resolved Hide resolved
@phchri
Copy link

phchri commented Sep 24, 2024

@bolyu I adjust the code as you suggested.

@bolyu
Copy link
Author

bolyu commented Sep 24, 2024

@bolyu I adjust the code as you suggested.

The current version looks good.

@pchtsp Please rerun the CI tests. Thank you so much!

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.

4 participants