I plan to start implementing a user interface to symbolic summation
soon and I want to get some opinions on how this interface should be.
The most natural construct for summation, either of a list or symbolic
summation is of course "sum." Initially, I was thinking that it's a big
sin to override the python "sum" function, since I also use it very
often and consider it (performance) critical. However, I was encouraged
to think again about this after Mike's comment here:
http://trac.sagemath.org/sage_trac/ticket/3587#comment:3
For quick reference, here's the help for Python's sum function:
sum(sequence, start=0) -> value
Returns the sum of a sequence of numbers (NOT strings) plus the
value of parameter 'start'. When the sequence is empty, returns start.
We could easily extend this to check if the second parameter is a tuple
which defines a range (e.g., (x, 1, n) where the upper and lower
bounds are inclusive). If it is, try to solve the sum symbolically,
otherwise call the python sum function. (Actually, I recall that there
were plans to overwrite this function anyway with one that does
balanced summation if the argument is a list.)
So I propose the following:
sage: var('i,n')
(i, n)
sage: sum(2^i, (i, 0, n))
2^(n+1) - 1
sage: sum(1/i, (i, 1, n))
harmonic_number(1, n)
(As far as I can see, Sage doesn't have a construct for harmonic
numbers yet.)
These still will work of course:
sage: sum(range(5))
10
sage: sum(i for i in range(5))
10
sage: sum(range(5), 2)
12
Similarly, I suggest we extend prod the same way.
Any comments or objections?
I should also add that I don't like to differentiate between upper and
lowercase commands, so I don't think using "Sum" is an option. If
overriding "sum" is not accepted, "SymbolicSum" might be an alternative.
Thanks.
Burcin
This certainly seems like the best notation. Sums with tuple
endpoints are sometimes useful, e.g.
sage: sum(zip(range(5), range(5)), ())
(0, 0, 1, 1, 2, 2, 3, 3, 4, 4)
However, this is a much more obscure use case than what you're
proposing. We could make our sum should take an optional keyword
parameter "endpoint" so that this behavior is still possible (even
though we're changing the default behavior).
>> I should also add that I don't like to differentiate between upper
>> and
>> lowercase commands, so I don't think using "Sum" is an option.
>
> I agree. I'm strongly opposed to that Maple-style case-has-meaning
> crap.
Me too.
- Robert
FYI, see trac #2737
Jason
See trac 2737
Jason
What about list comprehensions? Something like the following.
sage: var('i,n')
(i, n)
sage: sum(2^i for i in range(n+1))
2^(n+1) - 1
I ask because this seems like the natural/first thing a user would try.
Franco
--
It's consistent:
sage: sum(i for i in range(10))
45
However, range would have to also be overridden here, since range only
accepts integers.
Or we could override the ellipsis notation:
sum(i for i in (1..oo))
sum(i for i in (1..n+1))
hmmm, interesting ideas.
Jason
I agree that they probably aren't feasible. I was throwing them out for
the purposes of brainstorming for interfaces.
Jason
No need to complicate with another object.
Something like that would be nicer (if possible):
sum(sum([f(j%7) for j in (0..oo) if j>k]) for k in (0..oo)]))
using j and k, since i is parsed as the complex constant.
Ronan Paixão
Given that they're by far the most natural interface, we shouldn't
rule them out for sure. Consider
sage: import dis
sage: g = (i for i in range(10) if i % 3r)
sage: dis.dis(foo(10).gi_frame.f_code)
2 0 SETUP_LOOP 29 (to 32)
3 LOAD_GLOBAL 0 (range)
6 LOAD_FAST 0 (n)
9 CALL_FUNCTION 1
12 GET_ITER
>> 13 FOR_ITER 15 (to 31)
16 STORE_FAST 1 (i)
3 19 LOAD_FAST 1 (i)
22 LOAD_FAST 1 (i)
25 BINARY_MULTIPLY
26 YIELD_VALUE
27 POP_TOP
28 JUMP_ABSOLUTE 13
>> 31 POP_BLOCK
>> 32 LOAD_CONST 0 (None)
35 RETURN_VALUE
If we evaluate the conditional (12-19) with the loop variable set to
a symbolic we get the condition, and we evaluate the expression
(22-25) at a symbolic loop variable to get the generic term. The
inner iterator is local, and we can recognize range, xrange, and
recursively attempt to descend for others. Of course the [a..b]
notation would have to be extended to support symbolic endpoints, and
one would have to be careful to not botch up arbitrary generators.
- Robert
> On Nov 14, 2008, at 7:05 AM, Jason Grout wrote:
>
Check out the (still not 100% robust) code below.
sage: get_sum_components(n for n in range(10))
(n, None, <listiterator object at 0xd1b0cd0>)
sage: get_sum_components(n^2 for n in [1..10] if n != 3)
(n^2, n != 3, <listiterator object at 0xd1b0ff0>)
sage: get_sum_components(1/n^2 for n in (1..))
(1/n^2, None, <generator object at 0xd1abe18>)
sage: get_sum_components(n^2 for n in [1..100] if n != 10)
(n^2, n != 10, <listiterator object at 0xd1afef0>)
---------------------------------------------
import dis
def chop_code(c):
i = 0
all = []
while i < len(c):
if ord(c[i]) < dis.HAVE_ARGUMENT:
all.append(c[i])
else:
all.append(c[i:i+3])
i += len(all[-1])
return all
def get_sum_components(g):
frame = g.gi_frame
code = frame.f_code
ops = chop_code(code.co_code)
if (dis.opname[ord(ops[0][0])] != 'SETUP_LOOP'
or dis.opname[ord(ops[1][0])] != 'LOAD_FAST'
or dis.opname[ord(ops[2][0])] != 'FOR_ITER'):
raise TypeError, "Not a valid generator."
index_name = code.co_varnames[ord(ops[3][1])]
range_name = code.co_varnames[ord(ops[1][1])]
range_value = frame.f_locals[range_name]
cond = []
expr = []
for op in ops[4:]:
if dis.opname[ord(op[0])] == 'YIELD_VALUE':
break
elif dis.opname[ord(op[0])] == 'JUMP_IF_FALSE' and not cond:
cond = expr
expr = []
else:
expr.append(op)
if cond:
# there's an extra pop
expr = expr[1:]
index_var = var(index_name)
expr += [chr(dis.opmap['RETURN_VALUE'])]
def func(arg): pass
expr_code = type(code)(2, code.co_nlocals, code.co_stacksize,
32832, ''.join(expr),
code.co_consts, code.co_names,
code.co_varnames, code.co_filename, 'sum_expr', code.co_firstlineno,
'', code.co_freevars, code.co_cellvars)
func.func_code = expr_code
expr = func(None, index_var)
if cond:
cond += [chr(dis.opmap['RETURN_VALUE'])]
cond_code = type(code)(2, code.co_nlocals,
code.co_stacksize, 32832, ''.join(cond),
code.co_consts, code.co_names,
code.co_varnames, code.co_filename, 'sum_cond', code.co_firstlineno,
'', code.co_freevars, code.co_cellvars)
func.func_code = cond_code
cond = func(None, index_var)
else:
cond = None
return expr, cond, range_value