On Wed, Feb 1, 2012 at 3:44 PM, Nathan Rice
<nathan.alexander.r...@gmail.com> wrote:
>> On Tue, Jan 31, 2012 at 4:38 PM, Nathan Rice
>> <nathan.alexander.r...@gmail.com> wrote:
>>> I am the author of elementwise (http://packages.python.org/
>>> elementwise/) and constraints (http://packages.python.org/
>>> constraintslib/).  These tools are similar to SymPy in their use of
>>> symbolic proxy objects to represent values that may be provided later
>>> (and to some degree in their target audience).
>>
>> This is cool.  Where can I get the code for these projects? I didn't
>> see any download links (I know I can pip install, but I want to play
>> with the code too).
>
> My bad:
>
> https://github.com/nathan-rice/Constraints
> https://github.com/nathan-rice/Elementwise

Cool.  So you are basically letting Python do the work by putting
everything in lambdas (or is it nested lambdas?).  SymPy builds things
up in it's own proxy objects, which has advantages (e.g., we can
easily print the object as it was built). I'm sure there are
advantages to just using Python closures, though I can't think of them
now (perhaps more space efficient, and simpler code?).

>
>>>
>>> I'm getting in touch because:
>>>
>>> 1.) I'd like to get people on board with the idea of a standard symbol
>>> proxy.  The general case is basically doing anything with a symbol
>>> returns another symbol representing the resulting expression.  While
>>> you do this to some degree now, the way in which you've gone about it
>>> only allows you to use functions that return a wrapper object (Add,
>>> Pow, etc).  Anyone that wants to interact SymPy has to take and return
>>> these objects.  A much better scenario is to have and symbolic
>>> expressions that can be realized down to normal python code (and which
>>> can be generated in a general manner from ASTs).    This gives you
>>> full interoperability with regular Python code.
>>
>> So I'm trying to understand exactly what you are suggesting here?  You
>> want to do the sort of thing that SymPy does where you can build up
>> expression symbolically, but abstract out the mathematical properties?
>>  For example, x + x would not necessarily auto-reduce to 2*x (because
>> "x" could be anything that supports __add__, like str).  We have an
>> idea to make it easier to produce unevaluated expressions that I think
>> is similar to what you are suggesting (see
>> http://code.google.com/p/sympy/issues/detail?id=2738).
>
> Basically, instead of having the specialized properties at the proxy
> level, just use the proxy to build something resembling a generic AST.
>  If done properly, it should be a fairly trivial matter to convert a
> "true" python AST into a symbolic expression AST.  That lets you do
> fun things with normal python functions :)  Granted, to really do fun
> stuff, you will need to provide some annotations for any python
> functions you want to use (it is possible to infer these automatically
> in a great many cases but I don't think it is time to go down that
> rabbit hole yet).
>
> Also, building a generic AST means if you want to implement some
> calculus down the line where distributivity or commutativity hold in
> very different circumstances, you touch a lot less code.
>
>>>
>>> 2.) I noticed that a type system was on your roadmap.  Given a
>>> solution can be achieved for #1, I feel this has broad applicability
>>> to the community.  This also ties into your assumption architecture,
>>> which I understand you are currently overhauling.  Ideally, your types
>>> and assumptions should pretty much be one and the same.
>>
>> Definitely.  We are still in the process of removing the old system,
>> and to some degree, evolving the new one.
>>
>> By the way, here's an example of the sort of thing to think about.
>> Right now, our relationals (a < b, a <= b, etc.) work like this.  Expr
>> < Expr creates Lt(Expr, Expr) (Expr is the base class of SymPy
>> mathematical objects).  If the relational can be evaluated to True or
>> False, that is returned. Otherwise, it is done mathematically.  For
>> example:
>>
>> In [55]: S(1) < S(2)
>> Out[55]: True
>>
>> In [56]: x < x
>> Out[56]: False
>>
>> In [57]: x < y
>> Out[57]: x < y
>>
>> (S() is a shortcut to sympify(), which converts the arguments into
>> SymPy objects: in this case, Integer objects).
>>
>> The problem with this is in the auto-evaluation.  The relationals are
>> serving a double purpose. On the one hand, they represent boolean
>> objects (i.e., x < y means asking if x is less than y).  On the other
>> hand, they represent symbolic (mathematical) objects.  (x < y as a
>> mathematical statement).
>>
>> The difference may seem subtle, but the point is that the former
>> should try to evaluate to boolean types, and the latter should not.
>> We have an open issue to split the two
>> (http://code.google.com/p/sympy/issues/detail?id=1887).
>>
>> The sort of thing I'd like to discuss is how to combine the two.  My
>> idea is to make __lt__ default to the symbolic type.  But any call to
>> bool() would coerce it to the boolean type.  This would also happen
>> automatically if they are used in the assumptions system (like ask(x <
>> y)).  On the other hand, even if x = Symbol('x', positive=True) (or
>> assume(Q.positive(x)) or whatever the system looks like), you would
>> want x**2 > 0 to remain unevaluated, so you can pass it to solve
>> (solve(x**2 > 0), which would give something like x > 0).
>>
>> What is the best way to make the object unevaluated by default, but to
>> evaluate intelligently in the right contexts, so that the user rarely
>> has to think about such things.  The same question could apply to any
>> kind of symbolic object.
>
> My simple rule has been anything done with a symbol returns a symbol.
> I think that works well for the general symbol proxy case.
>
> I think in the more specific case, it depends on the context.  If you
> know that a particular expression exists in the context of some
> specific calculus, and under the accepted axioms you can make a
> simplification that will __always__ be valid, you should go ahead and
> make it.

In my experience, you should be even more cautious than that, because
the ability to print symbolic expressions in SymPy means that someone
might want to write something like x + x = 2*x and have it printed
that way.  That's why we allow to create unevaluated objects even when
we can evaluate them (like Eq(Integral(x, x), integrate(x, x))).
Perhaps this concept can be abstracted into the "calculus" concept.

There are also issues like the cost of the simplification (for
example, we don't auto-simplify sqrt(number) into factors beyond a
certain size of the prime factors, as integer factorization is too
expensive), and whether or not the "simplification" actually makes
things simpler or not (it's very subjective, and depends on what
measure you use).

But the point is that automatic simplification cannot be overridden,
at least in the same context, so it should only be done if you are
certain that no one would ever not want it done (in that context).

> You can fix the calculus based on what functions are used on
> the expression, and any symbols would ideally have constraints, so
> that requirement seems reasonable to me.  To reference your ask
> example, if you assume that calling ask imposes an elementary algebra
> context, that would give you a green light to reduce and transform,
> without introducing inconsistencies in the behavior of the symbol
> proxy itself.
>
> So, to answer the question, I would build a general expression that is
> "as written", then transform it as needed once the specific context
> can be clearly identified.

I remembered what the tricky part of this was.  We use a three-valued
logic in our assumptions system.  If something can be deduced to be
always true or always false, we use True and False.  Otherwise, we use
None, which means that it can be either true or false with the
information given (or possibly just one and our algorithms aren't
smart enough to deduce it).

So if we have x < y, where x and y are just Symbol objects, ask(x < y)
would return None, as we don't know if x is less than y or not with
the information given.   The problem is that bool() only returns True
or False.  So if someone does "if x < y:", this is a problem. My idea
is to make __nonzero__ raise ValueError if it would be None, which
would prevent users from writing such statements (right now,
Le.__nonzero__ returns True or False arbitrarily, which leads to
hidden bugs if someone writes such an if statement).

The question is, will this restrict our ability to coerce it into a
Boolean object when necessary?  It seems like it might, though I can't
tell without actually implementing it if it would or not. Perhaps it
would be cleaner to make Lt.__nonzero__ *always* raise ValueError,
even if it's something like Lt(2, 3), and require code that wants to
ask about it to wrap it around a call to ask().

Sorry if this is fledging off topic, but your discussion reminded me
of this particular issue.

>
>
> I will go ahead and try to generalize the proxy base I have now to
> support multiple variables and generate a full AST.  Once I've got
> that I'll create a SymPy-experimental fork and start nibbling around
> the edges of a rather large refactor as time permits :)

Great. Keep us updated on your progress.

Aaron Meurer

>
>
> Nathan
>

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To post to this group, send email to sympy@googlegroups.com.
To unsubscribe from this group, send email to 
sympy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sympy?hl=en.

Reply via email to