Ooof, I need some metaprogramming help… There is the macro @parameters x = 0.5
. The macro, in a simplified sense, does the following: makes a named parameter with name :x
and default value 0.5
and returns the container as a custom type Num(:x, 0.5)
and assigns this to the variable x
. I want to make a new macro @named_parameters vars...
to be called like e.g., @named_parameters x y
which would apply the @paramaters
macro above to two existing variables x, y
with real values. It would make two Num(:x, x)
and Num(:y, y)
and assign that to x, y
, using @parameters
.
Here is what I have, but it is not correct:
using ModelingToolkit: @parameters
macro named_parameters(vars...)
expr = Expr(:macrocall, Symbol("@parameters"))
for v in vars
push!(expr.args, :($(QuoteNode(v)) = $(esc(v))))
end
return expr
end
A = 0.5
B = 0.5
@named_parameters A B # should make the parameters
what would be the “correct way” to do this, if any?
For reference @parameters
comes from ModelingToolkit.jl and can be called with multiple parameters as
@parameters begin
A = 0.3
B = 0.2
end
which is what inspired the for
loop in my macro version.
x = :A
y = :B
@parameters $x $y
and use loops, etc. The interpolation works into it, so you don’t need to metaprogram around it.
That’s not what I want. I want
A = 0.5 # notice its a real number
B = 0.5
@parameters A B # makes parameter A with default value 0.5
in my code A, B
are the keyword arguments to functions that provide the default values to parameters that participate into equations. So making them symbols would require lots of boilerplate code. That’s exactly why I want this macro, so that the existing expressions in my code like
x ~ A*y - B
automatically become “parameterized” as A, B
are no longer Reals but Nums
with default values.
You’ll get better advice if you include the result of @macroexpand
on what you have now.
I suggest grabbing the code for @parameters
and modifying it to do what you want. It’s legal to use a macro to build up another macro, but it can produce all sorts of strange edge cases which are tricky to reason about.
It sounds like what you want is a small modification of @parameters
, the easy way to achieve this is to make small modifications to @parameters
.
Right, yes. Well, the macroexpand of the macro I want to “re-create” is:
@macroexpand @parameters begin
a = 0.2
b = 0.2
end
quote
a = (ModelingToolkit.toparam)((Symbolics.wrap)((SymbolicUtils.setmetadata)((Symbolics.setdefaultval)((Sym){Real}(:a), 0.2), Symbolics.VariableSource, (:parameters, :a))))
b = (ModelingToolkit.toparam)((Symbolics.wrap)((SymbolicUtils.setmetadata)((Symbolics.setdefaultval)((Sym){Real}(:b), 0.2), Symbolics.VariableSource, (:parameters, :b))))
[a, b]
end
Inspired by this I thought I would make a loop that pushes expressions and then “quotes” them, like so
macro named_parameters(vars...)
return quote
out = []
for v in vars
res = (ModelingToolkit.toparam)((Symbolics.wrap)((Symbolics.SymbolicUtils.setmetadata)((Symbolics.setdefaultval)((Sym){Real}($(QuoteNode(v))), $(esc(v))), Symbolics.VariableSource, (:parameters, $(QuoteNode(v))))))
push!(out, res)
end
$(out...)
end
end
but alas this doesn’t work either irrespecitvely of if I put the loop inside the quote
or not. When inside the loop it says “v
” not defined when I run the macro…
Is this approximately what you were looking for?
macro named_params(vars...)
expr = Expr(:block)
for var in vars
binding = esc(var)
varname = QuoteNode(var)
push!(expr.args,
:($binding = (; name=$varname, val=$binding) )
)
end
push!(expr.args, Expr(:vect, esc.(vars)...))
return expr
end
@named_params (macro with 1 method)
julia> @macroexpand @named_params A B
quote
A = (; name = :A, val = A)
B = (; name = :B, val = B)
[A, B]
end
julia> function testfun(; a=1, b=2, c=3)
@named_params a b c
end
testfun (generic function with 1 method)
julia> testfun()
3-element Vector{@NamedTuple{name::Symbol, val::Int64}}:
(name = :a, val = 1)
(name = :b, val = 2)
(name = :c, val = 3)
This is obviously just a demo for using both the name and previously assigned value without actually using the MTK Num
.
2 Likes
macrocall takes a hidden argument, so you are better off generating the initial expression with the parser instead of manually (the splatted initial args list is optional):
expr = :(@parameters $(args…))
omg yes hell yeah thank you so much!!! That is exactly what I Was looking for!!! For reference, the final solution is:
macro named_params(vars...)
expr = Expr(:block)
for var in vars
binding = esc(var)
varname = QuoteNode(var)
push!(expr.args,
:($binding = (ModelingToolkit.toparam)((Symbolics.wrap)((SymbolicUtils.setmetadata)((Symbolics.setdefaultval)((Sym){Real}($varname), $binding), Symbolics.VariableSource, (:parameters, $varname)))))
)
end
push!(expr.args, Expr(:vect, esc.(vars)...))
return expr
end