-
-
Notifications
You must be signed in to change notification settings - Fork 89
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 Python API to get all constraints #161
Comments
Doing this would require to also extend the C++ API. I know how to do it and I can see the value in the feature but I would like @sccolbert opinion on such a change, before working on it. |
@sccolbert Any thoughts on this? |
I don’t think I fully understand what you are asking. You create the
constraints yourself before adding them to the solver. Could you just add
them to your own list at the same time?
…On Fri, May 19, 2023 at 03:18 Gulshan Singh ***@***.***> wrote:
@sccolbert <https://github.com/sccolbert> Any thoughts on this?
—
Reply to this email directly, view it on GitHub
<#161 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABBQSITTMZXLH6RU7YH3MLXG4UETANCNFSM6AAAAAAYBJ3A2M>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Yes, but in my codebase constraints are added in a number of different places, it would be a messy and intrusive change to keep track of all of them myself just for debugging. That's why if we could get access to the constraints from the solver, which already has a list of them, it would be much more convenient. |
Can you subclass the solver and override the add_constraint method?
…On Fri, May 19, 2023 at 09:50 Gulshan Singh ***@***.***> wrote:
Yes, but in my codebase constraints are added in a number of different
places, it would be a messy and intrusive change to keep track of all of
them myself just for debugging.
That's why if we could get access to the constraints from the solver,
which already has a list of them, it would be much more convenient.
—
Reply to this email directly, view it on GitHub
<#161 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABBQSP6AJWG7O4OOKDXEODXG6CC7ANCNFSM6AAAAAAYBJ3A2M>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I think in the meantime that's what I'll do. But if anyone else needs this functionality in the future, or if I need it for a different project, we'll have to reimplement it in those projects. Since this library already has access to the list of constraints, it makes more sense to me to have this exposed from the library itself than have users reimplement this. Is there any reason you're thinking of why we shouldn't do this? |
Related to this, other classes like |
If anyone else needs this: from typing import Optional
import kiwisolver
from kiwisolver import Constraint, UnsatisfiableConstraint, Variable
class Solver:
def __init__(self):
self._solver = kiwisolver.Solver()
self._constraints = set()
def _find(self, v: str) -> tuple[Optional[Variable], set[Constraint]]:
res = set()
res_strs = set()
var = None
for c in self._constraints:
for term in c.expression().terms():
if term.variable().name() == v:
var = term.variable()
# Skip duplicate constraints
if str(c) in res_strs:
continue
res_strs.add(str(c))
res.add(c)
return var, res
def add(self, c: Constraint) -> None:
self._constraints.add(c)
self._solver.addConstraint(c)
def remove(self, c: Constraint) -> None:
self._constraints.remove(c)
self._solver.removeConstraint(c)
def hasConstraint(self, c: Constraint) -> bool:
return self._solver.hasConstraint(c)
def update(self):
self._solver.updateVariables()
def analyze(
self, e: UnsatisfiableConstraint
) -> list[tuple[Variable, set[Constraint]]]:
variables = []
msg = str(e)
constraint, _ = msg.split(" | ")
op = None
for _op in [" <= ", " >= ", " == "]:
if _op in constraint:
op = _op
break
assert op is not None
expr, constant = constraint.split(op)
assert constant == "0"
terms = expr.split(" + ")
for term in terms:
res = term.split(" * ")
if len(res) == 2:
_, var = res
variables.append(self._find(var))
return variables Calling the |
I think this makes sense, actually - especially with |
The main reason why this doesn't already exist, is because the solver doesn't actually store the Python constraint object under the hood. It stores the C++ constraint object which is created internally when you create a constraint from Python. So in order to return a list of Python constraints from a method like If we were to add this, we would need to update the Python wrapper class to hold an internal list of the constraints, similar to what your sample code above is doing. We wouldn't make a change on the C++ implementation class. |
@gsingh93 Regarding the https://github.com/nucleic/kiwi/blob/main/py/src/solver.cpp#L59 |
At least I always thought it was the |
Got it, thanks for the explanation. I think this could still be valuable if it's disabled by default and enabled with some flag that makes it clear it's only for debugging, but it's also fine to not include it and people can just something similar to the wrapper code I posted above. Alternatively, it would be nice if there was some optional argument to
Awesome, thanks, I'll switch to using this. Would be nice to have a dedicated attribute like |
Yeah. Adding a `.value` attribute would require us to make a full-on
subclass for each exception object. The Python C-API has a simple function
for creating a "dumb" exception subclass with no new attributes or methods,
so that's what I used because it was quick and did the job at the time.
…On Sat, May 20, 2023 at 6:42 PM Gulshan Singh ***@***.***> wrote:
So in order to return a list of Python constraints from a method like
solver.constraints(), the solver would have to allocate new Python
constraint objects to wrap the C++ constraint objects. That would introduce
a few gotcha's when it comes to comparing objects via is.
Got it, thanks for the explanation. I think this could still be valuable
if it's disabled by default and enabled with some flag that makes it clear
it's only for debugging, but it's also fine to not include it and people
can just something similar to the wrapper code I posted above.
Alternatively, it would be nice if there was some optional argument to
dumps where I could just dump constraints related to the variables I
cared about, like in the wrapper code I posted above.
Regarding the UnsatisfiableConstraint. The constraint object should be
contained in the .value attribute of the exception. At least I always
thought it was the .value attribute. Whoops. Turns out it's .args[0].
Awesome, thanks, I'll switch to using this. Would be nice to have a
dedicated attribute like .value or .constraint so it's easier to discover
(I made an empty UnsatisifiableConstraint object and didn't see anything
related in __dir__ so I figured it may not exist).
—
Reply to this email directly, view it on GitHub
<#161 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABBQSJ73Q5ZC4GDPMLUGHLXHFJFHANCNFSM6AAAAAAYBJ3A2M>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
I know
solver.dumps
will print all of the constraints, but I would find it useful to be able to get a list ofConstraint
objects in Python. I'm finding debugging unsatisfied constraint issues difficult, and if I had access to the actual constraints I could automate some of the process, i.e. filtering to just the constraints affect the unsatisfied constraint, simplifying some of the output, etc.The text was updated successfully, but these errors were encountered: