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

Use fixed width text for compound variable names #1305

Merged
merged 7 commits into from
Oct 20, 2024

Conversation

ChrisRackauckas
Copy link
Member

capacitor makes sense as code but does not make as much sense inside of latex output. When you look at it, it italics each of the letters which makes it look like its c times a times ..., which isn't quite right.

Instead, this uses \texttt{name} in order to more clearly delineate a value which is a compound name. Using the fixed width text also looks quite "computer-y" so it has a nice connection to verbose and is clearly a "computer variable" in the expression.

`capacitor` makes sense as code but does not make as much sense inside of latex output. When you look at it, it italics each of the letters which makes it look like its c times a times ..., which isn't quite right.

Instead, this uses `\texttt{name}` in order to more clearly delineate a value which is a compound name. Using the fixed width text also looks quite "computer-y" so it has a nice connection to `verbose` and is clearly a "computer variable" in the expression.
@hersle
Copy link
Contributor

hersle commented Oct 15, 2024

I think this is a good change. Does it also properly render "long" unicode variable names? Like

@variables δt αβ b₊Ω0

@ChrisRackauckas
Copy link
Member Author

Handled those cases:

using Latexify

@variables δt αβ b₊Ω0 b₊Ω_0
latexify(δt)
latexify(αβ)
latexify(b₊Ω0)
latexify(b₊Ω_0)
Screenshot 2024-10-20 at 4 19 57 AM

@hersle
Copy link
Contributor

hersle commented Oct 20, 2024

Nice. I think this makes perfect sense for capacitor, but I feel slightly mixed about cases like δt/Δt, because a user that defines such a variable probably wants it rendered in the normal math font. Some other tricky cases I can think of are

latexify(@variables Ω₀ Ω′ Ω̇) # last one is \Omega\dot (looks better in e.g. VSCode)

which currently give what I like (I would dislike these with \texttt{}):

... \Omega{_0} \Omega\prime \dot{\Omega} ...

I'm not sure what would be a good and simple way to handle all cases 😅

@hersle
Copy link
Contributor

hersle commented Oct 20, 2024

What do you think about texttt-ing all-but-the-last namespace-separated parts of a variable name regardless of their lengths? Something like

    # ...
    if issym(O) 
        sym = string(nameof(O))
        parts = split(sym, NAMESPACE_SEPARATOR)
        map!(part -> "\\texttt{$part}", parts, parts[begin:end-1]) # \texttt subsystem names
        sym = join(parts, ".") # replace NAMESPACE_SEPARATOR => "."
        return Symbol(sym)
    end
    # ...

That would effectively texttt{} all subsystem names, so

latexify(@variables circuit₊capacitor₊C b₊Ω₀ perturbation₊δP)

would give

... \texttt{circuit}.\texttt{capacitor}.C \texttt{b}.\Omega{_0} \texttt{perturbation}.{\delta}P ...

I think this would also help distinguish subsystems from variables, and leave the variable rendering "untouched".

EDIT: Maybe combined with your handling of compound unicode names etc., so compound unicode names are valid subsystem names?

@ChrisRackauckas
Copy link
Member Author

I don't know about that. \texttt{circuit}.\texttt{capacitor}.C I'm not sure that's really better? If it's namespaced then it's already in "computer land". I'd be willing to review a PR on it but I want to at least get this iteration in.

@ChrisRackauckas ChrisRackauckas merged commit 630bd7a into master Oct 20, 2024
11 of 13 checks passed
@ChrisRackauckas ChrisRackauckas deleted the fixedwidth_tex branch October 20, 2024 11:57
@hersle
Copy link
Contributor

hersle commented Oct 20, 2024

Sorry for my rambling, I realize now that your changes already handle those tricky cases well. This is much better 👍

@isaacsas
Copy link
Contributor

isaacsas commented Oct 21, 2024

I wasn't aware of this PR, but I don't think it makes much sense for Catalyst. A reaction like A + B --> AB is now going to look weird with fixed width on one side of the equation and regular width on the other. It would've been nice to make this configurable via a kwarg.

@isaacsas
Copy link
Contributor

To illustrate that reaction now generates:

L"\begin{align*}
\mathrm{A} + \mathrm{B} &\xrightarrow{k} \mathrm{\texttt{AB}}  
 \end{align*}
"

which is a pretty strange typesetting.

@ChrisRackauckas
Copy link
Member Author

How would you propose putting a kwarg on it?

@isaacsas
Copy link
Contributor

Can't one add kwargs to each @latexreceipe f(...; kwargs...) that calls recipe(n)? That would allow us to override this behavior in ReactionSystem printing I think. Though it wouldn't fix Latex display of ODESystems generated from a ReactionSystem where A and AB would still render differently.

Alternatively, in Catalyst we have a global Latex config struct to allow one to change such options. If Symbolics had something like that we could just change it when Catalyst gets loaded. Having such a struct store the wrapping tex command, and length at which to apply it, would probably be enough. This would also have the benefit of modifying printing for systems that are converted from a Catalyst reaction system (if Catalyst changes the convention once loaded). But I guess that could also be considered a drawback as the modifications Catalyst makes would propagate to non-Catalyst created/converted systems too.

Alternatively, the suggestion above to only apply \texttt to everything before the final separator would fix this too I guess.

@isaacsas
Copy link
Contributor

Or we could add an optional field to MTK systems through which a user can pass a printing options struct to control these kwargs when displaying the system. Then MTK could have a default that Catalyst overrides for Catalyst-generated systems, and Symbolics just needs the printing options struct and associated kwarg in the receipes.

@isaacsas
Copy link
Contributor

I like this last option, since it means Symbolics can by default not use \texttt, which seems to really be an MTK-hierarchical system-focused choice.

@ChrisRackauckas
Copy link
Member Author

No? Why would Symbolics not default to \texttt on a variable like myvariable? It does not make sense to read that as 10 different variables which is the default Latex output.

@ChrisRackauckas
Copy link
Member Author

Or we could add an optional field to MTK systems through which a user can pass a printing options struct to control these kwargs when displaying the system. Then MTK could have a default that Catalyst overrides for Catalyst-generated systems, and Symbolics just needs the printing options struct and associated kwarg in the receipes.

Why a field? Why would that need to be changed at runtime?

@ChrisRackauckas
Copy link
Member Author

I actually don't think

L"\begin{align*}
\mathrm{A} + \mathrm{B} &\xrightarrow{k} \mathrm{\texttt{AB}}  
 \end{align*}
"

is so bad. It disambigues AB from A*B which are the same in LaTeX. The issue here is that chemistry syntax is ambiguous, while computer system have difficulties with ambiguity.

@isaacsas
Copy link
Contributor

Having a field means that when Catalyst generates an ODESystem it could pass in the Latex display options to the system (which would then propagate to Symbolics when that particular system is displayed / converted to Latex).

@ChrisRackauckas
Copy link
Member Author

Why not just a query function latexoptions(::AbstractSystem)?

@isaacsas
Copy link
Contributor

isaacsas commented Oct 21, 2024

So there would be a global MTK setting for what gets passed to Symbolics?

Having it (optionally) stored in the system means that Catalyst can change it for Catalyst-generated ODESystems and such without impacting the display of a manually created ODESystem in the same notebook and/or REPL session.

@ChrisRackauckas
Copy link
Member Author

I see what you mean, so you do need a runtime overload. I think it could be fine to have a latexoptions piece added for customization. Though I'm not convinced there isn't a good general solution here. With Catalyst, it would be interesting though to use chemical notation d[AB]/dt in the generated TeX though.

@isaacsas
Copy link
Contributor

Adding support for a user choosingd[AB]/dt would make sense, but that notation usually means concentration so doesn't necessarily work for all Catalyst models (for example, an SIR type model or one where using units of number of molecules to compare with stochastic simulations). But presumably that could be turned on by setting the length to zero and the wrapper to [ ] instead of \texttt (though it probably wouldn't make sense to add it on sub or superscripts too).

@isaacsas
Copy link
Contributor

Are you envisioning that latexoptions(sys) returns a configuration option object that can be passed down to Symbolics? If so, I don't see how it can be made seemless to users unless the system knows about it, and can hence pass it through when various stuff calls latexify(sys) (keep in mind users often don't call that -- it is implicitly called in notebook environments via show).

@ChrisRackauckas
Copy link
Member Author

Interesting.

string("\\texttt", "{", sym, "}"))
convert(String, LaTeXString(string("\\texttt", "{", sym, "}"))

fails with:

ERROR: in Latexify.jl:
You are trying to create latex-maths from a `String` that cannot be parsed as
an expression: `\texttt{{\delta}t}`.

`latexify` will, by default, try to parse any string inputs into expressions
and this parsing has just failed.

If you are passing strings that you want returned verbatim as part of your input,
try making them `LaTeXString`s first.

If you are trying to make a table with plain text, try passing the keyword
argument `latex=false`. You should also ensure that you have chosen an output
environment that is capable of displaying not-maths objects. Try for example
`env=:table` for a latex table or `env=:mdtable` for a markdown table.

Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:35
  [2] _latexraw(::Val{…}, i::String; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:158
  [3] _latexraw(i::String; parse::Bool, kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:148
  [4] process_latexify(args::String; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:49
  [5] process_latexify
    @ ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:40 [inlined]
  [6] latexraw
    @ ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:58 [inlined]
  [7] _latexequation(eq::String; starred::Bool, kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexequation.jl:5
  [8] process_latexify(args::Num; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:49
  [9] process_latexify
    @ ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:40 [inlined]
 [10] latexify(args::Num; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:27
 [11] latexify(args::Num)
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:25
 [12] top-level scope
    @ ~/Desktop/test.jl:107

caused by: ParseError:
# Error @ none:1:1
\texttt{{\delta}t}
╙ ── not a unary operator
Stacktrace:
  [1] #parse#3
    @ ./meta.jl:244 [inlined]
  [2] parse
    @ ./meta.jl:236 [inlined]
  [3] parse(str::String; filename::String, raise::Bool, depwarn::Bool)
    @ Base.Meta ./meta.jl:278
  [4] parse
    @ ./meta.jl:276 [inlined]
  [5] _latexraw(::Val{…}, i::String; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:155
  [6] _latexraw(i::String; parse::Bool, kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:148
  [7] process_latexify(args::String; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:49
  [8] process_latexify
    @ ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:40 [inlined]
  [9] latexraw
    @ ~/.julia/packages/Latexify/ieukI/src/latexraw.jl:58 [inlined]
 [10] _latexequation(eq::String; starred::Bool, kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexequation.jl:5
 [11] process_latexify(args::Num; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:49
 [12] process_latexify
    @ ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:40 [inlined]
 [13] latexify(args::Num; kwargs::@Kwargs{})
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:27
 [14] latexify(args::Num)
    @ Latexify ~/.julia/packages/Latexify/ieukI/src/latexify_function.jl:25
 [15] top-level scope
    @ ~/Desktop/test.jl:107
Some type information was truncated. Use `show(err)` to see complete types.

only

LaTeXString(string("\\texttt", "{", sym, "}"))

works. @gustaphe can you help us figure out a good solution here?

@hersle
Copy link
Contributor

hersle commented Oct 21, 2024

I tried #1317

@ChrisRackauckas
Copy link
Member Author

Are you envisioning that latexoptions(sys) returns a configuration option object that can be passed down to Symbolics? If so, I don't see how it can be made seemless to users unless the system knows about it, and can hence pass it through when various stuff calls latexify(sys) (keep in mind users often don't call that -- it is implicitly called in notebook environments via show).

Yeah some SymbolicLatexOptions struct in Symbolics.jl that can be specified in an ODESystem construction or it just uses the default.

@isaacsas
Copy link
Contributor

isaacsas commented Oct 21, 2024

Yeah some SymbolicLatexOptions struct in Symbolics.jl that can be specified in an ODESystem construction or it just uses the default.

Ok, that is exactly what I was trying to propose (with a user-customized one then stored in the system, or else the default is used).

@hersle
Copy link
Contributor

hersle commented Oct 21, 2024

Though I'm not convinced there isn't a good general solution here.

I agree that this must exist, and that finding it is most important.

What do you think about an optional font keyword that simply wraps a whole expression, rather than variable-by-variable? This better respects how variables are originally defined. Unlike \text**{}, \math**{} seems to do this in a unicode-safe way. It would make sense to me to write something like

@variables A₁ β₂ ℂ₃ fourth_letter₄
latexify(A₁ + β₂ ~ ℂ₃ + fourth_letter₄) # no styling; equivalent to font = :normal
latexify(A₁ + β₂ ~ ℂ₃ + fourth_letter₄; font = :rm)
latexify(A₁ + β₂ ~ ℂ₃ + fourth_letter₄; font = :tt)
latexify(A₁ + β₂ ~ ℂ₃ + fourth_letter₄; font = :bb)

and have it simply give

A{_1} + \beta{_2} = fourth\_letter{_4} + \mathbb{C}{_3}
\mathrm{A{_1} + \beta{_2} = fourth\_letter{_4} + \mathbb{C}{_3}}
\mathtt{A{_1} + \beta{_2} = fourth\_letter{_4} + \mathbb{C}{_3}}
\mathbb{A{_1} + \beta{_2} = fourth\_letter{_4} + \mathbb{C}{_3}}

which renders as (note it is also "safe" to have an inner \mathbb{C} variable inside any outer \math**{})
image

Then it would make sense to me that

  • Symbolics defaults to no styling (for purity and because of common short variable names)
  • ModelingToolkit defaults to font = :rm or font = :tt (focused to hierarchical systems)
  • Any other user or package can override the styling (in a way like you discussed)

I think this is simple, consistent, has less potential for surprises, and handles the cases discussed so far.

@hersle
Copy link
Contributor

hersle commented Oct 21, 2024

This could probably also be handled mostly on the Latexify side (cc @gustaphe), so users could also use the existing Latexify.set_default() with e.g. font added to the keyword arguments to customize their behavior, if needed.

@hersle
Copy link
Contributor

hersle commented Oct 21, 2024

I guess this is just gustaphe's suggestion, but with variable_style = math**, which would make it work on whole expressions?

@gustaphe
Copy link
Contributor

I'm happy to help if there is some change in Latexify that could make this more general and configurable. I don't quite know the internals of this package well enough though, and I'm not sure I understand all of this thread. Let me know if you need anything specific.

The philosophy of Latexify has mostly been that "people will know not to give variables multi-character names if they want them to latexify nicely", but I guess Symbolics sort of erodes that freedom of choice. I would also like to mention the snake_case kwarg, which I'm not sure if it applies but it could be good to keep in mind.

@gustaphe
Copy link
Contributor

Interesting.

This one I don't understand at all, no part of that expression should run latexify?

@isaacsas
Copy link
Contributor

I think I'd prefer adding the ability to have system-based customization via a SymbolicsLatexParameters object. My thought here is that for Catalyst I like the idea of having a different font for system names, but using regular math for the final symbol after the last separator (regardless of the symbol's length). With such a customization object we could add support for that here in Symbolics and Catalyst could then use it, while it wouldn't be possible with just uniformly setting a global font everything gets wrapped in.

@hersle
Copy link
Contributor

hersle commented Oct 21, 2024

I would also be happy with that solution. Maybe a little more complicated to implement, but also more flexible. With suitable options it should be able to emulate a global wrapping font, for those that want it. I would also like the ability to distinguish system vs. variable names.

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

Successfully merging this pull request may close these issues.

4 participants