-
Notifications
You must be signed in to change notification settings - Fork 223
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
Adopt singleton semantics for globally defined constraint instances #1507
Adopt singleton semantics for globally defined constraint instances #1507
Conversation
Doh, thanks for fixing this issue! In retrospect we probably should have avoided the singletons and required the slightly less clever notation LGTM, but I defer to @fehiepsi |
On second thought, I think it would be cleaner to handle this as in general (rather than merely for pickling) by following the singleton pattern: class _Real(Constraint):
def __new__(cls):
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
... That way |
I don't have a strong opinion. |
I guess another advantage of constraints.real is copy.copy(constraints.real) And thanks for bringing up this issue! It's also a problem with |
If by experience private contracts are not respected in practice, then let's go with
For the record: Overriding
Interesting. I mostly work with |
Oh thanks for explaining, I didn't realize that! |
Thanks @pierreglaser (and @fritzo for chiming in)! Could you also add this logic or the |
Done. |
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.
LGTM! Thanks @pierreglaser!
Actually, the current logic provides no way to treat as singleton instances the constraints coming from parametrized classes and exposed as top-level variables of the positive = _GreaterThan(0.0) Treating them as complete singleton instances would require introspecting the arguments of the constructor and using a lookup-table mechanism to return instances already created for a given set of arguments. Preserving identity semantics during pickling (which is ultimately what this PR was for) can be done by adding a lookup table of all global variables of the Ultimately, I think addressing 1 (treating constraints a singleton when applicable) is more complex, and to be fair, out of scope for this PR, which originally addressed 2 (preserving identity semantics during pickling). So I'd rather rollback any changes made to treat |
(By the way, I can't parse the lint failure from the CI, any help would be appreciated) |
I believe that's fine. Here's my reasoning:
Thanks for thinking hard about this issue! |
I agree that, say, for any instance of the parametrized |
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.
Though it's convenient to have constraint is constraints.positive
, I feel that it is a better practice to use the check isinstance(constraint, constraints.greater_than)
for a couple of reasons:
- _TOPLEVEL_CONSTRAINTS is not exposed to users, so the semantic for user-defined constraints will be different from the "toplevel" ones,
- avoid deviating from Pyro/PyTorch (related issue Use unified type for distributions.constraint API pytorch/pytorch#50616)
I'm not sure how to tell users about the differences of real
and greater_than
. Probably we can add an attribute named is_singleton
which is False by default.
I did several grep
s and found that we didn't follow the expected behavior at those places:
All of these were for efficiency. I'll remove them in a separate PR.
I do see the parsimony of something like this: class _DependentSingleton:
def __new__(cls, *args): # TODO convert **kwargs to *args
# only cache simple python things
if not all(isinstance(arg, (int, float)) for arg in args):
return super().__new__(*args)
# weakly memoize
cache = cls.__dict__.setdefault("_instance_cache", WeakValueDictionary())
instance = cache.get(args)
if instance is None:
instance = super().__new__(*args)
cache[args] = instance
return instance but that's a bit complex and seems out of scope for your nice clean first PR. |
Alternatively, I guess we can also subclass those constraints, like
|
As such, this
IMO, defining equality/identity semantics for user-defined constraints should be left to the user defining them. To me, this sounds more understandable and uncontroversial than explaining that the two built-in
I like this solution a lot @fehiepsi :) it is more elegant than a global lookup table, and preserves identity semantics for top-level singleton objects, which I find more natural and concise than instance+attribute checking. If that sounds good to you, I'd happily implement this + singleton-style Long term, my suggestion would maybe be to expose a |
Regarding the (still open) pytorch/pytorch#50616, I don't believe that |
Agreed! I tried to add some logic to check for
LGTM. It's great to have this in 0.11.0 release. (not important: if possible, could you revert the checks for |
constraints.real
by referenceThere 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.
Looks great, thanks for making this module much more consistent!
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.
LGTM pending lint issue.
Addressed the linting issue. Some test related to |
Hi @pierreglaser, sorry, I'll take a closer look. It seems that in funsor, we use support's name for some logics. Maybe it's easiest to let me to add a patch and create an issue there. |
Closes #1378.