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

@register_symbolic takes "very long" to compile as the number of arguments increases #2251

Open
chris-hampel-CA opened this issue Sep 6, 2023 · 15 comments

Comments

@chris-hampel-CA
Copy link

Hello,

I have noticed a limitation of the @register_symbolic is that the compile time, when you try to register a function with typically >= 5 arguments, is "very long". It seems to increase exponentially with more arguments which can equate to 100s to 1000s of seconds of wait time when compiling.

It would be awesome if this could be improved. I have found the register capability to be extremely useful/powerful for integrating external functions that are difficult to write symbolically or cannot be written symbolically; however, I have registered functions that simply cannot be written with less arguments and the compile time is long enough that it difficult to work with.

Thanks, Chris

@chris-hampel-CA chris-hampel-CA changed the title @register_symbolic takes "very long" to compile as the number of arguments increase @register_symbolic takes "very long" to compile as the number of arguments increases Sep 6, 2023
@ChrisRackauckas
Copy link
Member

There is an exponential growth in the number of functions being registered by doing so. I'm not sure that is very easy without the user restricting dispatches.

@chris-hampel-CA
Copy link
Author

I don't fully understand what is happening under the hood, but is there a specific reason it has been configured like this? If being able to specify the type of each argument is the fix for this issue, then it is worthwhile in my opinion.

@ChrisRackauckas
Copy link
Member

You can already specify the type of each argument, which is why I suggested that. It has to be like that otherwise the general form has dispatch ambiguities.

@chris-hampel-CA
Copy link
Author

chris-hampel-CA commented Sep 12, 2023

Oh I didn't realize that was possible. Would the code look something like this example?

function test_func(a::Float64, b::Float64, c::Float64, d::Float64, e::Float64)
return a+b+c+d+e     #this will really be a non-symbolic func
end
@register_symbolic test_func(a::Float64, b::Float64, c::Float64, d::Float64, e::Float64)::Float64

@ChrisRackauckas
Copy link
Member

ChrisRackauckas commented Sep 12, 2023

You have to just not tag what would be symbolic:

@register_symbolic test_func(a, b::Float64, c::Float64, d::Float64, e::Float64)

allows a symbolic a, but not others. Then cuts the dispatches down from (5 choose 1) + (5 choose 2) + (5 choose 3) + (5 choose 4) + (5 choose 5) = 31 dispatches per symbolic form (so 3*31 = 93 IIRC) to just 3 * 1 = 3

@chris-hampel-CA
Copy link
Author

So my goal is for all the arguments to be symbolic, actually. So 5 symbolic arguments for this example. Once I surpass 5 args in my src code, the compile time skyrockets. So now I am wondering if your suggestion is applicable for bypassing this issue.

@ChrisRackauckas
Copy link
Member

The problem is that it has to do all combinations:

@register_symbolic test_func(a::Num, b::Float64, c::Float64, d::Float64, e::Float64)
@register_symbolic test_func(a::Float64, b::Num, c::Float64, d::Float64, e::Float64)
...

If you know you'll only ever do all of them symbolic, and not mixing in floating point numbers, that could possibly work.

@chris-hampel-CA
Copy link
Author

Ok I see the pattern. Yes my goal is for all the function arguments to be symbolic such that it can be used in an MTK equation. Thanks, I will try this and report back!

Perhaps there is a way to make this generic such that functions of larger argument quantities can be registered, assuming all symbolic args? I would say this is the reason I made this issue.

@ChrisRackauckas
Copy link
Member

@shashi what do you think about a macro that only registers the all symbolic version?

@shashi
Copy link
Collaborator

shashi commented Sep 13, 2023

Why not just define the method instead?

@ChrisRackauckas
Copy link
Member

I don't think we want people touching internals and building Terms?

@shashi
Copy link
Collaborator

shashi commented Sep 16, 2023

It's like quoting in Julia. It should be more encouraged. It's not great that we have a state of affairs where this becomes "touching the internals". I think the weird bit is that there are 2 kinds of Symbolic types: Num, and Symbolic{Real} which is why we need the macro in the first place.

For this case, I would want to add this:

julia> using Symbolics: has_symwrapper, wrapper_type, Symbolic, wrap, unwrap

julia> symbolic_t(T) = has_symwrapper(T) ? Union{wrapper_type(T), Symbolic{<:T}} : Symbolic{<:T}
symbolic_t (generic function with 1 method)

julia> R=symbolic_t(Real)
Union{Num, Symbolic{<:Real}}

Or just take the last line if you're not going to write this as a macro,

Then you can define:

julia> f(w::R,x::R,y::R,z::R) = wrap(term(f, unwrap.((w, x, y, z))...))

@ChrisRackauckas
Copy link
Member

Add docs on it?

@chris-hampel-CA
Copy link
Author

Sorry for the delay.

I am not versed in all the nuances here so I am trying my best to understand; I'm not sure the process is clear to me yet. This is what I am interpreting

1.You are suggesting to define the type R.
2. Use the type R to define a function that will have all symbolic inputs. (I don't understand the RHS of the function definition for f. Where do I define the operation that will return a scalar value?)
3. Then use @register_symbolic on f ?

Really, what I am interested in is this:

I want to be able to register a function with any number of arguments that returns a scalar value or vector of values. This currently works for a function with all scalar argument types up to a limit of <= 5 args without considerable compile time. How do we enable this?

Taking it one step further: I think we should even be able to register any function that might have a variety of argument types as long as the function returns a scalar value or vector of values. For example, one of the inputs is a string that is used in an if-else statement internal to the function. Another example, we register a function with methods that are dispatched based on subtype; each method returns a value but the operation is different based on the passed in type. Another case, one of the inputs is a vector of values.

I assume the all-symbolic-inputs-case might be the most common case and is less to bite off, but I have come across the more complicated case already. Curious to hear your thoughts. Thanks, Chris

@shashi
Copy link
Collaborator

shashi commented Sep 27, 2023

1.You are suggesting to define the type R.
2. Use the type R to define a function that will have all symbolic inputs. (I don't understand the RHS of the function definition for f. Where do I define the operation that will return a scalar value?)

The right hand side of that function will make a term that is a scalar

use

julia> f(w::R,x::R,y::R,z::R) = wrap(term(f, unwrap.((w, x, y, z))..., type=Real))

to make it a scalar.

  1. Then use @register_symbolic on f ?

You don't need this.

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