Skip to content
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

[Don't merge][GSoC]Added ArrayComprehension class #16845

Merged
merged 12 commits into from May 28, 2019

Conversation

Projects
None yet
6 participants
@kangzhiq
Copy link
Contributor

commented May 16, 2019

References to other Issues or PRs

Brief description of what is fixed or changed

Added ArrayComprehension class to generate a list comprehension which can accept symbols

Other comments

The implementation is not yet finished.
PR related to GSoC 2019

Release Notes

  • tensor
    • Added a new array object ArrayComprehension for list comprehension.

TODO list:

  • add unit tests
  • add doctests in the documentation string.

kangzhiq added some commits May 16, 2019

Initial ArrayComprehension class
- Added constructor, doit()
- _expand_aray() to be completed
@sympy-bot

This comment has been minimized.

Copy link

commented May 16, 2019

Hi, I am the SymPy bot (v147). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • tensor
    • Added a new array object ArrayComprehension for list comprehension. (#16845 by @kangzhiq)

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.5.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it.

Click here to see the pull request description that was parsed.

<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->


#### Brief description of what is fixed or changed
Added ArrayComprehension class to generate a list comprehension which can accept symbols

#### Other comments
The implementation is not yet finished. 
PR related to GSoC 2019

#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* tensor
  * Added a new array object ArrayComprehension for list comprehension.
<!-- END RELEASE NOTES -->


TODO list:

- [x] add unit tests
- [x] add doctests in the documentation string.

Update

The release notes on the wiki have been updated.


def _expand_array(self, limits):
print(limits)
return 0

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 17, 2019

Contributor

missing new line, tests won't pass.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 17, 2019

Author Contributor

Thank you for reviewing! I will correct the coding convention errors in next commit.

Show resolved Hide resolved sympy/tensor/array/array_comprehension.py Outdated
def __new__(cls, expr, *values, **assumptions):
if any(len(l) != 3 or None for l in values):
raise ValueError('ArrayComprehension requires values lower and upper bound'
'for the expression')

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 18, 2019

Member

give a space between ' and for like, ' for the expression')

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 18, 2019

Author Contributor

I am sorry that I didn't see your review, I will uodate it in next commit. Thanks a lot!!



def _expand_array(self, limits):
print(limits)

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 18, 2019

Member

shouldn't it be return limits?

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 18, 2019

Author Contributor

Yes, it is a fast test to verifier the output. I have update the implememntation of _extand_array, please feel free to review it. :-)

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

@kangzhiq are you using a debugger? You shouldn't use print to inspect variables. I suggest to use PyCharm to inspect the variables while running the code.

# Array will not ne expanded if there is a symbolic dimension
if any(i.is_symbol for i in [inf, sup]):
return self
else:

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 18, 2019

Member

else not needed. The control will automatically skip the else part if the if part is executed. In addition, using not i.is_number or i.atoms(Symbol) would be better otherwise, expressions like N + 1 will go for expansion rather than being unevaluated. You can write it as,

if Basic(inf, sup).atoms(Symbol):
    return self
self.expr = self._expand_array([inf, sup])

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 18, 2019

Author Contributor

That is a very valuable advice! Thank you! I will update it as you suggested!

kangzhiq added some commits May 18, 2019

_expand_array() completed
- Completed first version of _expand_array
_ Correct the coding convention errors
@czgdp1807

This comment has been minimized.

Copy link
Member

commented May 19, 2019

Add the test in sympy.core.tests.test_args.

arr[index] = arr[index].subs(var, val)
return arr.tolist()

# Recursive function to perform subs at every variable according to its boundary

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 19, 2019

Member

I think f should be having iterative logic as recursion is expensive than iteration in Python as there is always an allocation of new stack frame at each call leading to an overhead.
I believe that there is a possibility of converting this recursive logic into an iterative one. At least try doing.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 19, 2019

Author Contributor

Yes, you are right! I have an idea to implement it in an iterative way. I will update it asap. Thank you for reviewing!

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

Have you thought about the itertools library? itertools.product(range(...), range(...), ...) expands into the correct iterator.

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

I think f should be having iterative logic as recursion is expensive than iteration in Python as there is always an allocation of new stack frame at each call leading to an overhead.

It depends on the case, sometimes you need a stack even with iteration. Python is unable to optimize recursive functions (unlike other programming languages that remove recursion at compile time), so better avoid them.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 22, 2019

Author Contributor

so better avoid them.

I just implemented a ew version with only iterations. I will commit it.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 22, 2019

Author Contributor

I am afraid that itertools might not be very helpful, since it is an iteration over a list with Symbol. I will still thinking about it but after a first try, I didn't find a way to use it.

kangzhiq added some commits May 21, 2019

Added test for ArrayComprehension
- added test in test_args
- added test file for ArrayComprehension
@sylee957

This comment has been minimized.

Copy link
Member

commented May 22, 2019

I can see how the implementation looks like, after tests had been added.
I think that the term comprehension can be ambiguous, but from the python doc

A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses.

it states about for clause.
And I don't know if ArrayComprehension is using for loops.

It can be more correspondent to the Table in Mathematica, though.

@kangzhiq

This comment has been minimized.

Copy link
Contributor Author

commented May 22, 2019

@sylee957 Thank you for reviewing!

It can be more correspondent to the Table in Mathematica, though.

That Table module is exactly what I am trying to reproduce here. And yes, ArrayComprehension is using for loops.
For example ArrayComprehension(i, (i, 1, 5)) is equivalent to [i for i in range(1, 5)], except that the built-in list comprehension of Python doesn't accept Symbol, e.g. [i for i in range(1, n)] where n is a Symbol which value is unknown. So ArrayComprehension will be useful in this specific case.

arr[index] = arr[index].subs(var, val)
return arr.tolist()

# Recursive function to perform subs at every variable according to its boundary

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

Have you thought about the itertools library? itertools.product(range(...), range(...), ...) expands into the correct iterator.

Show resolved Hide resolved sympy/tensor/array/array_comprehension.py
arr[index] = arr[index].subs(var, val)
return arr.tolist()

# Recursive function to perform subs at every variable according to its boundary

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

I think f should be having iterative logic as recursion is expensive than iteration in Python as there is always an allocation of new stack frame at each call leading to an overhead.

It depends on the case, sometimes you need a stack even with iteration. Python is unable to optimize recursive functions (unlike other programming languages that remove recursion at compile time), so better avoid them.

assert b.subs(j, 3) == [1, 2, 3]
assert c.doit == [j, 2*j, 3*j]
c.add_bound((j, 2, 4))
assert c.doit() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

We need some tests for larger dimensions (e.g. 4, 5 variables).

return arr

# Add/replace a boudary of variable
def add_bound(self, bound):

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

SymPy methods are immutable. No edits after class constructor, otherwise bad things may happen (i.e. problems with hashing, term rewriter, etc...).

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 22, 2019

Author Contributor

I didn't know that. Thank for telling me :-)

assert isinstance(b.doit(), ArrayComprehension)
assert b.subs(j, 3) == [1, 2, 3]
assert c.doit == [j, 2*j, 3*j]
c.add_bound((j, 2, 4))

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

c cannot be modified after construction. This is a SymPy standard.

c = ArrayComprehension(i*j, (i, 1, 3))
assert a.doit() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]
assert isinstance(b.doit(), ArrayComprehension)
assert b.subs(j, 3) == [1, 2, 3]

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

why is .subs triggering the evaluation? I suppose it should create ArrayComprehension(i, (i, 1, 3)).

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 22, 2019

Author Contributor

I was thinking that .subs try do expand the array (calling a doit() inside) if all variable has a specific bound or value. It should be [3, 6, 9] instead, I made a mistake.

a = ArrayComprehension(i*j, (i, 1, 3), (j, 2, 4))
b = ArrayComprehension(i, (i, 1, j))
c = ArrayComprehension(i*j, (i, 1, 3))
assert a.doit() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 22, 2019

Contributor

can you also add assert isinstance(a.doit(), ImmutableDenseNDimArray)?

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 22, 2019

Author Contributor

ok!

@Upabjojr

This comment has been minimized.

Copy link
Contributor

commented May 22, 2019

Also, ArrayComprehension( ... ).free_symbols should not return the symbols being expanded in.

Implemented functions and added tests
- Implemented _expand_array with iteration
- Implemented _check_bounds_validity
- Added doctest
- Added test in test_array_comprehension

@kangzhiq kangzhiq changed the title Added ArrayComprehension class [Don't merge][GSoC]Added ArrayComprehension class May 22, 2019

@codecov

This comment has been minimized.

Copy link

commented May 22, 2019

Codecov Report

Merging #16845 into master will increase coverage by 0.017%.
The diff coverage is 91.752%.

@@              Coverage Diff              @@
##            master    #16845       +/-   ##
=============================================
+ Coverage   73.888%   73.905%   +0.017%     
=============================================
  Files          619       620        +1     
  Lines       159818    160120      +302     
  Branches     37519     37579       +60     
=============================================
+ Hits        118087    118338      +251     
- Misses       36266     36287       +21     
- Partials      5465      5495       +30
Show resolved Hide resolved sympy/tensor/array/array_comprehension.py Outdated
bounds = sympify(bounds)
for var, inf, sup in bounds:
if var not in expr.free_symbols:
raise ValueError('Varialbe {} does not exist in expression'.format(var))

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

why? what about generating constant arrays? I don't see any problem with ArrayComprehension(1, (i, 1, N)).

Also, add a test to check that this is possible.

raise ValueError('ArrayComprehension requires values lower and upper bound'
' for the expression')
cls.default_assumptions = assumptions
obj = Expr.__new__(cls, **assumptions)

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

the arguments expr and bounds need to be passed to the superclass in the canonical form. That is, you should be able to rebuild the same object with the arguments you pass to the superclass.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 23, 2019

Author Contributor

@Upabjojr Thank you for reviewing!
Now I understand better the structure of a new class.

' for the expression')
cls.default_assumptions = assumptions
obj = Expr.__new__(cls, **assumptions)
obj.expr = expr

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

obj._expr = expr, then add a property for expr.

cls.default_assumptions = assumptions
obj = Expr.__new__(cls, **assumptions)
obj.expr = expr
obj.bounds = cls._check_bounds_validity(expr, bounds)

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

same as above

if var not in expr.free_symbols:
raise ValueError('Varialbe {} does not exist in expression'.format(var))
if any(not isinstance(i, (Integer, Symbol)) for i in [inf, sup]):
raise TypeError('Bounds should be an Integer or a Symbol')

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

why? I don't see any problem with expressions as bounds. Maybe also add a test ArrayComprehension(..., (i, 0, N+1)).

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 23, 2019

Author Contributor

@Upabjojr Infact, I was trying to eliminate the cases where inf and sup are other than expressions, e.g. ArrayComprehension(k, (i, 1, [2, 3, 3, 3])) or ArrayComprehension(k, (i, 1, ArrayComprehension(k, (i, 1, 5)))), these don't really make sense.
I changed the condition to not isinstance(i, Expr)
But I haven't found a way then to check if bounds are Integer, e.g. ArrayComprehension(k, (i, 1, 3.55)), as the number will be sympified into Expr.

Show resolved Hide resolved sympy/tensor/array/array_comprehension.py

# Substitute the variable with a value, so that the symbolic dimension can be expanded as well
def subs(self, var, val):
return 0

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

Remove this. This is handled by sympy/core and should not be overwritten.

assert a.doit().tolist() == [[2, 3, 4], [4, 6, 8], [6, 9, 12]]
assert isinstance(b.doit(), ArrayComprehension)
assert isinstance(a.doit(), ImmutableDenseNDimArray)
#assert b.subs(j, 3) == [3, 6, 9]

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

You can still test subs.

[[[5, 6, 7, 8, 9], [6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12]],
[[6, 7, 8, 9, 10], [7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13]],
[[7, 8, 9, 10, 11], [8, 9, 10, 11, 12], [9, 10, 11, 12, 13], [10, 11, 12, 13, 14]]]]
raises(TypeError, lambda:ArrayComprehension(i*j, (i, 1, 3), (j, 2, [1, 3, 2])))

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 23, 2019

Contributor

space after lambda:

Ameliorated functions and added test
- Ameliorated the value check of bounds
- Ameliorated the constructor
- Added properties and tests
- Implemented generation of constant arrays
- Added tests to ArrayComprehension
if any(len(l) != 3 or None for l in bounds):
raise ValueError('ArrayComprehension requires values lower and upper bound'
' for the expression')
cls.default_assumptions = assumptions

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

let this be handled by the superclass constructor.

>>> a.expr
10*i + j
"""
return self._args[0]

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

we have self._expr now

return self._args[0]

@property
def bounds(self):

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

is this the same terminology used in Sum and Product? I remember a different name, can you check?

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 24, 2019

Author Contributor

In Sum and product, function ans symbols are used as parameters of construtor which correspond to expr and bounds. But then symbols is transformed limits. Since there is not a standard naming convention, I created the name by my own.
Besides, there are not explicit properties in Sum to make these parameters accesible. In product, there is only one for function, but it is renamed as term.
In this case, what terminology do you think I should keep?

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 25, 2019

Contributor

I think there is a standard terminology. There is a superclass implementing it.

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 26, 2019

Author Contributor

@Upabjojr
I think what you are refering to is the ExprWithLimits class. And yes, it defines many properties like function, limits, variables, bound_symbols, free_symbols, is_number. Do you prefer following these examples?

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 27, 2019

Author Contributor

Would it be easier if ArrayComprehension herits form ExprWithLimits?

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 27, 2019

Contributor

Maybe yes, if no complications arise I'm +1 with that. You can do this here or in another PR.

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 27, 2019

Contributor

Maybe just make sure the names correspond with ExprWithLimits, I want this PR to be merged as soon as possible. Subclassing can be done in the future.

Show resolved Hide resolved sympy/tensor/array/array_comprehension.py Outdated
from sympy.core.compatibility import Iterable
from sympy.core.numbers import Integer

class ArrayComprehension(Basic):

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

This class is missing the .shape property, and the .rank() function as well. It should be compatible with the arrays.

assert isinstance(b.doit(), ArrayComprehension)
assert isinstance(a.doit(), ImmutableDenseNDimArray)
assert b.subs(j, 3) == ArrayComprehension(i, (i, 1, 4))
assert c.free_symbols == {i, j, k, l}

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

c.free_symbols == set([]). There are no free symbols in c as they are variables to be expanded.

You should subtract the iteration variables from .expr.free_symbols, then add the free symbols in the bounds.

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

For example:

>>> ArrayComprehension(a*i, (i, k, N)).free_symbols
{a, k, N}

where i is removed because it won't exist anymore after calling .doit().

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 24, 2019

Member

Will this implementation allow multivariate lists like,

>>> A = ArrayComprehension(i + j, (i, ki, Ni), (j, kj, Nj))
>>> A.free_symbols
{kj, Nj, ki, Ni}

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 24, 2019

Contributor

Yes, only i and j will be removed from free symbols in that example.

The free symbols should be the same before and after calling .doit().

Added free_symbols and tests
- Added property free_symbols and tests
- Added bound value check and test
- Ameliorated expr bounds properties
@@ -87,6 +111,8 @@ def _check_bounds_validity(cls, expr, bounds):
raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)')
if isinstance(inf, Integer) and isinstance(sup, Integer) and (inf > sup) == True:
raise ValueError('Lower bound should be inferior to upper bound')
if var in inf.free_symbols or var in sup.free_symbols:
raise ValueError('Variable should not be part of its bounds')

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 25, 2019

Author Contributor

I was thinking about a case where the var in the bounds happens to be one of the Integer in the expression, e.g.

>>> from sympy.tensor.array import ArrayComprehension
>>> from sympy.abc import i, j, k, l, m, n
>>> c = ArrayComprehension(3*i, (i, 1, 5), (3, 2, 7))
>>> c.doit()
[[2, 3, 4, 5, 6, 7], [4, 6, 8, 10, 12, 14], [6, 9, 12, 15, 18, 21], [8, 12, 16, 20, 24, 28], [10, 15, 20, 25, 30, 35]]
>>> a = ArrayComprehension(3*i, (i, 1, 5), (k, 2, 7))
>>> a.doit()
[[3, 3, 3, 3, 3, 3], [6, 6, 6, 6, 6, 6], [9, 9, 9, 9, 9, 9], [12, 12, 12, 12, 12, 12], [15, 15, 15, 15, 15, 15]]

As you can see, here 3 of array c is replaced by the sequence of [2,7]. Since it is sympified, it can be replaced like other Symbols. So can we add a convention that the variable in a bound, which means var in (var, inf, sup), shoud be a Symbol?

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 25, 2019

Member

I think that's quite intuitive to have variable symbols to be replaced rather than constants. Moreover, instead of allowing only Symbol, expressions containing Symbol should be allowed. Like (n + 1, 4, 8).

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 25, 2019

Contributor

The variable needs to be a symbol, of course. inf and sup can be any expression.

Moreover, instead of allowing only Symbol, expressions containing Symbol should be allowed. Like (n + 1, 4, 8).

No, use (n, 4+1, 8+1). First variable needs to be a symbol.

Have you ever trying a list comprehension like this:

[n for n+1 in range(4, 8)]

I don't think this should be allowed.

Added properties to ArrayComprehehnsion
- Added .shape and tests
- Added .rank() and tests
- Added len() and tests
12
"""
if len(self._loop_size.free_symbols) != 0:
raise ValueError('Symbolic length is not supported')

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 27, 2019

Author Contributor

Is it possible to find a bypass so that we can return an expression rather than integer for len()?
Or maybe another function/property like size to return this length of the expanded array.

This comment has been minimized.

Copy link
@czgdp1807

czgdp1807 May 27, 2019

Member

AFAIK, __len__ always returns an integer, so recommended is using size. Something similar has been done in Range in sympy.sets.fancysets.

This comment has been minimized.

Copy link
@Upabjojr

Upabjojr May 27, 2019

Contributor

what about

return int(product([sup-inf for var, inf, sup in self.limits]))

?

This comment has been minimized.

Copy link
@kangzhiq

kangzhiq May 27, 2019

Author Contributor

I am sorry but I don't really understand how product will help in this case. Do you mean itertool.product? In fact, the problem is when we have an array like

>>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k + 3))
>>> len(a)
4 * (k + 3)

it is supposed to have an expression, but len() accepts only integer as return type.
For your information, a bound like (i, j, j+4) will return 4 as length so there is not problem if the difference is an Integer.

return obj

@property
def expr(self):
"""
Return the expression that will be expanded to an array
Returns the expression that will be expanded to an array

This comment has been minimized.

Copy link
@jksuom

jksuom May 27, 2019

Member

From PEP 257:

The docstring is a phrase ending in a period. It prescribes the function or method's effect as a command ("Do this", "Return that"), not as a description; e.g. don't write "Returns the pathname ...".

Changed to standard naming convention
- Follewed example of ExprWithLimits
- Changed expr to function
- Changed bounds to limits
- Added variables,bouns_symbols, is_numeric properties and tests
_ Ameliorated doit() and doc quality

@Upabjojr Upabjojr merged commit ccf245a into sympy:master May 28, 2019

3 checks passed

codecov/project 73.905% (target 0%)
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
sympy-bot/release-notes The release notes look OK
Details
@Upabjojr

This comment has been minimized.

Copy link
Contributor

commented May 28, 2019

OK, merged.

@kangzhiq kangzhiq deleted the kangzhiq:Added_ArrayComprehension_Class branch May 29, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.