Are arbitrary expressions allowed as input arguments in Compile?
Here's what I think is happening. If we look at the CompilePrint
for both:
CompilePrint[cf2]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
Result = R1
1 R1 = MainEvaluate[ Function[{Times$796906}, a][ R0]]
2 Return
"
CompilePrint[cf1]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
R1 = 1.
Result = R1
1 Return
"
This tells us that the argument which is assume to be Real
, is just absorbed into R0
. Then we see that Times$796906
there, which comes from the Head
wrapping the argument.
We can see what happens with a different Head
:
cf3 = Compile[{Hold[{a, _Integer}, m, {b, _Integer}]}, a];
CompilePrint[cf3]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
Result = R1
1 R1 = MainEvaluate[ Function[{Hold$802124}, a][ R0]]
2 Return
"
It seems Mathematica is interpreting this construct like
Compile[{ singleArgument }, expr]
since that singleArgument
doesn't fit into the form of a "regular" variable, Compile
takes its Head
and tries to force the function that will be sent to MainEvaluate
to be side-effect free using that. This can be made clear by looking at
cf4 = Compile[{{a}}, b];
CompilePrint[cf4]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
Result = R1
1 R1 = MainEvaluate[ Function[{a}, b][ R0]]
2 Return
"
Same compiled form as for the other functions, but in this case since we just had a symbolic argument Global`a
, we've got no issues.
We get interesting behavior if we use
cf5 = Compile[{a[1]}, b];
CompilePrint[cf5]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
Result = R1
1 R1 = MainEvaluate[ Function[{a$809738}, b][ R0]]
2 Return
"
where it's clear that Compile
is doing some localization of (for some reason) just the Head
of its argument.
My usual tricks and hacks aren't having the usual effect, so I can't determine if exactly that Function
argument is just directly fed to the main loop, but I think it is. Maybe someone else can find a way around the internal implementation of Unique
or whatever they're using. Here's what I've tried for that
cf6 =
With[{m = $ModuleNumber},
With[{b = ToExpression["a$" <> ToString[m]]},
Internal`InheritedBlock[
{Unique},
Block[{$ModuleNumber = m - 1},
Unprotect[Unique];
Unique[a] := b;
Compile[
{a[1]},
b
]
]
]
]
];
CompilePrint[cf6]
"
1 argument
2 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}
R0 = A1
Result = R1
1 R1 = MainEvaluate[ Function[{a$809750}, a$809751][ R0]]
2 Return
"
Well to be honest Compile
works in very strange ways (in more then one regard). I do not think that this is intended and I have not seen it docummented or in any code I came across.
From some experiments I think the following is happening: Dropping the type specifier automatically assumes _Real
and variable "names" can be (as OP discovered) rather exotic. The probable reason for this rather loose behavior is that arguments inside the CompileFunction
are refereed to as A1
to AN
(for a CompileFunction
with N
). The argument names specified by the user are not used in the final function. If one drops the outer curly bracket comma separated expression get treated as real scalar arguments. So
Needs["CompiledFunctionTools`"];
Compile[{Sin[x^2]},(Sin[x^2])^2];
%
%//CompilePrint
results in
with Compile[{{x, _Real}}, (x)^2];
as an equivalent conventional input form.
Somewhat scary in this context is this Compile[{x, _Real}, x + _Real];
which is equivalent to Compile[{{x, _Real},{y,_Real}}, x + y];
. I do not know how robust this is and on first glance it seems rather useless to input functions/arguments this way but one advantage I see is the possibility to use strings, sup-/superscripted values and more for argument names which allows for names which are normally impossible in Mathematica. E.g.:
Compile[{{"A_1", _Real}, {"A_2", _Real}}, ("A_1")^2 + "A_2"];
Compile[{{Subscript[A, 1], _Real}, {Subscript[A,2], _Real}}, (Subscript[A, 1])^2 + Subscript[A, 2]]
work as one would expect. This might be a use case for this curious find.
I would call the whole scenario a "feature" in the sense that this seems to be rather robust behavior related to the input parser of compile. That being said there is no guarantee that the current behavior will persist across different versions of the software.
I always use CompilePrint
to check the outputted CompiledFunction
for obvious errors, unevaluated expressions (e.g. If[2==2,...]
) and especially MainEvaluate[...]
since in my experience (using compiled functions inside NDSolve) just one MainEvaluate[...]
completely eliminates any performance benefit.