On Wed, Jan 20, 2021 at 3:49 PM gu...@uwosh.edu <gu...@uwosh.edu> wrote:
>
> First, Oscar thank you for taking the time to summarize for everyone the key 
> issues we are trying to make decisions on.
>
> Second, I think I can help people understand a little more about why these 
> are issues.
>
> Most of them arise because in code we make a distinction between a function 
> and a symbol. As a physical scientist I routinely work with equations where 
> all the symbols can be thought of as functions of all the other symbols. 
> Consider the ideal gas law: P = nRT/V, where P = pressure, n = moles, R = gas 
> constant, T = absolute temperature and V = volume. You can solve this 
> equation for any of the symbols. In the form it is written it makes perfect 
> sense to say dP/dT = nR/V. If I had solved for V => dV/dT = nR/P. 
> Measurements in the laboratory show that both relationships are true. Thus 
> the following does not make sense for this simple example:
> >>>diff(P=n*R*T/V,T)
> 0 = n*R/V
> n*R/V is generally not equal to zero (only two cases: n = 0 and V = oo; both 
> physically not very important).
>
> Because of the way SymPy presently treats functions, getting the behavior 
> that I would expect as a scientists for an equation such as the ideal gas law 
> cannot be achieved by using functions instead of symbols.

This starts to get into the question of what the semantic meaning of
Eqn is. Should Derivative treat the variables in Eqn as depending on
each other, because the equality implies they do? What about Eqn(f(x),
a*x)? Does the presence of f(x) prevent a from depending on x? You
could also write Eqn(a, f(x)/x). The first one "looks" like a doesn't
depend on x and the second one "looks" like it does. But shouldn't the
two Eqns be treated in the same way, since one can be obtained from
the other by a simple, reversible transformation?

My feeling on this is that Derivative already solves this problem by
implicitly assuming that variables are independent of one another,
unless explicitly stated otherwise via a functional relationship. I
don't think Eqn should change this behavior. As an aside, it may be
useful, independently of Eqn, to have a function that can take
derivatives under the assumption that certain symbols depend on one
another, as a convenience over creating explicit Function objects
(idiff() is an example of this sort of thing).

But the underlying question about the semantic meaning is, to what
degree does Eqn represent an "equality", vs. just being a convenient
way to manipulate two expressions in tandem. This question also comes
up with some of my suggestions about manipulating an Eqn in my
previous post. I think an Eqn *is* more than just a pair of
expressions. But we should think about a clear way to define an
"equality" so that it is clearer what an Eqn should and shouldn't do.

>
> The fundamental question, as Oscar points out, is whether we want to be able 
> to wrap an equation in an operation and have that default to application of 
> the operation to both sides. We are clear that this should work for simple 
> binary math operations, but for other things it is less clear.
>
> From an ease of use and consistency with how expressions behave, I am 
> inclined towards being able to wrap equations in operations such as 
> `cos(eqn)`. However, there are clearly some cases where this can cause 
> problems. So, two important questions we would like the community to respond 
> to:

This really boils down to how we want to answer the question I posed above.

I think that ideally cos(eq) would work. If it works with operations
like + and *, it should work with any mathematical function. I also
think Eqn should be an Expr so that it has all the same methods.

The problem is how to make this work technically. As Oscar said, you
can get most mathematical "functions" in SymPy by hooking into
Function. But that doesn't do anything for manipulation or
simplification functions, like simplify(). Ideally, any generic
function that doesn't know about Eqn would just operate on both sides
of the equation, and so long as it is a function that normally takes
an expression and returns an expression, it should work. But how do we
make this work technically? Does every function need to be decorated
with some special logic to pass through equations? The ability to
"pass through" or dispatch on arbitrary functions is a larger question
that has come up in a lot of other places in SymPy. I don't  know if
we have a solution for it yet, but it is something we'd like to solve.

Aaron Meurer

>
> How important is the convenience of being able to wrap equations in 
> operations?
> What operations are you aware of where this behavior could cause problems?
>
>
> On Wednesday, January 20, 2021 at 2:45:44 PM UTC-6 Oscar wrote:
>>
>> I'll add my thoughts in a lengthy post here.
>>
>> Firstly some background on why the existing Eq (full name Equality)
>> class is problematic. The Eq class is a Boolean and its intention is
>> to represent the truth of an expression. What this means is that it
>> will often evaluate to True or False e.g.:
>>
>> >>> x = Symbol('x')
>> >>> Eq(x, 1)
>> Eq(x, 1)
>> >>> Eq(1, 2)
>> False
>> >>> Eq(1, 1)
>> True
>>
>> This is useful in contexts where we want to use the Boolean-ness
>> because it allows simplifications to happen. Here simply substituting
>> a value for x into Eq(x, 1) turns it into a True or False and then the
>> Piecewise can simplify automatically.
>>
>> >>> p = Piecewise((1, Eq(x, 1)), (2, True))
>> >>> p
>> Piecewise((1, Eq(x, 1)), (2, True))
>> >>> p.subs(x, 1)
>> 1
>> >>> p.subs(x, 2)
>> 2
>>
>> However this can be quite awkward in other contexts when you want to
>> manipulate equations. Any code that operates with instances of Eq
>> needs to be prepared for the possibility that any operation (subs,
>> simplify, evalf etc) might happen to turn the Eq into True or False
>> which then does not have the same methods or attributes as Eq. For
>> example:
>>
>> >>> eq = Eq(x, 2)
>> >>> eq
>> Eq(x, 2)
>> >>> eq.lhs
>> x
>> >>> eq.subs(x, 1)
>> False
>> >>> eq.subs(x, 1).lhs
>> Traceback (most recent call last):
>> File "<stdin>", line 1, in <module>
>> AttributeError: 'BooleanFalse' object has no attribute 'lhs'
>>
>> That means that code that operates on Eq needs a lot of checking to be
>> robust (and many bugs in sympy come from forgetting this). It's also
>> necessary to check for these every time you have a function like solve
>> or linsolve etc that can take a list of equations as inputs. The
>> possible presence of True/False needs to be checked in all inputs
>> every time before we can begin to e.g. ask for the rhs of the
>> equation.
>>
>> For interactive use it would be nice to be able to do things like 2*eq
>> to multiply both sides of an equation. We could make that work with Eq
>> but we would have the danger that at any time any operation might turn
>> into True/False and then subsequent arithmetic operations would fail
>> because 2*True is meaningless.
>>
>> It's not just arithmetic but any operation that you might want to
>> apply to one or both sides of an equation. For example to factor both
>> sides of an equation you have to do
>>
>> eq = Eq(factor(eq.lhs), factor(eq.rhs))
>>
>> which doesn't look so bad but then for example integrating both sides
>> wrt x from 0 to 1 looks like:
>>
>> eq = Eq(Integral(eq.lhs, (x, 0, 1)), Integral(eq.rhs, (x, 0, 1))
>>
>> which begins to show the awkwardness of needing to repeat the same
>> operation. There are ugly constructs to work around the need to avoid
>> repetition such as
>>
>> eq = Eq(*(Integral(side, (x, 0, 1)) for side in [eq.lhs, eq.rhs]))
>>
>> but that's fairly awkward and cryptic in itself.
>>
>> The proposal here adds a new Eqn (full name Equation) class that does
>> not evaluate to True/False. It also defined various operations like
>> 2*eq to make interactive manipulation of equations easier. Here's a
>> demonstration using that to derive a formula for cos in terms of exp:
>>
>> >>> theta = Symbol('theta', real=True)
>> >>> eq = Eqn(exp(I*theta), cos(theta) + I*sin(theta))
>> >>> eq
>> exp(I*theta) = I*sin(theta) + cos(theta)
>> >>> conjugate(eq)
>> exp(-I*theta) = -I*sin(theta) + cos(theta)
>> >>> (eq + conjugate(eq))/2
>> exp(I*theta)/2 + exp(-I*theta)/2 = cos(theta)
>> >>> _.reversed
>> cos(theta) = exp(I*theta)/2 + exp(-I*theta)/2
>>
>> This kind of derivation won't work in general with Eq because at any
>> step it could evaluate to True. All of the equations above are true -
>> that's the whole point in a derivation!
>>
>> So I really like this feature and I want to get some form of it into
>> the next release. There is a longstanding issue to add this to sympy:
>> https://github.com/sympy/sympy/pull/19479
>> Some time ago I had a quick go at adding it myself but it turned out
>> other things needed to be fixed first. In the end I fixed those other
>> things but didn't get round to adding the new Equation class itself.
>>
>> It is worth trying to get the details right though. In this proposal
>> there are several mechanisms for applying an operation to either the
>> lhs, rhs, or both sides of an equation:
>>
>> 1. Methods apply/applylhs/appylrhs to apply a function to either/both sides
>> 2. Methods do/dolhs/dorhs to call a method on either/both sides
>> 3. Some functions work on the equation like together(eq)
>> 4. Some methods are defined on the equation like eq.together()
>>
>> Here's a demo of these:
>>
>> >>> x = Symbol('x')
>> >>> eq = Eqn(x*(x + 1), x**2 + x)
>> >>> eq
>> x*(x + 1) = x**2 + x
>> >>> expand(eq)
>> x**2 + x = x**2 + x
>> >>> eq.applylhs(expand)
>> x**2 + x = x**2 + x
>> >>> factor(eq)
>> x*(x + 1) = x*(x + 1)
>> >>> eq.applyrhs(factor)
>> x*(x + 1) = x*(x + 1)
>> >>> eq.dorhs.factor()
>> x*(x + 1) = x*(x + 1)
>> >>> eq.factor()
>> x*(x + 1) = x*(x + 1)
>>
>> The first question that comes to mind is do we need all of these?
>>
>> If eq.dorhs was callable then it could replace eq.applyrhs and we
>> could use eq.dorhs(factor) instead of eq.applyrhs(factor).
>>
>> For many operations that only apply to one side only it is not that
>> bad to do eq = Eqn(eq.lhs, factor(eq.rhs)). We could even have a
>> setrhs method so it becomes eq = eq.setrhs(factor(eq.rhs)) although
>> doesn't seem like a major advantage.
>>
>> For common operations like factor it maybe makes sense to add those as
>> methods on Eqn but otherwise are we going to want to add basically all
>> of the methods that Expr has?
>>
>> Functions can be made to work with Eqn like factor(eq) but where do we
>> draw the line with this? As soon as we have Eqn and there are some
>> functions that can work with it then there will be an expectation to
>> be able to pass Eqn to almost any possible function and we will have
>> to add support for it everywhere.
>>
>> The PR adds a special case in Function.__new__ to make the following work:
>>
>> >>> eq
>> x*(x + 1) = x**2 + x
>> >>> cos(eq)
>> cos(x*(x + 1)) = cos(x**2 + x)
>>
>> Adding that special case in Function.__new__ makes it work for most
>> common mathematical functions but then the question is how to handle
>> functions that take more than one argument:
>>
>> >>> atan2(eq, 1)
>> atan2(x*(x + 1), 1) = atan2(x**2 + x, 1)
>> >>> atan2(1, eq)
>> atan2(1, x*(x + 1) = x**2 + x)
>> >>> atan2(eq, eq)
>> atan2(x*(x + 1), x*(x + 1) = x**2 + x) = atan2(x**2 + x, x*(x + 1) = x**2 + 
>> x)
>>
>> The last example has generated an equation with nonsensical objects on
>> each side. Of course that aspect of the PR can be improved but I show
>> that example to illustrate the more general point that if we have an
>> expectation that we can pass an Eqn in place of an expression to any
>> function then we need a way to draw the line between what should work
>> and what should not. Also adding this to Function.__new__ covers a lot
>> of Expr subclasses but there are still plenty more that have their own
>> __new__ methods and the expectation will arise that all of them should
>> be able to handle Eqn.
>>
>> This is my biggest concern: making things like cos(eq) work in a way
>> that seems coherent for users requires adding little bits of support
>> code widely across the codebase. However we do that there will always
>> be gaps and in general I think it will give the impression that sympy
>> is buggy (other things like this already have that effect).
>>
>> I would much rather stick to an API that can work in general without
>> risk of bugs and I would prefer users to learn something that will
>> always work. That means that rather than having cos(eq) I think it
>> would be better to use eq.apply(cos) or perhaps there could be an
>> apply function like apply(eq, cos). More complicated cases can be
>> handled with a lambda function like apply(eq, lambda x: (cos(x) +
>> 1)/2)
>>
>> With a mechanism like apply it's clear that the function we apply to
>> the sides of the equation needs to be a callable that can only take
>> one argument so there is no confusion with something like atan2. This
>> approach also generalises completely to any function that you could
>> apply to the sides including both symbolic functions like cos and
>> manipulation routines like trigsimp. That way users only have to learn
>> one thing that can always work and is always well defined. It also
>> means that there is no need to add haphazard support for Eqn
>> throughout the codebase.
>>
>> There are a couple of other quirks in the PR such as:
>>
>> >>> Derivative(eq)
>> Derivative(x*(x + 1) = x**2 + x, x)
>> >>> Derivative(eq).doit()
>> 2*x + 1 = 2*x + 1
>>
>> The unevaluated Derivative in the first output there is nonsensical.
>> We also have e.g.
>>
>> >>> P, V, T = symbols('P, V, T')
>> >>> eq = Eqn(P*V, T)
>> >>> eq
>> P*V = T
>> >>> diff(eq, T)
>> Derivative(P*V, T) = 1
>> >>> diff(eq, T).doit()
>> 0 = 1
>>
>> Here the way the derivative is evaluated treats the lhs and rhs
>> differently giving an unevaluated derivative on the left. I think the
>> idea is to prevent the left hand side from fully evaluating although
>> it will if you call doit. I expect that a lot of users will find this
>> surprising (I certainly wouldn't want/expect this effect from
>> differentiating the equation).
>>
>> Another quirk is the way that integration is handled:
>>
>> >>> integrate(eq, T)
>> Traceback (most recent call last):
>> File "<stdin>", line 1, in <module>
>> File "/Users/enojb/current/sympy/sympy/sympy/integrals/integrals.py",
>> line 1567, in integrate
>> integral = Integral(*args, **kwargs)
>> File "/Users/enojb/current/sympy/sympy/sympy/integrals/integrals.py",
>> line 81, in __new__
>> return function._eval_Integral(*symbols, **assumptions)
>> File "/Users/enojb/current/sympy/sympy/sympy/core/equation.py", line
>> 491, in _eval_Integral
>> raise ValueError('You must specify `side="lhs"` or `side="rhs"` '
>> ValueError: You must specify `side="lhs"` or `side="rhs"` when
>> integrating an Equation
>>
>> You have pass a "side" argument to integrate to specify which side to 
>> integrate:
>>
>> >>> integrate(eq, T, side='lhs')
>> P*T*V
>>
>> I would rather just pass the lhs to integrate if that's all this is doing:
>>
>> >>> integrate(eq.lhs, T)
>> P*T*V
>>
>> I think that for integration and differentiation we should stick to
>> the general approach implied by apply or do e.g. we use apply to
>> integrate both sides and applylhs to integrate one side.
>>
>> >>> eq.apply(lambda s: Integral(s, (x, 0, 1)))
>> Integral(P*V, (x, 0, 1)) = Integral(T, (x, 0, 1))
>>
>> If we want a convenience method for integrating both sides like
>> eq.integrate(x, (x, 0, 1)) then that could make sense. I could
>> potentially countenance diff and integrate as functions that make a
>> specific exception to support Eqn so that diff(eq, x) works. I do not
>> think that Derivative(eq) should be allowed though as the unevaluated
>> derivative of an equation is nonsensical as a symbolic expression.
>>
>> I think this is what we want and we should try to get it in for the
>> next release. I have problems with specific parts of it though. As
>> discussed on GitHub Jonathan and I are in disagreement over things
>> like whether cos(eq) should work. We would like to hear what others
>> think about these kinds of details about how working with Eqn should
>> work or what makes more sense as an interface. That's why we have
>> brought the discussion here to the mailing list so if anyone has any
>> thoughts about any of these things then please say so.
>>
>> --
>> Oscar
>>
>>
>>
>>
>>
>> On Wed, 20 Jan 2021 at 00:58, gu...@uwosh.edu <gu...@uwosh.edu> wrote:
>> >
>> > Aaron,
>> > Thank you for the comments. I need go through them more carefully, but I 
>> > did want to direct you to the SymPy pull request for the `Eqn` class that 
>> > implements everything in the Binder except for the ability to use `solve` 
>> > on an `Eqn`. My preference would be to get the `Eqn` class into SymPy and 
>> > then make it work with solve as a separate project/pull.
>> >
>> > As you will see there has been quite a bit of discussion within the pull 
>> > request. Hence this request for more input from the community.
>> >
>> > Jonathan
>> >
>> > On Tuesday, January 19, 2021 at 5:34:47 PM UTC-6 asme...@gmail.com wrote:
>> >>
>> >> Is there a pull request with the code associated with this, or is it
>> >> only in the Binder for now?
>> >>
>> > --
>> > You received this message because you are subscribed to the Google Groups 
>> > "sympy" group.
>> > To unsubscribe from this group and stop receiving emails from it, send an 
>> > email to sympy+un...@googlegroups.com.
>> > To view this discussion on the web visit 
>> > https://groups.google.com/d/msgid/sympy/3627a868-7cf0-42a8-92b2-b29b723ddca5n%40googlegroups.com.
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sympy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sympy+unsubscr...@googlegroups.com.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sympy/d7f67a32-381b-4ca1-baa0-da6c240c4c98n%40googlegroups.com.

-- 
You received this message because you are subscribed to the Google Groups 
"sympy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sympy+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sympy/CAKgW%3D6KSj3NTWDgqr7WjeTFf%3DkHrAtvk7sxkW%2Bzm_RbLO72ZWg%40mail.gmail.com.

Reply via email to