# Julia Sympy Apply To Both Sides Of Equation

**URL:** https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514
**Category:** General Usage
**Tags:** sympy
**Created:** [December 10, 2022, 5:47pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514 "2022-12-10T17:47:52Z")
**Posts on this page:** 11
**Page:** 1

<div class="post-metadata">

### Author: ![Brian\_Toomey](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brian_toomey/32/20879_2.png) [@Brian\_Toomey](https://discourse.julialang.org/u/Brian_Toomey)
#### Post date: [December 10, 2022, 5:47pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/1 "2022-12-10T17:47:52Z")

</div>

Julia Sympy is great! Many thanks to the people that worked on it.

I’m wondering about how one might implement a strategy like the one described [here](https://awstip.com/customizing-pythons-sympy-for-easy-equation-manipulation-ca30b9d0dabf) so that I could work through calculations myself for problem sets.

Take for example a simple algebra 1 problem:

```julia
using SymPy
@vars x
eq = Eq(x^2-4x,2x+16)

```

Obviously, I could use `solveset(eq)` but if I wanted to work it through myself it would be cumbersome and something like:

```julia
eq = Eq(eq.lhs // 2,
        eq.rhs // 2)
eq = Eq(eq.lhs - 8,
        eq.rhs - 8)
eq = Eq(eq.lhs - x,
        eq.rhs - x)
eq = Eq(factor(eq.lhs)*2,
        eq.rhs)

```

I’m just starting to learn metaprogramming, but I wonder if there is a relatively simple way to set up a macro (or function?) to take in the equation and the operation you wish to perform and return the result as an equation.

This would be similar to [ApplySides](https://reference.wolfram.com/language/ref/ApplySides.html) in Mathematica.

I’m picturing something like `@eqApply sin eq` or `@eqApply \\2 eq`.

Thanks!

---

<div class="post-metadata">

### Author: ![j\_verzani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/j_verzani/32/8551_2.png) [@j\_verzani](https://discourse.julialang.org/u/j_verzani)
#### Post date: [December 10, 2022, 9:41pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/2 "2022-12-10T21:41:29Z")

</div>

You certainly don’t need metaprogramming, though it might be helpful to simplify the syntax. The thing is like a broadcast or map and is basically done by:

```julia
mapeq(f, eq) = Eq(f.((lhs(eq), rhs(eq)))...) # or f(lhs(eq)) ~ f(rhs(eq))

```

The issue is an equation is typed in Python, not Julia, so it isn’t easy to make this use some generic function.

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [December 10, 2022, 9:53pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/3 "2022-12-10T21:53:54Z")

</div>

```julia
julia> macro eq(ex) esc(:(Eq($(ex.args[1]), $(ex.args[2])))) end
@eq (macro with 1 method)

julia> macro step(eqname, ex)
           rep!(ex, r) = (ex isa Expr && (map(e->rep!(e,r), ex.args); replace!(ex.args, r)); ex)
           lhs = rep!(deepcopy(ex), eqname=>:($eqname.lhs))
           rhs = rep!(deepcopy(ex), eqname=>:($eqname.rhs))
           esc(:($eqname = Eq($lhs, $rhs)))
       end
@step (macro with 1 method)

julia> eqn = @eq x^2-4x = 2x+16
 2
x - 4⋅x = 2⋅x + 16

julia> @step eqn eqn//2
 2
x
── - 2⋅x = x + 8
2

julia> @step eqn eqn-8
 2
x
── - 2⋅x - 8 = x
2

julia> @step eqn eqn-x
 2
x
── - 3⋅x - 8 = 0
2

julia> @step eqn factor(eqn)*2
(x - 8)⋅(x + 2) = 0

```

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [December 11, 2022, 12:33am UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/4 "2022-12-11T00:33:18Z")

</div>

I didn’t fully read the request; I just jumped straight into writing a macro for fun, and I came up with `@step` because that felt most natural to me 😅 I’m sure you’ll find a better name for it.

Whereas the macro I wrote allows you to execute arbitrary expressions, Mathematica’s `ApplySides` requests a _function_. We can do this too if preferred, using _partially-applied functions_:

```julia
julia> apply_sides(f, eq) = Eq(f(lhs(eq)), f(rhs(eq)))
apply_sides (generic function with 1 method)

julia> eqn = @eq x^2-4x = 2x+16
 2
x - 4⋅x = 2⋅x + 16

julia> eqn = apply_sides(Base.Fix2(//, 2), eqn)
 2
x
── - 2⋅x = x + 8
2

julia> eqn = apply_sides(Base.Fix2(-, 8), eqn)
 2
x
── - 2⋅x - 8 = x
2

julia> eqn = apply_sides(Base.Fix2(-, x), eqn)
 2
x
── - 3⋅x - 8 = 0
2

julia> eqn = apply_sides(factor, eqn)
(x - 8)⋅(x + 2)
─────────────── = 0
       2

julia> eqn = apply_sides(Base.Fix2(*, 2), eqn)
(x - 8)⋅(x + 2) = 0

```

There is a proposal, [PR#24990,](https://github.com/JuliaLang/julia/pull/24990) that would make the construction of partially-applied functions easier: using underscore `_` to represent unfilled arguments to a function. By that proposal, the above expressions would be:

```julia
eqn = apply_sides(_//2, eqn)
eqn = apply_sides(_-8, eqn)
eqn = apply_sides(_-x, eqn)
eqn = apply_sides(factor, eqn)
eqn = apply_sides(_*2, eqn)

```

but that proposal has been stuck in purgatory.

---

<div class="post-metadata">

### Author: ![Brian\_Toomey](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brian_toomey/32/20879_2.png) [@Brian\_Toomey](https://discourse.julialang.org/u/Brian_Toomey)
#### Post date: [December 11, 2022, 3:44pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/5 "2022-12-11T15:44:39Z")

</div>

@j_verzani, thanks! This makes sense. It was the differences between typing in Python and Julia that were tripping me up.

@uniment, thanks appreciate it greatly! That is exactly what I wanted. If anyone else looks at this and implements Uniment’s solution on the equation construction macro `@eq` larger expressions need to be wrapped in parentheses.

For example on my side at least:

```julia
eq = @Eq (x^2 + 3x -4) = 0 # works
eq = @Eq x^2 + 3x -4 = 0 # fails

```

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [December 11, 2022, 6:09pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/6 "2022-12-11T18:09:55Z")

</div>

> [@Brian\_Toomey](#):
>
> It was the differences between typing in Python and Julia that were tripping me up.

It’s not any difference between Python and Julia that’s making things difficult here, but the fact that the SymPy library is sloppy and loses type information. For example, I was expecting `Eq` would be a constructor that returns an object of type `Eq`; we would’ve been able overload the arithmetic operators (e.g. addition, `Base.:+(a::Eq, b) = Eq(lhs(a)+b, rhs(a)+b)`) as the article suggested. If this were any other Julia library, we could (and actually easier than Python since we wouldn’t need to make a wrapper class; we would just overload the methods directly).

But instead `Eq` is just a function that returns a `Sym` object, which can’t be told apart from any other SymPy symbolic expression. This a) is not Julian (because idiomatic Julia requests that _CamelCase_ be reserved for types and constructors, and that function and object names should be written in _snake\_case_), and b) means that we can’t overload the arithmetic operators like the article was suggesting.

All to say: Most of your surprise is due to SymPy, not Julia.

> [@Brian\_Toomey](#):
>
> If anyone else looks at this and implements Uniment’s solution on the equation construction macro `@eq` larger expressions need to be wrapped in parentheses.

This is actually not a result of the size of the expression, but a result of how you use the `-` minus operator. 😅

When calling macros, we have two options: a) use parentheses and delimit expressions with commas (e.g., `@my_macro(expr1, expr2, expr3)`—just like a regular function call but on expressions) or b) use spaces to delimit the arguments (e.g., `@my_macro expr1 expr2 expr3`).

A consequence of that is: What happens when you happen upon an operator which can be considered either a _binary_ operator or a _unary_ operator? (namely: minus!)

You get this weirdness:

```julia
julia> macro what_did_I_do(exprs...)
           for expr ∈ exprs
               show(expr); println()
           end
       end
@what_did_I_do (macro with 1 method)

julia> @what_did_I_do 1-2 3
:(1 - 2)
3

julia> @what_did_I_do 1 -2 3
1
-2
3

julia> @what_did_I_do 1 - 2 3
:(1 - 2)
3

julia> @what_did_I_do(1 - 2, 3)
:(1 - 2)
3

julia> @what_did_I_do(1 -2, 3)
:(1 - 2)
3

julia> @what_did_I_do(x^2 + 3x -4 = 0)
:((x ^ 2 + 3x) - 4 = begin
          #= REPL[215]:1 =#
          0
      end)

julia> @what_did_I_do x^2 + 3x -4 = 0
:(x ^ 2 + 3x)
:(-4 = 0)

julia> @what_did_I_do x^2 + 3x - 4 = 0
:((x ^ 2 + 3x) - 4 = begin
          #= REPL[217]:1 =#
          0
      end)

```

Nominally, Julia treats `1 -2` as `1 - 2`. But in contexts where expressions will be space-delimited, they result in different expressions.

The other context in which we will space-delimit expressions is in building matrices, and we have the same behavior:

```julia
julia> [1-2 3]
1×2 Matrix{Int64}:
 -1 3

julia> [1 -2 3]
1×3 Matrix{Int64}:
 1 -2 3

julia> [1 - 2 3]
1×2 Matrix{Int64}:
 -1 3

julia> [1-2, 3]
2-element Vector{Int64}:
 -1
  3

julia> [1 -2, 3]
ERROR: syntax: unexpected comma in array expression

```

This behavior is necessary so that we can build matrices with negative entries without writing a bajillion parentheses.

All to say: when you’re hoping to use `-` as a binary operator, you should adopt a habit of writing it symmetrically spaced so it’s clear you wish to call the binary operator, not the unary operator. If that constraint cannot be enforced, then use the parenthesized version of macro calls so that expressions are comma-delimited,

```julia
eqn = @eq(x^2 + 3x -4 = 0)

```

---

<div class="post-metadata">

### Author: ![j\_verzani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/j_verzani/32/8551_2.png) [@j\_verzani](https://discourse.julialang.org/u/j_verzani)
#### Post date: [December 11, 2022, 11:12pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/7 "2022-12-11T23:12:45Z")

</div>

> [@uniment](#):
>
> SymPy library is sloppy and loses type information.

Sloppy isn’t really apt, nothing is lost. While theoretically possible to re-build the SymPy type class system on the `Julia` side (cf `Symbolics`) that isn’t really worth the effort here. The constructors like `Eq` just dispatch to the same-named constructors in the the Python library so shouldn’t be too surprising, as they are documented there (e.g., `@doc sympy.Eq`). They may not follow a Julia idiom, but the package proves quite useful, even if just a stop gap until `Symbolics` gains more features.

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [December 11, 2022, 11:49pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/8 "2022-12-11T23:49:24Z")

</div>

> [@j\_verzani](#):
>
> nothing is lost

It may not be lost to the Python instance, but to the Julia instance there’s information loss. When I call `Eq`, it is known in that Julia expression that I’d like to construct an equation. However, SymPy calls the `Eq` constructor in Python and stores the associated object in a `Sym` object on the Julia side, which is the same type which refers to other expressions, making it impossible to create Julia type-specialized methods for `Eq` objects.

```julia
julia> x^2+x+1 |> typeof
Sym

julia> Eq(x^2+x+1, 0) |> typeof
Sym

```

Of course, each time you do _anything_ to a `Sym`, the memory of that interaction is stored Python-side and not Julia-side, so at least this Julia-side information loss is coherent with the rest of SymPy’s behavior. A policy of declaring some Julia-side types here and there could decrease this coherence, and the only way to get full coherence back might be to implement the entire thing in Julia (an effort which is well underway) 😅

> [@j\_verzani](#):
>
> the package proves quite useful

Agreed. It’s very functional as-is, and tidying it up is probably not worth it. But that doesn’t mean its departure from Julian idioms isn’t sloppy! 😉

---

<div class="post-metadata">

### Author: ![j\_verzani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/j_verzani/32/8551_2.png) [@j\_verzani](https://discourse.julialang.org/u/j_verzani)
#### Post date: [December 12, 2022, 12:00am UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/9 "2022-12-12T00:00:12Z")

</div>

I look forward to your TidySymPy package, but, until then this lightweight style of wrapping an external library will have to do.

---

<div class="post-metadata">

### Author: ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)
#### Post date: [December 12, 2022, 12:15am UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/10 "2022-12-12T00:15:38Z")

</div>

I didn’t realize that I’m literally speaking with SymPy’s creator 😰 I need to communicate more thoughtfully, or go anon 😅

I apologize if I was offensive; I didn’t mean to be.

---

<div class="post-metadata">

### Author: ![Brian\_Toomey](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brian_toomey/32/20879_2.png) [@Brian\_Toomey](https://discourse.julialang.org/u/Brian_Toomey)
#### Post date: [December 13, 2022, 3:25pm UTC](https://discourse.julialang.org/t/julia-sympy-apply-to-both-sides-of-equation/91514/11 "2022-12-13T15:25:46Z")

</div>

Many thanks to you both for the conversation, macro and package, all of which were are much appreciated.
