jupyter-book / jupyterlab-myst

Use MyST Markdown directly in Jupyter Lab
https://jupyter-book.github.io/jupyterlab-myst/
BSD 3-Clause "New" or "Revised" License
146 stars 18 forks source link

Weird re-rendering of adjacent cells with math #96

Open fperez opened 1 year ago

fperez commented 1 year ago

Describe the bug

context

I was experimenting with math examples. I defined a simple integral via

import sympy as sy
x = sy.Symbol('x')
expr = x**2 + x + 1
I = sy.Integral(expr, (x, 0, 1))

And then I made a markdown cell with:

The integral

{eval}`I`

can be analytically computed:

{eval}`I` = {eval}`I.doit()`

bug

I noticed two (no idea if related or not):

See video for illustration:

myst-math

Reproduce the bug

See above

List your environment

No response

rowanc1 commented 1 year ago

Ok, we will need to investigate this. It is possible we need to trigger a math pass by mathjax and that isn't happening, it seems pretty unlikely that something is blocking and taking that long rather than something just not being called soon enough.

The other cell is effected because the myst notebook is currently treating the cells as interdependent from the markdown perspective. That is, you can create an equation in one cell with a label, and then reference it in another cell with a hover-preview, that requires knowledge of the whole document, which is quite different than how the traditional notebook works.

We don't rerender the inline expressions unless you execute them intentionally, however, the current process is causing React to rerender, and then re-attach the widget - which now interacts with the kernel. We can probably stop this behaviour by having better management over the react keys.

I will do some investigation with @stevejpurves and/or @agoose77!!

fperez commented 1 year ago

Thx for looking into it! One thing I found useful was to add a print statement inside the observer callbacks. It shows that each callback is getting triggered twice per interaction, which in some cases could actually be problematic.

Those print statements show up conveniently in JuptyerLab's console log (as they are asynchronous, they don't have any natural cell to go to):

image

I think some profiling and attention to performance with long and complex documents is going to be necessary, and thinking of ways to keep the interactive experience smooth will be critical to success. I can easily imagine people going wild with complex documents that have a lot of interactions, but it will be problematic if performance slows to a crawl.

This system is fabulous, and just last night in a bit of playing I found myself rethinking how I'd write notebooks in th[e future. I think this is going to lead to changes in how we write our content, and probably to an evolution of the UI itself to support that. I can imagine wanting a lot more control over hiding code cells, and creating documents where a lot of the code that's of interest to the user, with very few exceptions, is written in these markdown cells.

See for example the short math illustrations I added using Sympy - that's just scratching the surface in a few minutes, but I can imagine all kinds of neat use cases (and @rowanc1's comments in #95 point in all kinds of cool directions).

For all this to be as successful and impactful as I think it will be, a very fluid experience is critical. I'm thinking of Notion as a kind of benchmark for how seamless and fluid it needs to feel, so the engine gets out of the way of the user and is only there to support them but without ever interfering (e.g. I find today's completion engine in JupyterLab to be the opposite of that - it mostly only interrupts me and gets in my way, rarely being actually helpful).

I want to make clear that this is NOT at all criticism!! It's the opposite :) I'm crazy excited about the possibilities, so I want to probe hard the limitations of what we have, even early on, so we target key issues to really make this fly. Thanks a lot for the awesome work, team!! 🚀

agoose77 commented 1 year ago

@fperez i assume that you used the observe mechanism to print these values. Did you listen specifically to the 'value' trait? If not, you'll see the observer triggered multiple times as there are several traits that are updated on a widget.

fperez commented 1 year ago

Yup, it was just a bare print(n) inserted in the fp() observable callback in the cookie example here. I was just curious whether those multiple triggers could lead to a performance issue, but I admit my widget-fu is very limited. At least the old/new behavior seems to have surprised others too, as per this question on the jlab gitter channel.

Thx for the pointer though! I do intend to get better with the widgets machinery now, as I think the inline tools open up lots of really interesting use cases especially for pedagogy.

agoose77 commented 1 year ago

If you run this code in a cell

import ipywidgets as w

s = w.IntSlider()

@s.observe
def func(change):
    print(change)
s

You'll see the following events with movement of the slider:

{'name': '_property_lock', 'old': {}, 'new': {'value': 8}, 'owner': IntSlider(value=7), 'type': 'change'}
{'name': 'value', 'old': 7, 'new': 8, 'owner': IntSlider(value=8), 'type': 'change'}
{'name': '_property_lock', 'old': {'value': 8}, 'new': {}, 'owner': IntSlider(value=8), 'type': 'change'}

because _property_lock is a trait (widgets use locks to allow batching of events).

If you only listen to the event that we care about here (changes to value), the callback should be triggered once (a single event):

import ipywidgets as w

s = w.IntSlider()

def func(change):
    print(change)

s.observe(func, "value")
s
{'name': 'value', 'old': 0, 'new': 1, 'owner': IntSlider(value=1), 'type': 'change'}
{'name': 'value', 'old': 1, 'new': 4, 'owner': IntSlider(value=4), 'type': 'change'}
{'name': 'value', 'old': 4, 'new': 8, 'owner': IntSlider(value=8), 'type': 'change'}
fperez commented 1 year ago

Oops, reopening - I had a typo and meant to close #94, not this one 🤦 Sorry!

maartenbreddels commented 1 year ago

@fperez do you think the explanation of @agoose77 solves the widget issue you talked about on gitter?

fperez commented 1 year ago

Yes it does @maartenbreddels! And thanks @agoose77 for the explanation!! My widgets-fu is super primitive, so this helps me a lot.