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 8 commits into
base: master
Choose a base branch
from
Open

Add SAS API interface #729

wants to merge 8 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

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.

None yet

3 participants