-
Notifications
You must be signed in to change notification settings - Fork 114
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
Added naming based on input types #660
base: master
Are you sure you want to change the base?
Conversation
Needs docs and tests |
I guess a good test would be to define a function that we want to have a different name based on Real vs Complex argument. Like sqrt_R+ vs sqrt_C Is the same function object, but we want to have different printing to make it easier to understand how the types are flowing through the formulas. |
Still need tests for decorated case.
Should this instead be related to Julia's compact printing modes? I still don't understand what you mean by "based on the input types" here: what are the types and how are they involved? |
Compact printing modes is a new feature to me. What are you thinking? In the exterior calculus context, the operations are graded by dimension, so for every operation, there is a subscript that corresponds to that dimension. We have implemented a bunch of functions and types to represent the types and operations of the exterior calculus that have the dimension as part of the variable type. Our variables have types like If we define a fallback, where you ignore the arguments, then the existing behavior is the same for everyone except for those that opt in to names that depend on argument types. |
I've just added a test that might explain more what I mean by the "naming based on input types ". In this case, I've defined a Julia function that acts like sq(x) = return SymbolicUtils.Term{Number}(sq, [x])
function Base.nameof(::typeof(sq), arg)
if arg <: Real
return :sqrt_R
elseif arg <: Complex
return :sqrt_C
else
return :sqrt
end
end
x,y,z = @syms x::Real y::Complex z
@test get_print(sq(x)) == "sqrt_R(x)"
@test get_print(sq(y)) == "sqrt_C(y)"
@test get_print(sq(z)) == "sqrt(z)"
|
I still don't understand. Not trying to be adversarial here, but this is really confusing to me.
Isn't that just multiple dispatch? If someone has a function that acts differently dependent on the type, you'd just make different dispatches and that would happen automatically. The
Julia's Base printing has a compact mode, which is how arrays do that |
I guess that means there are two features here
We definitely need (1). We can put (2) in our own codebase for a different purpose since it isn't critical if we have (1) How do you add a check for compact mode? And are the repl and jupyter notebook cells in compact mode? |
I always forget. https://discourse.julialang.org/t/show-and-showcompact-on-custom-types/8493 seems like it has it.
I guess what I don't understand is if this is just a display thing? A symbolic is defined by matching not just its name but also its type and metadata, so they can have the same name but still be different in a sense. This is already checked in equality matching. So is this just about allowing a system to allow for uniqueness in display? Is it odd if the displayed name does not match the given name of the symbolic? Pinging @bowenszhu @0x0f0f0f for some discussion. |
It would be helpful if users could opt in to having the printing of the symbolic term also depend on the type and metadata. My understanding of SymbolicUtils types is that the type parameter of a symbolic term is the type that the term will evaluate to. I agree that if the printed name doesn't match a constructor you can use to construct it, people will get confused. We are defining aliases so that |
Maybe related is JuliaSymbolics/Symbolics.jl#1305 with customization options, @hersle |
I also think being able to hook into the printing/latexifying of expressions sounds like a useful feature.
This sounds somewhat "dishonest" to me, since it really changes the name of the printed variable. For consistency, I would prefer scoping variables instead (e.g. But this application is far away from my domain, so I am sure there are good reasons for why you want to do this. Ideally, I think the customization of expression printing/latexifying should be done through a function in which the user can freely process the normal/Latex string, instead of only choosing between a set of predetermined "modes". Then this function could be used to e.g. suppress module names, customize the font style of module/variable names, turn "a_1" into "a₁", and anything else one can think of. |
@AayushSabharwal thoughts? |
EDIT at the bottom 😅 There's definitely a case to be made either way. But we already lie to the user when printing time-dependent variables (you can't actually type
The "name" of the operation defines what it does, not how it does it. The name of the function is effectively julia-compatible shorthand for the name of the operation. If "exterior derivative" isn't enough to describe what is happening, are they not either different operations, or operations which accept an additional argument If you're going to these lengths to make sure that A) a user can type I'm in support of better hooks into our display API, but this implementation feels like a quick and dirty approach. It would be good to list the sort of hooks we want and have at least a quick discussion around how to expose them, otherwise we might have to rework this functionality later to accommodate other requests and be stuck having to add edge cases to handle old infrastructure. Two things that occur to me right off the bat:
EDIT: I just realized this is SymbolicUtils not Symbolics. In this case, up until now I believe our printed expressions do directly reflect runnable code. The above comment is then hinged upon whether we decide this should continue to be the case or not and where we draw the line. Symbolics expressions are not necessarily runnable as they are printed, because it adds |
I'm leaving that comment up despite writing it under a bit of a misconception 😅. Realizing that this is SymbolicUtils I'll give this some more thought before writing much more, but: The motive isn't fully clear here. Are the subscripts and change in printing for user clarity or because they are semantically different operations? Why is the solution here to also define functions with subscripts, since manually defining all of this only goes so far and the user can always have a big enough |
@jpfairbanks if you have subscript functions defined, why not create the symbolic variable with the subscript functions instead of with |
We do that too. They convert to the unsubscripted versions as part of our implementation of dispatch. We have a layer of defining these operators that supports dispatching on dimension so that you can use the generic versions without subscripts or the specific versions with subscripts. You get the same terms either way. The subscripts are there just for readability in our implementation |
I'm not sure what you mean by your version of dispatch? Regardless, though, I think it's worth exposing some well-defined hooks into our printing. The major win I see here is better readability when the function in the A few notes on how we can go about this:
|
The first attempt to fix the printing issue on our side was to just overload the |
whoa whoa whoa, we didn't even think to try and make the head of a BasicSymbolic a callable struct. Are there examples of that in the wild somewhere that we can learn from? We could put our grade and space information into the fields of a callable struct and do X=Space(:X, 2)
p=Form(X,0)
u = Form(X, 1)
d(0)(p) + Δ(1)(u) Then we could use
This is exactly what we are doing. We are working with forms on a de Rahm complex and are trying to work with the grade of the forms, which corresponds to the grade of a blade. |
It's possible using Symbolics.jl to do something like struct Foo
# ...
end
(foo::Foo)(arg1, arg2) = (...)
@register_symbolic (foo::Foo)(arg1, arg2) Now if you do @variables x y
foo = Foo(...)
foo(x, y) you get a symbolic variable whose head is the callable struct.
I thought the
Oh, neat. I have some experience with GA and trying to use it for physical simulations, but never used exterior calculus. |
This closes #659.