Skip to content

add support general nonlinear cons #134

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

Merged
merged 3 commits into from
Mar 2, 2018
Merged

Conversation

fserra
Copy link
Collaborator

@fserra fserra commented Mar 2, 2018

closes #41

inspired in SCIP.jl, pyomo and biogeme's expressions
@fserra fserra requested a review from rschwarz March 2, 2018 11:20
Copy link
Collaborator

@rschwarz rschwarz left a comment

Choose a reason for hiding this comment

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

Nice tests!

# We have different types of general expressions that in addition
# to the operation and list of children stores
# SumExpr: coefficients and constant
# ProdExpr: constant
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice documentation!

But what is the meaning of the constant for ProdExpr? Is it contant * child1 * child2 * ... ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes

@@ -11,21 +55,21 @@ def _is_number(e):

def _expr_richcmp(self, other, op):
if op == 1: # <=
if isinstance(other, Expr):
if isinstance(other, Expr) or isinstance(other, GenExpr):
Copy link
Collaborator

Choose a reason for hiding this comment

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

If Expr and GenExpr are mostly handled the same, then maybe it would be easier to use a common base class for both of them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, maybe, but I felt uncertain. The idea was to try to keep Expr as untouched as possible, given that it has been much more tested.

# nor we simplify common terms or do any other type of simplification.
# The `GenExpr` is pass as is to SCIP and SCIP will do what it see fits during presolving.
#
# TODO: All this is very complicated, so we might wanna unify Expr and GenExpr.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems to me like GenExpr is strictly more powerful than Expr, so Expr could be removed completely?
I don't know whether there is a difference in performance between these implementations...

Copy link
Collaborator

Choose a reason for hiding this comment

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

In any case, this step could also be done in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, this is not true, you can't ask information about GenExpr as you can with Expr, but if we do not care about that, then yes, it is more powerful.

I guess there are performance difference, I didn't really test this, but I would definitely do this in the future

# is no longer in place, might affect performance?
# can't do `self = buildGenExprObj(self) + other` since I get
# TypeError: Cannot convert pyscipopt.scip.SumExpr to pyscipopt.scip.Expr
return buildGenExprObj(self) + other
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 we do this instead:

return other + buildGenExprObj(self)

Would this be inplace (but for other's place)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am guessing that inplace means self's place. But yeah, this inplace stuff is a bit complicated and would also postpone it.

divisor = buildGenExprObj(other)
# we can't divide by 0
if divisor.getOp() == Operator.const:
assert divisor.number != 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think a Python programmer would expect a ValueError instead of an AssertionError, but I don't mind too much.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I changed this and added a test for this. thanks

'''turn it into a constraint'''
return _expr_richcmp(self, other, op)

def degree(self):
Copy link
Collaborator

Choose a reason for hiding this comment

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

why even add this method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

just to keep it compatible with the implementation of addCons in scip.pxd or whathever the termination is

ans.op = Operator.exp
ans.operatorIndex = Operator.operatorIndexDic[ans.op]
ans.children.append(buildGenExprObj(expr))
return ans
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do set all these attributes after creating an "empty" object, instead of using the UnaryExpr.__init__ method to initialize them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

what you suggest is way more elegant, will implement after lunch ;)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good, so I made one constructive suggestions ;-)

@@ -891,6 +891,8 @@ cdef class Model:
return self._addLinCons(cons, **kwargs)
elif deg <= 2:
return self._addQuadCons(cons, **kwargs)
elif deg == float('inf'): # general nonlinear
return self._addGenNonlinearCons(cons, **kwargs)
else:
return self._addNonlinearCons(cons, **kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

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

so this is for polynomials only?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

_addNonlinearCons is for polynomials only, yes

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would rather rename it to _addPolyCons() or something similar. It's quite unintuitive right now and these are only internal methods.

@@ -1008,6 +1010,100 @@ cdef class Model:
free(varexprs)
return PyCons

def _addGenNonlinearCons(self, ExprCons cons, **kwargs):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll have to believe you on this function...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

haha, this is basically a copy-paste of the one in CSIP

@codecov
Copy link

codecov bot commented Mar 2, 2018

Codecov Report

Merging #134 into master will increase coverage by 1.54%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master    #134      +/-   ##
=========================================
+ Coverage   87.15%   88.7%   +1.54%     
=========================================
  Files          18      19       +1     
  Lines        1059    1204     +145     
=========================================
+ Hits          923    1068     +145     
  Misses        136     136
Impacted Files Coverage Δ
tests/test_linexpr.py 100% <ø> (ø) ⬆️
src/pyscipopt/__init__.py 100% <100%> (ø) ⬆️
tests/test_expr.py 100% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 49a830d...e6227a4. Read the comment docs.

@mattmilten
Copy link
Collaborator

Shall we increase the version number to 1.4 or 2.0 after merging?

@fserra
Copy link
Collaborator Author

fserra commented Mar 2, 2018

I have no idea how the versioning works

@rschwarz
Copy link
Collaborator

rschwarz commented Mar 2, 2018

I have no idea how the versioning works

Apparently, the version is defined in __init__.py

@mattmilten
Copy link
Collaborator

Apparently, the version is defined in init.py

Haha, I guess, @fserra was referring to what the meaning of the version digits was - not how we can change the version. So, the question boils down to whether we want to consider this a major or a minor version jump. I say we call it 1.4 since we don't break the interface.

@mattmilten mattmilten merged commit acff07c into master Mar 2, 2018
@mattmilten mattmilten deleted the fs/general_nonlinear branch July 13, 2018 07:42
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.

Non Linear expressions
3 participants