Update 1: Answer restructured and code modified to address OP's comments.
Update 2: Code extended to account for the cases mentioned in OP's comments (see Definition 2 in commented line in the code).
Update 3: Code extended to account for cases with powers, such as g[x^2, x^3]
.
This only addresses the first property of OP's question:
$f(a x_1, a x_2, ..., a x_n) = a^k f(x_1, x_2, ..., x_n)$
Introduction
makeHomogeneous[f, k]
defines for a symbol f
a downvalue that encodes the homogeneity of degree k
. Some particular features of the code are:
1) The homogeneity property applies for any number of arguments passed to f
.
2) The downvalue for homogeneity always fires first, even if other downvalues were defined previously.
3) The degree k
needs to be given as a symbol or a positive integer.
4) Exponents of powers must be nonnegative integers or symbols for the homogeneity property to fire.
Code
makeHomogeneous[fun_Symbol, k : (_Integer?NonNegative | _Symbol)] :=
Module[{downvalues = DownValues[fun]},
DownValues[fun] = {};
(** Definition 1 **)
fun[args : HoldPattern[Times[__]] ..] := Module[
{list = {args}, fact, x, res, temp, hold},
temp = Replace[list,
Power[a_, expn_] /; MatchQ[expn, _Integer?NonNegative | _Symbol] :>
a hold[Power[a, expn - 1]], {2}];
fact = MaximalBy[GatherBy[Join @@ List @@@ temp, Identity], Length];
If[Length[fact[[1]]] == Length[list],
fact = Times @@ fact;
x = Divide[list, fact];
res = (First@ fact)^k fun @@ x /. hold -> Identity,
res = $Failed
];
res /; res =!= $Failed
];
(** Definition 2 **)
fun[args___] := Block[{$tried = True},
Module[{list = {args}, a, temp, hold},
temp = Replace[list,
Power[x_, expn_] /; MatchQ[expn, _Integer?NonNegative | _Symbol] :>
x hold[Power[x, expn - 1]],
{1}
];
fun[Sequence @@ (a temp)] /. {a -> 1, hold -> Identity}
]
] /; ! TrueQ[$tried];
AppendTo[DownValues[fun], downvalues];
];
Usage
makeHomogeneous[g, k]
g[a x, a y]
(* a^k g[x, y] *)
g[a x, x y]
(* x^k g[a, y] *)
g[a x, a x]
(* (a x)^k g[1, 1] *)
g[a x, a y b]
(* a^k g[x, b y] *)
g[5 x, 5 x y, 5 x y z]
(* 5^k x^k g[1, y, y z] *)
(* Update 2 *)
g[x, x]
(* x^k g[1, 1] *)
g[x, x y]
(* x^k g[1, y] *)
g[x, a x, x y, x y z]
(* x^k g[1, a, y, y z] *)
(* Update 3 *)
g[x, x^2]
(* x^k g[1, x] *)
g[a x, a x^p, a x^q]
(* (a x)^k g[1, x^(-1 + p), x^(-1 + q)] *)
g[x^l y^m, x^p y^q]
(* (x y)^k g[x^(-1 + l) y^(-1 + m), x^(-1 + p) y^(-1 + q)] *)
Comments
1) The homogeneity property applies for any number of arguments passed to f
.
In case it is preferable to avoid this flexibility, the syntax of makeHomogeneous
and its code could be modified as follows:
makeHomogeneous[{fun_Symbol, nbArgs_Integer?NonNegative},
k : (_Integer?NonNegative | _Symbol)] :=
Module[{downvalues = DownValues[fun]},
DownValues[fun] = {};
With[{n = nbArgs},
fun[args : Repeated[HoldPattern[Times[__]], {n}]] := ....
];
With[{n = nbArgs},
fun[args : Repeated[_, {n}]] := ...
];
AppendTo[DownValues[fun], downvalues];
];
2) The downvalue for homogeneity always fires first, even if other downvalues were defined previously.
Here is an example:
g[x_, y_, z_, u_] := x y z u (* new downvalue after homogeneity definition above *)
g[5 x, 5 y, 5 z, 5 u]
(* 5^k u x y z *) (* homogeneity fires first *)
(* alternative would result in: 625 x y z *)
h[x_, y_] := x^6 y (* downvalue before homogeneity definition *)
h[x_, y_, z_] := x y^3 z^3 (* downvalue for three arguments *)
makeHomogeneous[h, p]
h[a x, a y]
(* a^p x^6 y *) (* homogeneity fires first *)
(* alternative would result in: a^7 x^6 y *)
h[2 x, 2 y, 2 z]
(* 2^p x y^3 z^3 *) (* homogeneity fires first *)
(* alternative would result in: 128 x y z *)
In case this behavior is not needed, the AppendTo
in the code could be changed to PrependTo
. This would modify the evaluations of h
only.
3) The degree k
needs to be given as a symbol or a positive integer.
The pattern test (_Integer?NonNegative | _Symbol)
can be removed for complete flexibility. In case constraints are needed with more working cases, one could define a function degreeQ
and test it on k
with k_?degreeQ
. Simple definitions for this symbol could be for instance:
degreeQ[_?Internal`RealValuedNumericQ] := True;
degreeQ[_Symbol] := True;
degreeQ[args: HoldPattern[Times[__]]] := And @@ degreeQ /@ List @@ args;
(* ... other definitions ... *)
degreeQ[_] := False;
4) Exponents of powers must be nonnegative integers or symbols for the homogeneity property to fire.
Here also, the pattern MatchQ[expn, _Integer?NonNegative | _Symbol]
can be removed for more flexibility, and a function be added if necessary. This modification may yield recursion errors, which can be avoided by using the Block
trick of the second definition on the first one as well.
Attributes[f]=HoldAll; f[x_*a_:1,x_*b_:1]:=x^k f[a,b]; f/:k*f[a_,b_]:=a*Derivative[1,0,0,0][f][a,b]+b*Derivative[0,1,0,0][f][a,b];
$\endgroup$D
are protected. $\endgroup$HoldAll
does the trick in order to achieve the definition of a homogeneous function i.e $f(a x, a y)=a^k f(x,y)$ butIn[1]: f[x,x]
spits out a RecursionLimit error. Also, defining the upvalue wrt f certainty produces the desired result as far as Euler's theorem is concerned butIn[2]: D[g[y b, z b], b]
still doesn't evaluate to k g[y, z] as it should. Thanks for the input! $\endgroup$