Opened 7 years ago

Closed 4 years ago

Last modified 16 months ago

#9556 closed enhancement (fixed)

Dynamic attributes for symbolic expressions

Reported by: SimonKing Owned by:
Priority: major Milestone: sage-5.11
Component: symbolics Keywords: symbolic expression dynamic attribute sd48
Cc: burcin, kcrisman, vbraun, eviatarbach Merged in: sage-5.11.beta3
Authors: Burcin Erocal, Simon King, Mike Hansen Reviewers: Volker Braun
Report Upstream: N/A Work issues:
Branch: Commit:
Dependencies: Stopgaps:

Description (last modified by burcin)

Let e be a symbolic expression. It may happen that e.operator() has a certain callable attribute, say, foo, that is not a method of Function. In this situation, one would like to use e.foo(), which is supposed to return e.operator().foo(*e.operands()) - apparently this is useful for working with hypergeometric functions (#2516).

Example

       sage: from sage.symbolic.function import BuiltinFunction
        sage: class TFunc(BuiltinFunction):
        ....:     def __init__(self):
        ....:         BuiltinFunction.__init__(self, 'tfunc', nargs=1)
        ....:
        ....:     class EvaluationMethods:
        ....:         def argp1(self, x):
        ....:             '''
        ....:             Some documentation about a bogus function.
        ....:             '''
        ....:             return x+1
        ....:
        ....:         @property
        ....:         def foo(self):
        ....:             return 5
        ....:
        sage: tfunc = TFunc()
        sage: e = tfunc(x); e
        tfunc(x)
        sage: type(e)
        <class '__main__.Expression_with_dynamic_methods'>
        sage: e.argp1()
        x + 1
        sage: e.foo
        5
        sage: x.argp1()
        Traceback (most recent call last):
        ...
        AttributeError: 'sage.symbolic.expression.Expression' object has no
        attribute 'argp1'
        sage: t = (e + 1).op[0]; t
        tfunc(x)
        sage: t
        tfunc(x)
        sage: type(t)
        <class '__main__.Expression_with_dynamic_methods'>
        sage: t.argp1()
        x + 1
        sage: import sagenb.misc.support as s
        sage: s.completions('t.argp', globals(), system='python')
        ['t.argp1']
        sage: t.argp1.__doc__.strip()
        'Some documentation about a bogus function.'

Apply: trac_9556-dynamic_class_everywhere.patch

Attachments (4)

trac-9556_dynamic_attributes_symbolics.patch (10.3 KB) - added by SimonKing 7 years ago.
The patch implements dynamic attributes, but some doctests segfault
trac_9556-dynamic_class.patch (3.1 KB) - added by mhansen 7 years ago.
trac_9556-dynamic_class_everywhere.patch (7.5 KB) - added by burcin 4 years ago.
trac_9556-dynamic_class_everywhere.part2.patch (4.3 KB) - added by burcin 4 years ago.

Download all attachments as: .zip

Change History (21)

Changed 7 years ago by SimonKing

The patch implements dynamic attributes, but some doctests segfault

comment:1 Changed 7 years ago by SimonKing

  • Owner changed from segfaulting doctests to (none)

comment:2 Changed 7 years ago by SimonKing

  • Cc burcin added
  • Status changed from new to needs_work
  • Work issues set to segfaulting doctests

Sorry, accidentally I inserted the work issue "segfaulting doctests" into the owner field.

It would be nice to have a segfaulting example that works interactively.

comment:3 follow-up: Changed 7 years ago by mhansen

What is the typical use case for this? Are you just looking for a place to put the functions for evaluated function? Or do you really want the function to appear on both the function and its evaluations? It seems like the first case is what you really want.

We currently have all of the machinery to do this and it's used for the category code. Using this, you would have something like the following:

class ExampleBuiltin(BuiltinFunction):
    def __init__(self):
        BuiltinFunction.__init__(self, 'ex_func', nargs=0) #arbitrary no of args
 
    class EvaluationMethods:
        def some_function_name(self):
            return len(self.operands())

comment:4 follow-up: Changed 7 years ago by mhansen

I did a little mockup using this idea.

sage: from sage.symbolic.function import BuiltinFunction
sage: class ExampleBuiltin(BuiltinFunction):
....:         def __init__(self):
....:             BuiltinFunction.__init__(self, 'ex_func', nargs=0) #arbitrary no of args
....:     class EvaluationMethods:
....:             def some_function_name(self):
....:                 return len(self.operands())
....: 
sage: ex_func = ExampleBuiltin()
sage: f = ex_func(x, x+1, x+2)
sage: f.some_function_name()
3
sage: abs(f)
abs(ex_func(x, x + 1, x + 2))

Changed 7 years ago by mhansen

comment:5 Changed 7 years ago by mhansen

Note that pickling also works:

sage: loads(dumps(ceil(x))).foo_bar()
4

One issue is that in order to get the dynamic features, you have to go through the call method of the function. Thus, you'd have to change Expression.abs

        return new_Expression_from_GEx(self._parent, g_abs(self._gobj))

to return the dynamic class version. Similarly, unpickling old objects will just return an Expression object.

comment:6 Changed 7 years ago by mhansen

Also, all tests pass with this.

comment:7 in reply to: ↑ 3 Changed 7 years ago by SimonKing

Replying to mhansen:

What is the typical use case for this? Are you just looking for a place to put the functions for evaluated function? Or do you really want the function to appear on both the function and its evaluations?

Sorry that I was absent for some hours.

Unfortunately I don't know what the real use case is. It was a suggestion of Burcin, and I merely tried to implement what he suggested.

Burcin, could you comment on what is really expected?

comment:8 in reply to: ↑ 4 Changed 7 years ago by SimonKing

Replying to mhansen:

I did a little mockup using this idea.

sage: f = ex_func(x, x+1, x+2) sage: f.some_function_name() 3 sage: abs(f) abs(ex_func(x, x + 1, x + 2))

... which seems like the correct answer to me. So, the fact that ex_func has an _abs_ method helps to get the right answer for f.

comment:9 Changed 6 years ago by kcrisman

  • Cc kcrisman added

comment:10 Changed 4 years ago by burcin

  • Authors set to Burcin Erocal, Simon King, Mike Hansen
  • Cc vbraun added
  • Description modified (diff)
  • Status changed from needs_work to needs_review

I uploaded a new patch that uses Mike's dynamic class idea, but applies to all code paths that might generate symbolic expressions with minimal speed penalty. Please review.

Patchbot, apply only trac_9556-dynamic_class_everywhere.patch.

comment:11 Changed 4 years ago by eviatarbach

  • Cc eviatarbach added
  • Keywords sd48 added

comment:12 Changed 4 years ago by burcin

patchbot apply trac_9556-dynamic_class_everywhere.patch

comment:13 Changed 4 years ago by vbraun

  • Reviewers set to Volker Braun
  • Status changed from needs_review to positive_review
  • Work issues segfaulting doctests deleted

Looks good to me

Changed 4 years ago by burcin

comment:14 Changed 4 years ago by jdemeyer

  • Merged in set to sage-5.11.beta3
  • Resolution set to fixed
  • Status changed from positive_review to closed

comment:15 Changed 4 years ago by vbraun

As discussed with Burcin, patch needs to move to a different ticket.

Also, unpacking goes too far and doesn't let me preserve SR objects. This leads to funny behavior for constant functions etc::

sage: o = (SR(1), x)
sage: map(type, o)
[sage.symbolic.expression.Expression, sage.symbolic.expression.Expression]
sage: o = SR._force_pyobject(o)._unpack_operands()
sage: map(type, o)
[sage.rings.integer.Integer, sage.symbolic.expression.Expression]

comment:16 Changed 4 years ago by vbraun

For future historians: The part2 was moved to #14802

comment:17 Changed 16 months ago by jdemeyer

These EvaluationMethods should be a new-style class: #20825.

Note: See TracTickets for help on using tickets.