-
Notifications
You must be signed in to change notification settings - Fork 270
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
Conversation
inspired in SCIP.jl, pyomo and biogeme's expressions
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 * ...
?
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
src/pyscipopt/expr.pxi
Outdated
divisor = buildGenExprObj(other) | ||
# we can't divide by 0 | ||
if divisor.getOp() == Operator.const: | ||
assert divisor.number != 0.0 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
src/pyscipopt/expr.pxi
Outdated
ans.op = Operator.exp | ||
ans.operatorIndex = Operator.operatorIndexDic[ans.op] | ||
ans.children.append(buildGenExprObj(expr)) | ||
return ans |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 ;)
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 Report
@@ 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
Continue to review full report at Codecov.
|
Shall we increase the version number to 1.4 or 2.0 after merging? |
I have no idea how the versioning works |
Apparently, the version is defined in |
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. |
closes #41