Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mathjax display of symbolic output from SymPy not rendering in @manipulate #275

Open
AshtonSBradley opened this issue Oct 29, 2018 · 12 comments

Comments

@AshtonSBradley
Copy link

Previously I could view a range of Hermite polynomials inside @manipulate.
Screenshot probably says it all here:

screen shot 2018-10-30 at 5 37 56 am

any way to get it to render?

@piever
Copy link
Collaborator

piever commented Oct 29, 2018

Please add backticks next to @manipulate not to ping the manipulate github user :)

Mhm, I think it's because we specifically disable MathJax inside widgets as it messes things up a bit (and we needed a different method anyway for it to work also in Blink or Juno), but we support LaTeX via KaTeX any way (but not automatically). Is SymPy returning a LaTeXString? If that's the case I could probably just add a method to render LaTeXString.

@AshtonSBradley
Copy link
Author

oops.

Not sure, here is what I can get from it, if I "ctrl+click" (MacOs) on the output I get:

screen shot 2018-10-30 at 7 10 53 am

showing that latex is one option (maybe what it is defaulting to without the expected display?)

If I

typeof(H(5,x))
Sym

So I guess there is some processing of the Sym object before display in Mathjax.

@AshtonSBradley
Copy link
Author

MVE for Jupyter:

using Interact, SymPy

x = Sym("x")

@manipulate for n=1:3
    x^n
end

@AshtonSBradley
Copy link
Author

maybe you know a fix for this @stevengj ? Could LaTeXStrings fix this somehow?

The problem: @manipulate no longer renders mathjax output from SymPy

screen shot 2018-11-22 at 4 47 14 pm

@piever
Copy link
Collaborator

piever commented Nov 22, 2018

No, this has to be fixed in Interact. What happens is that the new Interact framework is designed to work not only in Jupyter but also in Blink, Juno plotpane and the browser. Normally Jupyter runs MathJax by default on things that you display but we had to disable it inside Interact widgets as they would for some reason recompile indefinitely. I can test if that's still necessary though.

We have support for LaTeX using KaTeX but don't yet have a method to render LaTeXStrings, but it should definitely live in Interact (it's strange for such a small package as LaTeXStrings to depend on web technology things). For me to understand, what MIME type do things overload to signal that they want to be MathJax compiled in Jupyter?

@AshtonSBradley
Copy link
Author

I guess this is a question for SymPy.jl devs (@jverzani, @mzaffalon ?), but this snippet generates the object that is rendered outside @manipulate, and then doesn't get rendered inside @manipulate:

using SymPy, Interact
x = Sym("x")
n=3
    SymPy.diff(sin(x^2),x,n)

can you get the MIME types by running this and interrogating the output? (way above my pay grade, sorry)

@AshtonSBradley
Copy link
Author

Hi @piever, any chance for a cure here? I am not quite sure how to get this:

For me to understand, what MIME type do things overload to signal that they want to be MathJax compiled in Jupyter?

from the SymPy output

@piever
Copy link
Collaborator

piever commented Mar 12, 2019

It can't be fixed from here without type piracy. What happens is that WebIO.render(x) (where x is a symbol from SymPy) has a fallback which is not the correct one:

julia> x = symbols("x");

julia> WebIO.render(x)
(div { setInnerHtml="<pre>\\begin{equation*}x\\end{equation*}</pre>" })

The correct method would be:

using WebIO, Interact, SymPy
function WebIO.render(x::SymPy.SymbolicObject)
    str = stringmime(MIME"text/latex"(), x)
    lt = match(r"^\\begin{equation\*}(.*)\\end{equation\*}$", str)[1]
    WebIO.render(latex(lt))
end

There are two ways forward:

  1. hacky: you can add this code and your notebook and things will work for you
  2. SymPy accepts to define this in their own package (possibly using Require to avoid the Interact dependency), but I suspect this approach does not scale.
  3. WebIO figures out a better fallback when richest_mime(t) == MIME"text/latex"()

I can ask over at WebIO whether there option 3 is available (it is somewhat complicated by the fact that different packages use different delimiters, i.e. $$ versus \begin{equation*}.

@AshtonSBradley
Copy link
Author

Ok, hacky is fine for now. I tried this, and got this:

using WebIO, Interact, SymPy
function WebIO.render(x::SymPy.SymbolicObject)
    str = stringmime(MIME"text/latex"(), x)
    lt = match(r"^\\begin{equation\*}(.*)\\end{equation\*}$", str)[1]
    WebIO.render(latex(lt))
end
@syms x
@manipulate for n = 1:100
    x^n
end

Widget{:manipulate,Any}(OrderedDict{Symbol,Any}(:n=>Widget{:slider,Int64}(OrderedDict{Symbol,Any}(:changes=>Observable{Int64} with 1 listeners. Value:
0,:value=>Observable{Int64} with 2 listeners. Value:
50), Observable{Int64} with 2 listeners. Value:
50, Scope("knockout-component-3f7f60e5-26e1-4383-b3ab-0d8c858cbc5b", Node{DOM}(DOM(:html, :div), Any[Node{DOM}(DOM(:html, :div), Any[Node{DOM}(DOM(:html, :label), Any["n"], Dict{Symbol,Any}(:className=>"interact ",:style=>Dict{Any,Any}(:padding=>"5px 10px 0px 10px")), 1)], Dict{Symbol,Any}(:attributes=>Dict("class"=>"interact-flex-row-left")), 2), Node{DOM}(DOM(:html, :div), Any[Node{DOM}(DOM(:html, :input), Any[], Dict{Symbol,Any}(:max=>100,:min=>1,:attributes=>Dict{Any,Any}(:type=>"range",Symbol("data-bind")=>"numericValue: value, valueUpdate: 'input', event: {change : function () {this.changes(this.changes()+1)}}","orient"=>"horizontal"),:step=>1,:className=>"slider slider is-fullwidth",:style=>Dict{Any,Any}()), 0)], Dict{Symbol,Any}(:attributes=>Dict("class"=>"interact-flex-row-center")), 1), Node{DOM}(DOM(:html, :div), Any[Node{DOM}(DOM(:html, :p), Any[], Dict{Symbol,Any}(:attributes=>Dict("data-bind"=>"text: value")), 0)], Dict{Symbol,Any}(:attributes=>Dict("class"=>"interact-flex-row-right")), 1)], Dict{Symbol,Any}(:attributes=>Dict("class"=>"interact-flex-row")), 7), Dict{String,Tuple{Observables.AbstractObservable,Union{Nothing, Bool}}}("changes"=>(Observable{Int64} with 1 listeners. Value:
0, nothing),"value"=>(Observable{Int64} with 2 listeners. Value:
50, nothing)), Set(String[]), nothing, WebIO.WebAsset[JSAsset("knockout", "/assetserver/86aaaf4300cd90c5f620536dd2224bed4f19a561-knockout.js"), JSAsset("knockout_punches", "/assetserver/46a9638687b90cb7624fcc0fcabecc8e23adf106-knockout_punches.js"), JSAsset("/Users/abradley/.julia/packages/InteractBase/PTCUD/src/../assets/all.js", "/assetserver/807594d219073e1485a4f82e7d148aff37076356-all.js"), CSSAsset("/Users/abradley/.julia/packages/InteractBase/PTCUD/src/../assets/style.css", "/assetserver/30efb393df553231951d529802321f5b11f5174d-style.css"), CSSAsset("/Users/abradley/.julia/packages/InteractBulma/PH56C/src/../assets/main.css", "/assetserver/bf49bf6827b9e80e09d26b61b9a5769db15f65f4-main.css")], Dict{Any,Any}("changes"=>Any[JSString("(function (val){return (val!=this.model"changes") ? (this.valueFromJulia["changes"]=true, this.model"changes") : undefined})")],"value"=>Any[JSString("(function (val){return (val!=this.model"value") ? (this.valueFromJulia["value"]=true, this.model"value") : undefined})")]), ConnectionPool(Channel{Any}(sz_max:9223372036854775807,sz_curr:0), Set(AbstractConnection[]), Channel{AbstractConnection}(sz_max:32,sz_curr:0)), WebIO.JSString[JSString("function () {\n var handler = (function (ko, koPunches) {\n ko.punches.enableAll();\n ko.bindingHandlers.numericValue = {\n init : function(element, valueAccessor, allBindings, data, context) {\n var stringified = ko.observable(ko.unwrap(valueAccessor()));\n stringified.subscribe(function(value) {\n var val = parseFloat(value);\n if (!isNaN(val)) {\n valueAccessor()(val);\n }\n })\n valueAccessor().subscribe(function(value) {\n var str = JSON.stringify(value);\n if ((str == "0") && (["-0", "-0."].indexOf(stringified()) >= 0))\n return;\n if (["null", ""].indexOf(str) >= 0)\n return;\n stringified(str);\n })\n ko.applyBindingsToNode(element, { value: stringified, valueUpdate: allBindings.get('valueUpdate')}, context);\n }\n };\n var json_data = JSON.parse("{\"changes\":0,\"value\":50}");\n var self = this;\n function AppViewModel() {\n for (var key in json_data) {\n var el = json_data[key];\n this[key] = Array.isArray(el) ? ko.observableArray(el) : ko.observable(el);\n }\n \n \n [this["changes"].subscribe((function (val){!(this.valueFromJulia["changes"]) ? (WebIO.setval({"name":"changes","scope":"knockout-component-3f7f60e5-26e1-4383-b3ab-0d8c858cbc5b","id":"ob_02","type":"observable"},val)) : undefined; return this.valueFromJulia["changes"]=false}),self),this["value"].subscribe((function (val){!(this.valueFromJulia["value"]) ? (WebIO.setval({"name":"value","scope":"knockout-component-3f7f60e5-26e1-4383-b3ab-0d8c858cbc5b","id":"ob_01","type":"observable"},val)) : undefined; return this.valueFromJulia["value"]=false}),self)]\n \n }\n self.model = new AppViewModel();\n self.valueFromJulia = {};\n for (var key in json_data) {\n self.valueFromJulia[key] = false;\n }\n ko.applyBindings(self.model, self.dom);\n}\n);\n Promise.all([WebIO.loadJS({"name":"knockout","url":"/assetserver/86aaaf4300cd90c5f620536dd2224bed4f19a561-knockout.js"}),WebIO.loadJS({"name":"knockout_punches","url":"/assetserver/46a9638687b90cb7624fcc0fcabecc8e23adf106-knockout_punches.js"})]).then((imports) => handler.apply(this, imports));\n}\n")]), ##52#53{#dom#13{##dom#11#12{Dict{Any,Any},DOM}},typeof(scope)}(#dom#13{##dom#11#12{Dict{Any,Any},DOM}}(##dom#11#12{Dict{Any,Any},DOM}(Dict{Any,Any}(:className=>"field"), DOM(:html, :div))), scope))), Observable{Any} with 0 listeners. Value:
x^50, nothing, getfield(InteractBase, Symbol("##142#143"))())

@piever
Copy link
Collaborator

piever commented Mar 15, 2019

My bad, stringmime was moved to stdlib Base64, so you need:

using WebIO, Interact, SymPy, Base64
function WebIO.render(x::SymPy.SymbolicObject)
    str = stringmime(MIME"text/latex"(), x)
    lt = match(r"^\\begin{equation\*}(.*)\\end{equation\*}$", str)[1]
    WebIO.render(latex(lt))
end

@stevengj
Copy link
Contributor

No need for stringmime — that's only needed if x might have binary data that needs base64 encoding. Just do repr(MIME"text/latex"(), x)

@AshtonSBradley
Copy link
Author

AshtonSBradley commented Mar 16, 2019

to summarise, this

using WebIO, Interact, SymPy
function WebIO.render(x::SymPy.SymbolicObject)
    str = repr(MIME"text/latex"(), x)
    lt = match(r"^\\begin{equation\*}(.*)\\end{equation\*}$", str)[1]
    WebIO.render(latex(lt))
end

gets @manipulate working again to a degree, for SymPy objects, e.g.

@syms x
@manipulate for n = 1:100
    x^n
end

will give a slider that renders output. It's not quite as it was, because now it only renders when you stop dragging the slider, whereas previously it updated in real time.

Full disclosure: tested on webio-retool branch. Ah, the plot thickens as I was testing in jupyterlab. Testing again in standard notebook, I find the output doesn't update. Could be branch issue for web-retool

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants