Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Use multiple dispatch to simplify sets #2979

Open
wants to merge 13 commits into from

6 participants

@mrocklin
Collaborator

Various functions for the simplification of Unions and Intersections are defined using multiple dispatch.

Simplification Process

Union/Intersection construction proceeds in the following steps:

  1. Construct Union/Intersection with full args
  2. Flatten / perform trivial/always wanted simplifications
  3. Call Union/Intersection.reduce which reduces the arguments in the following two ways
    • Performs known global simplifications (Intersection that contains EmptySet is EmptySet)
    • Calls multiply dispatched function union/intersectoin_simp on pairs of arguments

Performance

Sets already had this design, they just did double dispatch internally with lots of if other.is_Foo checks. This just makes the typing more explicit. No tests were altered for this PR.

@mrocklin mrocklin referenced this pull request
Open

Multiple dispatch #2972

@asmeurer
Owner

Does the dispatcher know that union_simp(a, b) is the same as union_simp(b, a)?

@asmeurer
Owner

I guess one disadvantage of this is that it moves the code away from the class that uses it. In some cases this is good, because the code is not about one class but two, but in others, it really belongs on the class itself somehow. What's a good way to do this? Maybe use a staticmethod?

@moorepants
Collaborator

What about the new dependency? SymPy currently has no required external dependencies. Does this add one?

@mrocklin
Collaborator

Does the dispatcher know that union_simp(a, b) is the same as union_simp(b, a)?

No, I think that this is outside of the scope of dispatch. This is handled in Union.reduce and Intersection.reduce. Also, just to be clear, these reduce methods have no relation to the builtin reduce.

@mrocklin
Collaborator

I guess one disadvantage of this is that it moves the code away from the class that uses it. In some cases this is good, because the code is not about one class but two, but in others, it really belongs on the class itself somehow. What's a good way to do this? Maybe use a staticmethod?

I think I disagree about there being cases in which the behavior belongs on the class itself. To me these are special cases. I also think that it's equally valid to group the implementations by operation (having all union_simp functions in one place) rather than grouping them by type (having all FiniteSet methods in one place.)

@mrocklin
Collaborator

What about the new dependency? SymPy currently has no required external dependencies. Does this add one?

This pull request does and so should not be merged before we deal with that. Personally I would like for us to start using external dependencies (I think that they were a good idea) but I don't expect this to happen. We could bundle a version of multipledispatch with sympy or use some custom implementation like in the other PR.

@asmeurer
Owner

No, I think that this is outside of the scope of dispatch. This is handled in Union.reduce and Intersection.reduce. Also, just to be clear, these reduce methods have no relation to the builtin reduce

Maybe it should be. It seems like a pretty common case to handle, and not so easy to get right.

I think I disagree about there being cases in which the behavior belongs on the class itself. To me these are special cases. I also think that it's equally valid to group the implementations by operation (having all union_simp functions in one place) rather than grouping them by type (having all FiniteSet methods in one place.)

I'm also thinking of the case where a third party writes a class and wants to implement all the methods for itself.

@certik
Owner

I think that this simplifies the code quite a bit!

What are arguments against merging this PR and start using this more in SymPy?

@moorepants
Collaborator

One is the dependency issue. But I think I agree with @mrocklin in that we should have dependencies in SymPy especially if they are pure python libs. Installing pure Python libraries is very straight forward.

@certik
Owner

Deps: It's a big change obviously, but if this is what most people in our community would prefer, I am fine with that. I don't want to stop progress because of this.

So there are no other issues with this PR?

@mrocklin
Collaborator

Well, it's not mergable and travis fails, but in principle yes I think this PR is conceptually fine. There isn't actually much of a change here. I'm just swapping out a custom dispatch system for multipledispatch. Given the new way of doing things one might eventually clean up the system by which set simplification routines are applied (Aaron mentioned something along these lines earlier I think) so there is more work that could be done in a future PR.

But adding external dependencies is a big change, one that probably warrants a discussion outside of this PR. Good arguments against this particular change is that it injects a dependency into a core module where we had a solution that was working just fine. I do think that multipledispatch is generally a good thing for SymPy, but it isn't very helpful if I'm the only one who thinks so. We should probably also have a policy in place about the sorts of dependencies that we accept.

@certik
Owner

Yes, deps need to be discussed on our main list anyway. In general, the argument for hard dependencies is that those should be libraries that are widely used (by lots of projects and people) and are well supported, alternatively if they are just legacy code, that is available in major distributions and doesn't need changes.

To boost usage of your library, do you have some ideas which other projects might find this useful? Maybe we can write some PRs for them as well. If it's used by a few projects, then I think we can depend on it.

@mrocklin
Collaborator

It's a good idea. My argument for dependence is mostly based on ease of installation caused by package managers like easy_install, pip, and conda. To the extent that these tools are popular I think that dependencies are mostly free. Unfortunately I don't know how widespread these are.

Unfortunately I'm also certain that Aaron will jump in with a few reasons why dependencies are not mostly free even in the presence of the more widespread package managers :)

@moorepants
Collaborator

I guess the easy solution is to just bundle the multidispatch lib, just like we do with mpmath. But I only agree that external hard dependencies are generally fine if they are pure python libs. As soon as the lib requires compilation and/or linking then we open the can of worms that projects like conda, pip wheels, distributions, and package managers are still trying to solve.

@mrocklin
Collaborator

I think that we should unbundle mpmath.

I agree that we should stick to pure python. We would also need dependencies to support 2.6+, 3.2+.

@certik
Owner

Deps are not free even for pure Python libs, e.g. on Windows. Not everybody is using Conda yet. Mpmath doesn't exactly satisfy "used by lots of projects" or "active development team". Anyway, let's concentrate on the issue at hand, which is multidispatch. Let's simply start using it, one way or another.

@moorepants
Collaborator

Yeah, Windows may not be favorable for adding these dependencies. For "free" deps I'm assuming that you at least have the ability to run easy_install and that the project is on PyPi.

The decision to include a dependency is different than whether to have external dependencies at all. If mpmath is not used by many and isn't maintained, then maybe it shouldn't be a dependency at all, regardless if it is external or bundled.

@certik
Owner

Mpmath is not maintained less than any other code in SymPy. But probably would be maintained less if it was external.

@moorepants
Collaborator

I see. We can move the conversation to #7339 (comment).

@mrocklin
Collaborator

For what it's worth this PR now passes tests. The main block is the multipledispatch dependency.

@certik
Owner

This PR is +1 from me, except we have to figure out about the new dependency.

sympy/core/sets.py
((19 lines not shown))
+ return None
+ if a.args[0] == b.args[0]:
+ return a.args[0] * Union(ProductSet(a.args[1:]),
+ ProductSet(b.args[1:]))
+ if a.args[-1] == b.args[-1]:
+ return Union(ProductSet(a.args[:-1]),
+ ProductSet(b.args[:-1])) * a.args[-1]
+ return None
+
+
+@dispatch(Interval, Interval)
+def union_simp(a, b):
+ """
+ This function should only be used internally
+
+ See Set._union for docstring

oops. Seems to be old (and wrong) copy-paste.

@mrocklin Collaborator

Fixed in recent commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 1, 2014
  1. @mrocklin
  2. @mrocklin
  3. @mrocklin

    add pip install multipledispatch to travis.yml

    mrocklin authored
    This is temporary, just for testing in a PR.
    Please remind me to rebase this out before merger (if that ever happens)
  4. @mrocklin
Commits on Mar 2, 2014
  1. @mrocklin
  2. @mrocklin
Commits on Mar 26, 2014
  1. @mrocklin

    Merge branch 'master' into multiple-dispatch-play

    mrocklin authored
    Conflicts:
    	sympy/core/sets.py
Commits on Mar 28, 2014
  1. @mrocklin

    install multipledispatch

    mrocklin authored
Commits on Mar 30, 2014
  1. @mrocklin
  2. @mrocklin
  3. @mrocklin

    remove old docstring

    mrocklin authored
Commits on Apr 1, 2014
  1. @mrocklin
Commits on Apr 2, 2014
  1. @mrocklin
This page is out of date. Refresh to see the latest.
View
1  .travis.yml
@@ -118,6 +118,7 @@ before_install:
sudo apt-get install sagemath-upstream-binary;
fi
install:
+ - pip install multipledispatch --use-mirrors;
- if [[ "${TEST_SAGE}" != "true" ]]; then
python setup.py install;
fi
View
531 sympy/core/sets.py
@@ -13,6 +13,7 @@
from sympy.logic.boolalg import And, Or, true, false
from sympy.utilities import default_sort_key
+from sympy.dispatch import dispatch
class Set(Basic):
@@ -87,34 +88,6 @@ def intersect(self, other):
"""
return Intersection(self, other)
- def _intersect(self, other):
- """
- This function should only be used internally
-
- self._intersect(other) returns a new, intersected set if self knows how
- to intersect itself with other, otherwise it returns None
-
- When making a new set class you can be assured that other will not
- be a Union, FiniteSet, or EmptySet
-
- Used within the Intersection class
- """
- return None
-
- def _union(self, other):
- """
- This function should only be used internally
-
- self._union(other) returns a new, joined set if self knows how
- to join itself with other, otherwise it returns None.
- It may also return a python set of SymPy Sets if they are somehow
- simpler. If it does this it must be idempotent i.e. the sets returned
- must return None with _union'ed with each other
-
- Used within the Union class
- """
- return None
-
@property
def complement(self):
"""
@@ -410,32 +383,6 @@ def _contains(self, element):
return False
return And(*[set.contains(item) for set, item in zip(self.sets, element)])
- def _intersect(self, other):
- """
- This function should only be used internally
-
- See Set._intersect for docstring
- """
- if not other.is_ProductSet:
- return None
- if len(other.args) != len(self.args):
- return S.EmptySet
- return ProductSet(a.intersect(b)
- for a, b in zip(self.sets, other.sets))
-
- def _union(self, other):
- if not other.is_ProductSet:
- return None
- if len(other.args) != len(self.args):
- return None
- if self.args[0] == other.args[0]:
- return self.args[0] * Union(ProductSet(self.args[1:]),
- ProductSet(other.args[1:]))
- if self.args[-1] == other.args[-1]:
- return Union(ProductSet(self.args[:-1]),
- ProductSet(other.args[:-1])) * self.args[-1]
- return None
-
@property
def sets(self):
return self.args
@@ -619,89 +566,6 @@ def right_open(self):
"""
return self._args[3]
- def _intersect(self, other):
- """
- This function should only be used internally
-
- See Set._intersect for docstring
- """
- # We only know how to intersect with other intervals
- if not other.is_Interval:
- return None
- # We can't intersect [0,3] with [x,6] -- we don't know if x>0 or x<0
- if not self._is_comparable(other):
- return None
-
- empty = False
-
- if self.start <= other.end and other.start <= self.end:
- # Get topology right.
- if self.start < other.start:
- start = other.start
- left_open = other.left_open
- elif self.start > other.start:
- start = self.start
- left_open = self.left_open
- else:
- start = self.start
- left_open = self.left_open or other.left_open
-
- if self.end < other.end:
- end = self.end
- right_open = self.right_open
- elif self.end > other.end:
- end = other.end
- right_open = other.right_open
- else:
- end = self.end
- right_open = self.right_open or other.right_open
-
- if end - start == 0 and (left_open or right_open):
- empty = True
- else:
- empty = True
-
- if empty:
- return S.EmptySet
-
- return Interval(start, end, left_open, right_open)
-
- def _union(self, other):
- """
- This function should only be used internally
-
- See Set._union for docstring
- """
- if other.is_Interval and self._is_comparable(other):
- from sympy.functions.elementary.miscellaneous import Min, Max
- # Non-overlapping intervals
- end = Min(self.end, other.end)
- start = Max(self.start, other.start)
- if (end < start or
- (end == start and (end not in self and end not in other))):
- return None
- else:
- start = Min(self.start, other.start)
- end = Max(self.end, other.end)
-
- left_open = ((self.start != start or self.left_open) and
- (other.start != start or other.left_open))
- right_open = ((self.end != end or self.right_open) and
- (other.end != end or other.right_open))
-
- return Interval(start, end, left_open, right_open)
-
- # If I have open end points and these endpoints are contained in other
- if ((self.left_open and other.contains(self.start) is True) or
- (self.right_open and other.contains(self.end) is True)):
- # Fill in my end points and return
- open_left = self.left_open and self.start not in other
- open_right = self.right_open and self.end not in other
- new_self = Interval(self.start, self.end, open_left, open_right)
- return set((new_self, other))
-
- return None
-
@property
def _complement(self):
a = Interval(S.NegativeInfinity, self.start, True, not self.left_open)
@@ -888,54 +752,10 @@ def flatten(arg):
# Reduce sets using known rules
if evaluate:
- return Union.reduce(args)
+ return simplify_union(args)
return Basic.__new__(cls, *args)
- @staticmethod
- def reduce(args):
- """
- Simplify a Union using known rules
-
- We first start with global rules like
- 'Merge all FiniteSets'
-
- Then we iterate through all pairs and ask the constituent sets if they
- can simplify themselves with any other constituent
- """
-
- # ===== Global Rules =====
- # Merge all finite sets
- finite_sets = [x for x in args if x.is_FiniteSet]
- if len(finite_sets) > 1:
- finite_set = FiniteSet(x for set in finite_sets for x in set)
- args = [finite_set] + [x for x in args if not x.is_FiniteSet]
-
- # ===== Pair-wise Rules =====
- # Here we depend on rules built into the constituent sets
- args = set(args)
- new_args = True
- while(new_args):
- for s in args:
- new_args = False
- for t in args - set((s,)):
- new_set = s._union(t)
- # This returns None if s does not know how to intersect
- # with t. Returns the newly intersected set otherwise
- if new_set is not None:
- if not isinstance(new_set, set):
- new_set = set((new_set, ))
- new_args = (args - set((s, t))).union(new_set)
- break
- if new_args:
- args = new_args
- break
-
- if len(args) == 1:
- return args.pop()
- else:
- return Union(args, evaluate=False)
-
@property
def _inf(self):
# We use Min so that sup is meaningful in combination with symbolic
@@ -1098,7 +918,7 @@ def flatten(arg):
# Reduce sets using known rules
if evaluate:
- return Intersection.reduce(args)
+ return simplify_intersection(args)
return Basic.__new__(cls, *args)
@@ -1134,63 +954,6 @@ def __iter__(self):
raise ValueError("None of the constituent sets are iterable")
- @staticmethod
- def reduce(args):
- """
- Simplify an intersection using known rules
-
- We first start with global rules like
- 'if any empty sets return empty set' and 'distribute any unions'
-
- Then we iterate through all pairs and ask the constituent sets if they
- can simplify themselves with any other constituent
- """
-
- # ===== Global Rules =====
- # If any EmptySets return EmptySet
- if any(s.is_EmptySet for s in args):
- return S.EmptySet
-
- # If any FiniteSets see which elements of that finite set occur within
- # all other sets in the intersection
- for s in args:
- if s.is_FiniteSet:
- return s.__class__(x for x in s
- if all(x in other for other in args))
-
- # If any of the sets are unions, return a Union of Intersections
- for s in args:
- if s.is_Union:
- other_sets = set(args) - set((s,))
- other = Intersection(other_sets)
- return Union(Intersection(arg, other) for arg in s.args)
-
- # At this stage we are guaranteed not to have any
- # EmptySets, FiniteSets, or Unions in the intersection
-
- # ===== Pair-wise Rules =====
- # Here we depend on rules built into the constituent sets
- args = set(args)
- new_args = True
- while(new_args):
- for s in args:
- new_args = False
- for t in args - set((s,)):
- new_set = s._intersect(t)
- # This returns None if s does not know how to intersect
- # with t. Returns the newly intersected set otherwise
- if new_set is not None:
- new_args = (args - set((s, t))).union(set((new_set, )))
- break
- if new_args:
- args = new_args
- break
-
- if len(args) == 1:
- return args.pop()
- else:
- return Intersection(args, evaluate=False)
-
def as_relational(self, symbol):
"""Rewrite an Intersection in terms of equalities and logic operators"""
return And(*[set.as_relational(symbol) for set in self.args])
@@ -1222,9 +985,6 @@ class EmptySet(with_metaclass(Singleton, Set)):
"""
is_EmptySet = True
- def _intersect(self, other):
- return S.EmptySet
-
@property
def _complement(self):
return S.UniversalSet
@@ -1242,9 +1002,6 @@ def as_relational(self, symbol):
def __len__(self):
return 0
- def _union(self, other):
- return other
-
def __iter__(self):
return iter([])
@@ -1282,9 +1039,6 @@ class UniversalSet(with_metaclass(Singleton, Set)):
is_UniversalSet = True
- def _intersect(self, other):
- return other
-
@property
def _complement(self):
return S.EmptySet
@@ -1299,9 +1053,6 @@ def _contains(self, other):
def as_relational(self, symbol):
return True
- def _union(self, other):
- return self
-
@property
def _boundary(self):
return EmptySet()
@@ -1348,33 +1099,6 @@ def __new__(cls, *args, **kwargs):
def __iter__(self):
return iter(self.args)
- def _intersect(self, other):
- """
- This function should only be used internally
-
- See Set._intersect for docstring
- """
- if isinstance(other, self.__class__):
- return self.__class__(*(self._elements & other._elements))
- return self.__class__(el for el in self if el in other)
-
- def _union(self, other):
- """
- This function should only be used internally
-
- See Set._union for docstring
- """
- if other.is_FiniteSet:
- return FiniteSet(*(self._elements | other._elements))
-
- # If other set contains one of my elements, remove it from myself
- if any(other.contains(x) is True for x in self):
- return set((
- FiniteSet(x for x in self if other.contains(x) is not True),
- other))
-
- return None
-
def _contains(self, other):
"""
Tests whether an element, other, is in the set.
@@ -1532,3 +1256,252 @@ def imageset(*args):
return r
return ImageSet(f, set)
+
+
+def simplify_union(args):
+ """
+ Simplify a Union using known rules
+
+ We first start with global rules like 'Merge all FiniteSets'
+
+ Then we iterate through all pairs and ask the constituent sets if they
+ can simplify themselves with any other constituent. This process depends
+ on _simplify_union(a, b) functions.
+ """
+
+ # ===== Global Rules =====
+ # Merge all finite sets
+ finite_sets = [x for x in args if x.is_FiniteSet]
+ if len(finite_sets) > 1:
+ finite_set = FiniteSet(x for set in finite_sets for x in set)
+ args = [finite_set] + [x for x in args if not x.is_FiniteSet]
+
+ # ===== Pair-wise Rules =====
+ # Here we depend on rules built into the constituent sets
+ args = set(args)
+ new_args = True
+ while(new_args):
+ for s in args:
+ new_args = False
+ for t in args - set((s,)):
+ new_set = _simplify_union(s, t)
+ # This returns None if s does not know how to intersect
+ # with t. Returns the newly intersected set otherwise
+ if new_set is not None:
+ if not isinstance(new_set, set):
+ new_set = set((new_set, ))
+ new_args = (args - set((s, t))).union(new_set)
+ break
+ if new_args:
+ args = new_args
+ break
+
+ if len(args) == 1:
+ return args.pop()
+ else:
+ return Union(args, evaluate=False)
+
+
+@dispatch(EmptySet, Set)
+def _simplify_union(a, b):
+ return b
+
+
+@dispatch(UniversalSet, Set)
+def _simplify_union(a, b):
+ return a
+
+
+@dispatch(ProductSet, ProductSet)
+def _simplify_union(a, b):
+ if len(b.args) != len(a.args):
+ return None
+ if a.args[0] == b.args[0]:
+ return a.args[0] * Union(ProductSet(a.args[1:]),
+ ProductSet(b.args[1:]))
+ if a.args[-1] == b.args[-1]:
+ return Union(ProductSet(a.args[:-1]),
+ ProductSet(b.args[:-1])) * a.args[-1]
+ return None
+
+
+@dispatch(Interval, Interval)
+def _simplify_union(a, b):
+ if a._is_comparable(b):
+ from sympy.functions.elementary.miscellaneous import Min, Max
+ # Non-overlapping intervals
+ end = Min(a.end, b.end)
+ start = Max(a.start, b.start)
+ if (end < start or
+ (end == start and (end not in a and end not in b))):
+ return None
+ else:
+ start = Min(a.start, b.start)
+ end = Max(a.end, b.end)
+
+ left_open = ((a.start != start or a.left_open) and
+ (b.start != start or b.left_open))
+ right_open = ((a.end != end or a.right_open) and
+ (b.end != end or b.right_open))
+
+ return Interval(start, end, left_open, right_open)
+
+
+@dispatch(Interval, Set)
+def _simplify_union(a, b):
+ # If I have open end points and these endpoints are contained in b
+ if ((a.left_open and b.contains(a.start) is True) or
+ (a.right_open and b.contains(a.end) is True)):
+ # Fill in my end points and return
+ open_left = a.left_open and a.start not in b
+ open_right = a.right_open and a.end not in b
+ new_a = Interval(a.start, a.end, open_left, open_right)
+ return set((new_a, b))
+
+ return None
+
+
+@dispatch(FiniteSet, FiniteSet)
+def _simplify_union(a, b):
+ return FiniteSet(*(a._elements | b._elements))
+
+
+@dispatch(FiniteSet, Set)
+def _simplify_union(a, b):
+ # If b set contains one of my elements, remove it from myself
+ if any(b.contains(x) is True for x in a):
+ return set((
+ FiniteSet(x for x in a if b.contains(x) is not True), b))
+
+ return None
+
+
+@dispatch(Set, Set)
+def _simplify_union(a, b):
+ return None
+
+
+def simplify_intersection(args):
+ """
+ Simplify an intersection using known rules
+
+ We first start with global rules like
+ 'if any empty sets return empty set' and 'distribute any unions'
+
+ Then we iterate through all pairs and ask the constituent sets if they
+ can simplify themselves with any other constituent
+ """
+
+ # ===== Global Rules =====
+ # If any EmptySets return EmptySet
+ if any(s.is_EmptySet for s in args):
+ return S.EmptySet
+
+ # If any FiniteSets see which elements of that finite set occur within
+ # all other sets in the intersection
+ for s in args:
+ if s.is_FiniteSet:
+ return s.__class__(x for x in s
+ if all(x in other for other in args))
+
+ # If any of the sets are unions, return a Union of Intersections
+ for s in args:
+ if s.is_Union:
+ other_sets = set(args) - set((s,))
+ other = Intersection(other_sets)
+ return Union(Intersection(arg, other) for arg in s.args)
+
+ # At this stage we are guaranteed not to have any
+ # EmptySets, FiniteSets, or Unions in the intersection
+
+ # ===== Pair-wise Rules =====
+ # Here we depend on rules built into the constituent sets
+ args = set(args)
+ new_args = True
+ while(new_args):
+ for s in args:
+ new_args = False
+ for t in args - set((s,)):
+ new_set = _simplify_intersection(s, t)
+ # This returns None if s does not know how to intersect
+ # with t. Returns the newly intersected set otherwise
+ if new_set is not None:
+ new_args = (args - set((s, t))).union(set((new_set, )))
+ break
+ if new_args:
+ args = new_args
+ break
+
+ if len(args) == 1:
+ return args.pop()
+ else:
+ return Intersection(args, evaluate=False)
+
+
+@dispatch(ProductSet, ProductSet)
+def _simplify_intersection(a, b):
+ if len(b.args) != len(a.args):
+ return S.EmptySet
+ return ProductSet(a.intersect(b)
+ for a, b in zip(a.sets, b.sets))
+
+
+@dispatch(Interval, Interval)
+def _simplify_intersection(a, b):
+ # We can't intersect [0,3] with [x,6] -- we don't know if x>0 or x<0
+ if not a._is_comparable(b):
+ return None
+
+ empty = False
+
+ if a.start <= b.end and b.start <= a.end:
+ # Get topology right.
+ if a.start < b.start:
+ start = b.start
+ left_open = b.left_open
+ elif a.start > b.start:
+ start = a.start
+ left_open = a.left_open
+ else:
+ start = a.start
+ left_open = a.left_open or b.left_open
+
+ if a.end < b.end:
+ end = a.end
+ right_open = a.right_open
+ elif a.end > b.end:
+ end = b.end
+ right_open = b.right_open
+ else:
+ end = a.end
+ right_open = a.right_open or b.right_open
+
+ if end - start == 0 and (left_open or right_open):
+ empty = True
+ else:
+ empty = True
+
+ if empty:
+ return S.EmptySet
+
+ return Interval(start, end, left_open, right_open)
+
+@dispatch(EmptySet, Set)
+def _simplify_intersection(a, b):
+ return S.EmptySet
+
+@dispatch(UniversalSet, Set)
+def _simplify_intersection(a, b):
+ return b
+
+@dispatch(FiniteSet, FiniteSet)
+def _simplify_intersection(a, b):
+ return FiniteSet(*(a._elements & b._elements))
+
+@dispatch(FiniteSet, Set)
+def _simplify_intersection(a, b):
+ return FiniteSet(el for el in a if el in b)
+
+@dispatch(Set, Set)
+def _simplify_intersection(a, b):
+ return None
View
5 sympy/dispatch.py
@@ -0,0 +1,5 @@
+from multipledispatch import dispatch
+from functools import partial
+
+namespace = dict()
+dispatch = partial(dispatch, namespace=namespace)
View
92 sympy/sets/fancysets.py
@@ -7,6 +7,7 @@
from sympy.core.symbol import symbols
from sympy.core.sympify import sympify
from sympy.core.decorators import deprecated
+from sympy.dispatch import dispatch
class Naturals(with_metaclass(Singleton, Set)):
@@ -41,12 +42,6 @@ class Naturals(with_metaclass(Singleton, Set)):
_inf = S.One
_sup = S.Infinity
- def _intersect(self, other):
- if other.is_Interval:
- return Intersection(
- S.Integers, other, Interval(self._inf, S.Infinity))
- return None
-
def _contains(self, other):
from sympy.assumptions.ask import ask, Q
if ask(Q.positive(other)) and ask(Q.integer(other)):
@@ -114,13 +109,6 @@ class Integers(with_metaclass(Singleton, Set)):
is_iterable = True
- def _intersect(self, other):
- from sympy.functions.elementary.integers import floor, ceiling
- if other.is_Interval and other.measure < S.Infinity:
- s = Range(ceiling(other.left), floor(other.right) + 1)
- return s.intersect(other) # take out endpoints if open interval
- return None
-
def _contains(self, other):
from sympy.assumptions.ask import ask, Q
if ask(Q.integer(other)):
@@ -276,37 +264,6 @@ def __new__(cls, *args):
stop = property(lambda self: self.args[1])
step = property(lambda self: self.args[2])
- def _intersect(self, other):
- from sympy.functions.elementary.integers import floor, ceiling
- from sympy.functions.elementary.miscellaneous import Min, Max
- if other.is_Interval:
- osup = other.sup
- oinf = other.inf
- # if other is [0, 10) we can only go up to 9
- if osup.is_integer and other.right_open:
- osup -= 1
- if oinf.is_integer and other.left_open:
- oinf += 1
-
- # Take the most restrictive of the bounds set by the two sets
- # round inwards
- inf = ceiling(Max(self.inf, oinf))
- sup = floor(Min(self.sup, osup))
- # if we are off the sequence, get back on
- off = (inf - self.inf) % self.step
- if off:
- inf += self.step - off
-
- return Range(inf, sup + 1, self.step)
-
- if other == S.Naturals:
- return self._intersect(Interval(1, S.Infinity))
-
- if other == S.Integers:
- return self
-
- return None
-
def _contains(self, other):
from sympy.assumptions.ask import ask, Q
return (other >= self.inf and other <= self.sup and
@@ -339,3 +296,50 @@ def _sup(self):
@property
def _boundary(self):
return self
+
+
+@dispatch(Naturals, Interval)
+def _simplify_intersection(a, b):
+ return Intersection(S.Integers, b, Interval(a._inf, S.Infinity))
+
+
+@dispatch(Integers, Interval)
+def _simplify_intersection(a, b):
+ from sympy.functions.elementary.integers import floor, ceiling
+ if b.measure < S.Infinity:
+ s = Range(ceiling(b.left), floor(b.right) + 1)
+ return s.intersect(b) # take out endpoints if open interval
+
+
+@dispatch(Range, Interval)
+def _simplify_intersection(a, b):
+ from sympy.functions.elementary.integers import floor, ceiling
+ from sympy.functions.elementary.miscellaneous import Min, Max
+ osup = b.sup
+ oinf = b.inf
+ # if b is [0, 10) we can only go up to 9
+ if osup.is_integer and b.right_open:
+ osup -= 1
+ if oinf.is_integer and b.left_open:
+ oinf += 1
+
+ # Take the most restrictive of the bounds set by the two sets
+ # round inwards
+ inf = ceiling(Max(a.inf, oinf))
+ sup = floor(Min(a.sup, osup))
+ # if we are off the sequence, get back on
+ off = (inf - a.inf) % a.step
+ if off:
+ inf += a.step - off
+
+ return Range(inf, sup + 1, a.step)
+
+
+@dispatch(Range, Naturals)
+def _simplify_intersection(a, b):
+ return _simplify_intersection(a, Interval(1, S.Infinity))
+
+
+@dispatch(Range, Integers)
+def _simplify_intersection(a, b):
+ return a
Something went wrong with that request. Please try again.