sympy icon indicating copy to clipboard operation
sympy copied to clipboard

Let Equality objects be used as substitution arguments

Open ByThePowerOfScience opened this issue 1 year ago • 13 comments

Brief description of what is fixed or changed

This PR lets Equality objects with a lone free Symbol on the left-hand side be used as an argument for subs().

force = Eq(F, M*A)
angular_accel = Eq(A, v**2/R)
force.subs(angular_accel) # acts like .subs(A, v**2/R)

This is very useful for substituting the answer to a recently-solve()'d equation into another one.

Release Notes

  • core
    • Allowed Equalitys with a left-hand side consisting of a single free symbol to be used as an argument for the subs function. [e.g. (m*a).subs(Eq(a, v**2/R))]

ByThePowerOfScience avatar Mar 24 '24 19:03 ByThePowerOfScience

:white_check_mark:

Hi, I am the SymPy bot. 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:

  • core
    • Allowed Equalitys with a left-hand side consisting of a single free symbol to be used as an argument for the subs function. [e.g. (m*a).subs(Eq(a, v**2/R))] (#26399 by @ByThePowerOfScience and @smichr)

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

Click here to see the pull request description that was parsed.
#### Brief description of what is fixed or changed

This PR lets `Equality` objects with a lone free `Symbol` on the left-hand side be used as an argument for `subs()`.  
```python
force = Eq(F, M*A)
angular_accel = Eq(A, v**2/R)
force.subs(angular_accel) # acts like .subs(A, v**2/R)
```
This is very useful for substituting the answer to a recently-`solve()`'d equation into another one.

#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers. Formerly, `log(-x)` incorrectly gave `-log(x)`.

* physics.units
  * Corrected a semantical error in the conversion between volt and statvolt which
    reported the volt as being larger than the statvolt.

or if no release note(s) should be included use:

NO ENTRY

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 -->
* core
    *  Allowed `Equality`s with a left-hand side consisting of a single free symbol to be used as an argument for the `subs` function. [e.g. `(m*a).subs(Eq(a, v**2/R))`]
<!-- END RELEASE NOTES -->

sympy-bot avatar Mar 24 '24 19:03 sympy-bot

Wait, can you target entire expressions for replacement in subs? If so, I should remove that check for a single variable on the lhs.

ByThePowerOfScience avatar Mar 24 '24 20:03 ByThePowerOfScience

I'm not sure how needed this is since expr.subs(*eq.args) or expr.subs(*solve(linear, sym, dict=True) both work, where eq is an Equality and linear is an equation that is linear in sym.

smichr avatar Mar 24 '24 20:03 smichr

I'm not sure how needed this is since expr.subs(*eq.args) or expr.subs(*solve(linear, sym, dict=True)both work, whereeqis an Equality andlinearis an equation that is linear insym`.

Considering that even when looking at the source code I couldn't find the args property, it's probably better to make it as intuitive as possible. People probably shouldn't have to dig through the source code to do something that should work intuitively.

Besides, it's a nice convenience factor that doesn't add any overhead, so there's no reason not to imo.

EDIT: Actually, something that would even more useful is being able to use a set of Equalities as an argument. Also important for consistency's sake.

ByThePowerOfScience avatar Mar 24 '24 20:03 ByThePowerOfScience

Wait, can you target entire expressions for replacement in subs? If so

Yes - but it only works if the expression is an exact leaf in the expression true (usually). I don't see any harm in telling the user what the semantics are, i.e. e.subs(Eq(a,b)) is short for e.subs(a, b).

smichr avatar Mar 25 '24 00:03 smichr

Besides, it's a nice convenience factor that doesn't add any overhead, so there's no reason not to imo.

Unfortunately, I'm generally +0 for these changes because of triviality. I also think that there is Eq.lhs, Eq.rhs to unpack the arguments of Eq. I understand some mathematical ideas behind how term system, substitution, unify, or something technical works. And I generally had no problem or significant inconvenience working with status quo.

sylee957 avatar Mar 26 '24 14:03 sylee957

Besides, it's a nice convenience factor that doesn't add any overhead, so there's no reason not to imo.

Unfortunately, I'm generally +0 for these changes because of triviality. I also think that there is Eq.lhs, Eq.rhs to unpack the arguments of Eq. I understand some mathematical ideas behind how term system, substitution, unify, or something technical works. And I generally had no problem or significant inconvenience working with status quo.

It's just a matter of how often you do it. When working with larger sets of equations, you're frequently solving an equation in terms of a variable and immediately substituting it in for that variable. Not being able to do what's intuitive is a small thing on its own, but a large thing when you're doing it all the time.

It's mainly in physics, where you're building one massive super-equation from a hundred different relationships that inevitably converge on Newton's second law :P

ByThePowerOfScience avatar Mar 26 '24 21:03 ByThePowerOfScience

The API for subs, is already messy, I appreciate the efforts to clean up to be more straightforward for users.

sylee957 avatar Mar 26 '24 21:03 sylee957

I'm +0, too. Solutions to equations can already be obtained as a dictionary. And we don't allow multiple dictionaries so (x+y).subs([{x:1},{y:2}]) does not work. But that sort of thing is already handled in python as

d = {x:1}; d.update({y:2}); (x+y).subs(d)->3

If someone had a bunch of equalities to apply its just expr.subs([i.args for i in equalities]).

Although I tend to agree that the overhead factor argument, something moves me toward -1/2: this opens up semantic issues like "why doesn't x.subs(Eq(y, x)) work?" We are asking an arbitrarily ordered object to act as an ordered object. And just stating that now makes me feel even more -1 for the same reasons that we have frowned upon the idea of treating Eq like an Equation. If a user wants to write a helper routine to turn Eqs into subsitution tuples, they are free to do so. But supporting that from SymPy's end does not seem like a good idea.

smichr avatar Mar 26 '24 22:03 smichr

Although I tend to agree that the overhead factor argument, something moves me toward -1/2: this opens up semantic issues like "why doesn't x.subs(Eq(y, x)) work?"

The reason I targeted it that way is because of how it interacts with the rest of SymPy, namely the solve() function. If you solve an Equality in terms of a Symbol, it returns an Equality with that Symbol on the left. This change would allow people to chain together solve calls into substitutions.

It's also how it works in other CASs.

But you're absolutely right, it could check if there's only one side with an isolated symbol and do that substitution! Though now that I think about it, that would stop reverse-substitution condensing multiple symbols into a single one. (E.g. 1/period = freq)

If there isn't already a Relational.swap function, that would be a good thing to add in combination with this change to solve that particular issue.

But again, Equality.args just isn't mentioned in the documentation. I've been deep in the source code and this thread was still the first time I heard of the property. If that's the only intended way to do this common thing, it should be documented and explained. But then if that's the standardized way to do it, then why not make it officially standardized so they can do it intuitively? This is like a few dozen bytes for a decent amount of hassle; it's not anything that could be considered close to a loss.

If a user wants to write a helper routine to turn Eqs into subsitution tuples, they are free to do so. But supporting that from SymPy's end does not seem like a good idea.

Well that's kind of a crappy way to look at things. If quality of life features were always third-party plugins, we'd still be using community-made VBS scripts in Excel for counting cells.

The extensible nature of SymPy is an amazing feature, but it can't be relied on as the end-all-be-all of development. I've spent roughly 100 hours troubleshooting various helper functions for this library; that's a tall order to ask of a mathematician who might not even know programming. Especially for something as trivial as this that should quite frankly already be a feature.

ByThePowerOfScience avatar Mar 26 '24 23:03 ByThePowerOfScience

Especially for something as trivial as this that should quite frankly already be a feature

I'll ask for feedback on the mailing list.

Relational.swap

>>> Eq(x,y).reversed
Eq(y, x)

smichr avatar Mar 27 '24 23:03 smichr

But again, Equality.args just isn't mentioned in the documentation

One could use (eq.lhs, eq.rhs) if args is not known.

Accepting other containers (today Eq, tomorrow Matrix or TableForm) violates the principle of making a routine do one thing well and erroring towards "including the batteries." Let's see what others say on the mailing list.

smichr avatar Mar 28 '24 03:03 smichr

This is implemented in Algebra_with_Sympy, which uses a fork of Sympy that includes an additional class called Equation. This additional class avoids some problems (e.g. automatic collapse to a boolean) that are encountered when trying to use Eq for things such as this. Maybe we should include Equation in the main sympy branch? The Equation class was originally developed as a project within Sympy but got bogged down in details about how the development team wanted it to behave. In the meantime, using Algebra_with_Sympy may meet many of @ByThePowerOfScience's needs.

gutow avatar Mar 28 '24 20:03 gutow