Opened 7 years ago

# evaluating symbolic expressions (without conversion to SR, i.e., staying in ring of values)

Reported by: Owned by: dkrenn major sage-6.6 symbolics sd66 cheuberg, mmezzarobba Daniel Krenn N/A u/dkrenn/SR/eval 276f0f3d3583c66c7e137350c578e8588c9b236d

This ticket proposes a new method `evaluate` which can evaluate symbolic expressions at values coming from a ring which not coerces into `SR`. The result again lives in the ring of the values. This forces the calculation to be done completely in the given ring (and not in the symbolic ring, where sometimes one does not know exactly what's going on).

For example:

```sage: P.<p> = ZZ[[]]
sage: E = x.evaluate(x=p)
sage: E, E.parent()
(p, Power Series Ring in p over Integer Ring)
```

which is not possible with `subs`

```sage: P.<p> = ZZ[[]]
sage: x.subs(x=p)
Traceback (most recent call last):
...
TypeError: no canonical coercion from Power Series Ring in p over Integer Ring to Symbolic Ring

sage: E = x.evaluate(x=p)
sage: E, E.parent()
p, Power Series Ring in p over Integer Ring)
```

### comment:1 Changed 7 years ago by dkrenn

• Branch set to u/dkrenn/SR/eval

### comment:2 Changed 7 years ago by dkrenn

• Commit set to c3696a81f65df6e64c5e2bcbdf8905d4f2d5b796
• Status changed from new to needs_review

New commits:

 ​9f8d483 `create method eval` ​68b65ca `rename eval to evaluate` ​24348f7 `docstring: output clearified` ​c3d0670 `rename ring to convert_to` ​6449f20 `implement automatic detection when convert_to=None` ​cd29dca `examples rewritten` ​bc2bb47 `add seealso-block` ​9419e1f `fix typo` ​9e83cb2 `docstring for helper function _evaluate_` ​c3696a8 `fix links in doc`

### comment:3 Changed 7 years ago by dkrenn

• Authors set to Daniel Krenn

### comment:4 follow-up: ↓ 5 Changed 7 years ago by rws

```+        The reason is that :meth:`subs` convert its arguments to the
+        symbolic ring, so we even have::
+
+            sage: x.subs(x=RIF(3.42)).parent()
+            Symbolic Ring
+
+        The :meth:`evaluate`-method prevents this conversion and
```

I think you misunderstand. `x` is not converted, it is wrapped in an expression:

```sage: x.subs(x=RIF(3.42)).pyobject().parent()
Real Interval Field with 53 bits of precision
```

I have no idea if your idea is worth the effort, but suspect that not if it is only based on the necessity to prevent "conversion".

### comment:5 in reply to: ↑ 4 Changed 7 years ago by dkrenn

• Description modified (diff)

I think you misunderstand. `x` is not converted, it is wrapped in an expression:

Ok, I used the wrong word; however, this example was to point out the differences between the two commands.

```sage: x.subs(x=RIF(3.42)).pyobject().parent()
Real Interval Field with 53 bits of precision
```

I have no idea if your idea is worth the effort, but suspect that not if it is only based on the necessity to prevent "conversion".

`subs` is not possible with something that does go into the symbolic ring, like power series:

```sage: P.<p> = ZZ[[]]
sage: x.subs(x=p)
Traceback (most recent call last):
...
TypeError: no canonical coercion from Power Series Ring in p over Integer Ring to Symbolic Ring

sage: E = x.evaluate(x=p)
sage: E, E.parent()
p, Power Series Ring in p over Integer Ring)
```

### comment:7 follow-up: ↓ 8 Changed 7 years ago by rws

I thought everything coerces to `SR`? Maybe this is just a coercion bug?

### comment:8 in reply to: ↑ 7 Changed 7 years ago by dkrenn

I thought everything coerces to `SR`? Maybe this is just a coercion bug?

IMHO, not everything coerces into `SR` and this for a good reason. But this is not (or should not) under discussion here.

The following is not possible at with subs:

```sage: sage: P.<p> = ZZ[[]]
sage: var('a,b')
(a, b)
sage: (a+b).subs({a: p, b: p^2})
```

`evaluate` can do.

### comment:10 follow-up: ↓ 13 Changed 7 years ago by vdelecroix

• Status changed from needs_review to needs_info

Hello,

Why not

```sage: E = (1+x).subs(x=RIF(3.42))
sage: E.parent()
sage: F = E.pyobject()
sage: F
4.4200000000000000?
sage: F.parent()
Real Interval Field with 53 bits of precision
```

Vincent

### comment:11 Changed 7 years ago by git

• Commit changed from c3696a81f65df6e64c5e2bcbdf8905d4f2d5b796 to 7e0be7f3a76c98d5ec3e5250947aec814467048b

Branch pushed to git repo; I updated commit sha1. New commits:

 ​ba59f5d `additional example: inserting power series` ​7e0be7f `correct bug when evaluating user-defined functions`

### comment:12 Changed 7 years ago by dkrenn

added a doctest and corrected a small bug

### comment:13 in reply to: ↑ 10 Changed 7 years ago by dkrenn

Why not

```sage: E = (1+x).subs(x=RIF(3.42))
sage: E.parent()
sage: F = E.pyobject()
sage: F
4.4200000000000000?
sage: F.parent()
Real Interval Field with 53 bits of precision
```

Ok, I see. Maybe RIFs are not a good example since they coerce into SR. Power series are better" example; since there problems.

I'll rewrite the description of the ticket and the examples.

### comment:14 follow-up: ↓ 18 Changed 7 years ago by nbruin

The general idea is that the result of arithmetic only depends on the parents of the input data, not on the values of the input data (because the idea is that these things implement maps, which have domains and codomains). When you evaluate a SR element at a non-symbolic value, you don't know if the result can live in the parent of the original result (e.g., `(sin(x)+y).subs(y=1)`).

The appropriate solution is probably to first *convert* your symbolic expression to a parent where the parent is the desired thing, e.g.

```sage: f = SR(1+x)
sage: R.<t>= ZZ[[]]
sage: P=R['x']
sage: P(f)(x=t^2+O(t^3))
```

This also has other advantages: in principle, when you do this with RIF, you might end up with an evaluation routine that takes into account that the coefficients are not exact and hence it could choose some more stable way of doing the evaluation (I think that's hypothetical--likely no such effort is made right now, but it could).

### comment:15 Changed 7 years ago by dkrenn

• Description modified (diff)

### comment:16 Changed 7 years ago by git

• Commit changed from 7e0be7f3a76c98d5ec3e5250947aec814467048b to 276f0f3d3583c66c7e137350c578e8588c9b236d

Branch pushed to git repo; I updated commit sha1. New commits:

 ​276f0f3 `rewrite documentation (examples) after discussion on trac`

### comment:17 Changed 7 years ago by dkrenn

rewritten documentation of function

### comment:18 in reply to: ↑ 14 ; follow-up: ↓ 19 Changed 7 years ago by dkrenn

The general idea is that the result of arithmetic only depends on the parents of the input data, not on the values of the input data (because the idea is that these things implement maps, which have domains and codomains). When you evaluate a SR element at a non-symbolic value, you don't know if the result can live in the parent of the original result (e.g., `(sin(x)+y).subs(y=1)`).

True.

The appropriate solution is probably to first *convert* your symbolic expression to a parent where the parent is the desired thing, e.g.

```sage: f = SR(1+x)
sage: R.<t>= ZZ[[]]
sage: P=R['x']
sage: P(f)(x=t^2+O(t^3))
```

What if

```f = SR(1+2^x)
```

or something worse (including e.g. exp, log, sin, ... or other functions)? There are no parents (except SR) for any of these constructs.

### comment:19 in reply to: ↑ 18 ; follow-ups: ↓ 20 ↓ 31 Changed 7 years ago by nbruin

What if

```f = SR(1+2^x)
```

or something worse (including e.g. exp, log, sin, ... or other functions)? There are no parents (except SR) for any of these constructs.

And indeed it's tricky to evaluate the result. What is `2^<power series>`? I guess `exp(log(2)*x)`, which requires a ring that contains both `log(2)` and inverses of all integers., so that doesn't work in `Z[[t]]`. I think Sage is right in putting the onus on the user to first find a parent in which the expression fits and where the evaluation behaviour is the desired one.

Anyway, `fast_callable` takes a best effort approach towards compiling a program that tries to perform the evaluation, so that might be your best bet.

### comment:20 in reply to: ↑ 19 Changed 7 years ago by dkrenn

Anyway, `fast_callable` takes a best effort approach towards compiling a program that tries to perform the evaluation, so that might be your best bet.

Ok, I'll make some experiments and run some tests to see if it satisfies my needs.

Thanks

### comment:21 follow-up: ↓ 22 Changed 7 years ago by vdelecroix

Hello,

Would this ticket solve the following issue (from #9787)?

```sage: parent(exp(1.2))
Real Field with 53 bits of precision
sage: f(x) = exp(x)
sage: parent(f(1.2))
Symbolic Ring
```

Vincent

### comment:22 in reply to: ↑ 21 ; follow-up: ↓ 23 Changed 7 years ago by dkrenn

Hello,

Would this ticket solve the following issue (from #9787)?

```sage: parent(exp(1.2))
Real Field with 53 bits of precision
sage: f(x) = exp(x)
sage: parent(f(1.2))
Symbolic Ring
```

Yes.

```sage: f(x).evaluate({x: 1.2}).parent()
Real Field with 53 bits of precision
```

### comment:23 in reply to: ↑ 22 ; follow-up: ↓ 24 Changed 7 years ago by vdelecroix

Hello,

Would this ticket solve the following issue (from #9787)?

```sage: parent(exp(1.2))
Real Field with 53 bits of precision
sage: f(x) = exp(x)
sage: parent(f(1.2))
Symbolic Ring
```

Yes.

```sage: f(x).evaluate({x: 1.2}).parent()
Real Field with 53 bits of precision
```

Sorry. This was not my question. What would be `parent(f(1.2))`? Is this modified by this ticket?

### comment:24 in reply to: ↑ 23 ; follow-up: ↓ 25 Changed 7 years ago by dkrenn

Would this ticket solve the following issue (from #9787)?

```sage: parent(exp(1.2))
Real Field with 53 bits of precision
sage: f(x) = exp(x)
sage: parent(f(1.2))
Symbolic Ring
```

[...]

Sorry. This was not my question. What would be `parent(f(1.2))`? Is this modified by this ticket?

No, not modified by this ticket.

### comment:25 in reply to: ↑ 24 ; follow-up: ↓ 26 Changed 7 years ago by vdelecroix

Would this ticket solve the following issue (from #9787)?

...

Sorry. This was not my question. What would be `parent(f(1.2))`? Is this modified by this ticket?

No, not modified by this ticket.

By the way, let me repeat another question from #9878. I found the behavior of `evaluate` in your comment:22 very weird. I thought it was a modification of `.subs` in order to take care of the parent. But

```sage: f(x) = 2*x
sage: f.subs(x=3)
x |--> 6
```

ie, `f` remains a function. It is hopefully not changed into a number.

### comment:26 in reply to: ↑ 25 ; follow-up: ↓ 27 Changed 7 years ago by dkrenn

By the way, let me repeat another question from #9878. I found the behavior of `evaluate` in your comment:22 very weird. I thought it was a modification of `.subs` in order to take care of the parent.

In the following it does the same as subs:

```sage:  sage: f(x) = 2*x
sage:  sage: f(x).subs(x=3)
6
sage:  sage: f(x).evaluate(x=3)
6
```

But

```sage: f(x) = 2*x
sage: f.subs(x=3)
x |--> 6
```

ie, `f` remains a function. It is hopefully not changed into a number.

Indeed, this changes (I wasn't aware of this up to now):

```sage:  sage: f.evaluate(x=3)
6
```

This is because `evaluate` uses

```sage: f.operator()
<built-in function mul>
sage: f.operands()
[x, 2]
```

From this, `f` is equal to `2*x`. Sage sees these two as equal as well:

```sage: bool(f == 2*x)
True
```
Last edited 7 years ago by dkrenn (previous) (diff)

### comment:27 in reply to: ↑ 26 ; follow-up: ↓ 29 Changed 7 years ago by vdelecroix

By the way, let me repeat another question from #9878. I found the behavior of `evaluate` in your comment:22 very weird. I thought it was a modification of `.subs` in order to take care of the parent.

From this, `f` is equal to `2*x`. Sage sees these two as equal as well:

```sage: bool(f == 2*x)
True
```

Argh. Definitely a bug to me. Another bug is that the variable defining a function should be transparent. And currently

```sage: f(x) = 2*x
sage: g(y) = 2*y
sage: bool(f == g)
False
```

Vincent

### comment:28 Changed 7 years ago by rws

Please Cc: me with any ticket you open regarding `Expression.nonzero()` or pynac.

### comment:29 in reply to: ↑ 27 Changed 7 years ago by dkrenn

```sage: f(x) = 2*x
```

From this, `f` is equal to `2*x`. Sage sees these two as equal as well:

```sage: bool(f == 2*x)
True
```

Argh. Definitely a bug to me. Another bug is that the variable defining a function should be transparent. And currently

```sage: f(x) = 2*x
sage: g(y) = 2*y
sage: bool(f == g)
False
```

This is now #18259.

### comment:30 follow-up: ↓ 32 Changed 7 years ago by rws

As to the original `subs` error, nbruin has explained why there is no general solution, a workaround for polynomials would be

```sage: P.<p> = ZZ[[]]
sage: x.power_series(ZZ)
x + O(x^2)
sage: P(_)
p + O(p^2)
```

I believe a more general way to have all possibilities of both `SR` and the series rings is to fix conversions between them, and use a series ring over `SR`. This depends on #17659, please review.

### comment:31 in reply to: ↑ 19 Changed 6 years ago by cheuberg

Anyway, `fast_callable` takes a best effort approach towards compiling a program that tries to perform the evaluation, so that might be your best bet.

I had another instance where I needed a version of `.subs` like in this ticket, because there is no coercion from a number field to the symbolic ring.

```sage: K.<omega> = NumberField(x^4 + 1)
sage: var('u')
sage: z = u/(u + 1)^2
sage: z.subs(u=omega)
Traceback (most recent call last):
...
TypeError: no canonical coercion from Number Field
in omega with defining polynomial x^16 + 1 to
Symbolic Ring
```

Using `fast_callable` works in this case:

```sage: fast_callable(z, vars=[u])(omega)
1/2*omega^3 - 1/2*omega + 1
```

It works, but the solution is hard to find and the notation a bit cumbersome.

I see several solutions:

1. adding a link to `fast_callable` and some examples from this ticket to the documentation of `subs`.
2. indeed create a method as proposed here which acts as a wrapper for `fast_callable`.

Opinions?

### comment:32 in reply to: ↑ 30 Changed 6 years ago by cheuberg

I believe a more general way to have all possibilities of both `SR` and the series rings is to fix conversions between them, and use a series ring over `SR`. This depends on #17659, please review.

Is it realistic to hope that all conversions will exist? Do they always make sense?

### comment:33 Changed 6 years ago by rws

I am now neutral or positive on this ticket. Still,

Is it realistic to hope that all conversions will exist? Do they always make sense?

I suspect many will and do. You can easily find out by reviewing #16203 and #17402.

Note: See TracTickets for help on using tickets.