3

I define a function with two variables and an argumment which is a function of this two first variables. For example the function Ttrho below (the two variables are T and Rho and pTt is a function of T or Rho or both):

getAllVariables[expr_] := 
Union@Cases[expr, 
Except[__Symbol?(Context@# === 
     "System`" &), _Symbol], {1, ∞}, Heads -> True];

Ttrho[T_, ρ_, pTt_] := 
With[{var = getAllVariables[pTt]}, Print[var]; 
If[Length@var > 0, 
Apply[pTt, var /. Thread[var -> {T, ρ}]] 5 0.069/ρ, 
pTt*5 0.069/ρ]]

I would like that the three results below must have the same value:

Ttrho[1, 0.1, Function[{T, ρ}, T]]
Ttrho[1, 0.1, Function[{ρ, T}, T]]
Ttrho[1, 0.1, Function[T, T]]

But I obtain actually the following results:

During evaluation of In[1]:= {T,ρ}

Out[1]= 3.45

During evaluation of In[2]:= {T,ρ}

Out[2]= 0.345

During evaluation of In[3]:= {T}

During evaluation of In[3]:= Thread::tdlen: Objects of unequal length in {T}->{1,0.1} cannot be combined.

Out[3]= 3.45

Thank you in advance.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
F. Aitken
  • 111
  • 5

4 Answers4

2

I am struggling to understand the motivation behind this question and therefore the actual behavior that you are seeking. However it seems to me that perhaps you want something like this.

ClearAll[Ttrho]

Ttrho[xT_, xρ_, HoldPattern@Function[_, body_]] := 
  Block[{T = xT, ρ = xρ}, body]

Ttrho[1, 0.1, Function[{T, ρ}, 3 T]]
Ttrho[1, 0.1, Function[{ρ, T}, 3 T]]
Ttrho[1, 0.1, Function[T, 3 T]]
3

3

3

This will only work with actual Function expressions as shown, not generic functions of any kind, but since you appear to want commonality of parameter names I think this is implied.

Application

To make this "elegant" please load my blockSet function from (69595) then:

ClearAll[Ttrho]

blockSet[
  Ttrho[T_, ρ_, pTt_] := pTt[[2]]*5 0.069/ρ
]

Ttrho[1, 0.1, Function[{T, ρ}, T]]
Ttrho[1, 0.1, Function[{ρ, T}, T]]
Ttrho[1, 0.1, Function[T, T]]
3.45

3.45

3.45

This also relies on the third argument of Ttrho being an explicit Function as pTt[[2]] is used to extract its body.

Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
  • The motivation is that Ttrho is a function which represents a property of fluids. This property has a general shape for all fluids but some coefficients like pTt are different functions (it can also be a constant) for different fluids. These functions (or constants) are given in a dataset. So T and rho represents always the same physical quantity. So when the parameter ptT is simply a constant (rather than a function) it is simpler for the user to put the value instead of writing a function? – F. Aitken Feb 03 '17 at 16:43
  • Does with the blockset function, ptT can be any kind of function? – F. Aitken Feb 03 '17 at 16:56
  • @F.Aitken I just read your comments, and pardon me, but I'm not sure I understand this any better. The issue I see is that you have a function Function[{T, ρ}, T] and a function Function[{ρ, T}, T] which have the parameters in different order; the only way to tell which parameter is T and which is ρ is to "look" at those Symbols themselves. If we had another function Function[{x, y}, 2 x + y] what should we (and our Ttrho) make of it? Likewise 2 # + #2 & or any other generic function that does not have explicit parameters T and/or ρ. – Mr.Wizard Feb 03 '17 at 17:43
1

You may use Symbol and the Parts of Expressions tutorial.

With

tRho[t_, rho_, f_Function] :=
 (f[[2]] /. {Symbol["t"] -> t, Symbol["rho"] -> rho}) 5 0.069/rho

Then

tRho[1, 0.1, Function[{t, rho}, t]]
tRho[1, 0.1, Function[{rho, t}, t]]
tRho[1, 0.1, Function[t, t]]
3.45
3.45
3.45

Hope this helps.

Edmund
  • 42,267
  • 3
  • 51
  • 143
  • To complete this you still need to localize t and rho. Presently these symbols evaluate as a result of both f[[2]] and Symbol["t"] etc. – Mr.Wizard Feb 07 '17 at 18:31
  • @Mr.Wizard I'm not certain I am following what you are saying. If I evaluate t = 10*10^9 and then evaluate tRho[1, 0.1, Function[t, t]] then I still get 3.45. Why do I need to localise with Module or Block? – Edmund Feb 07 '17 at 20:39
  • Your example works but for the wrong reason. :^) You end up with something like 10000000000 /. 10000000000 -> new but one should never let it get to that point. Consider t := RandomReal[] instead; now the function is broken! Our code should be robust against such things. – Mr.Wizard Feb 07 '17 at 22:03
  • @Mr.Wizard So true. Trace reveals all. That would have been one nasty bug to locate. Thanks. – Edmund Feb 07 '17 at 23:59
1

In Function[{T, ρ}, body] expression T and ρ are just "dummy variables", their names are not really meaningful, only meaningful thing is relation between T and ρ symbols, used inside function body, to positions of those symbols in arguments list. Mathematica can rename function variables, to avoid conflicts in nested scopes. So even if T or ρ was typed inside Function, resulting expression can contain different symbols. That's why extraction of function body and identification of variables, that you asked for in question, can be tricky.

I would take advantage of Mathematica symbolic capabilities and, instead of Functions, I'd use expressions containing globally meaningful symbols. So I'd put T and ρ in some context, that I'd keep on $ContextPath, and, instead of Function[{T, ρ}, T], just pass T to your Ttrho function.

I would also separate concerns of calculating Ttrho expression and of inserting numerical values for T and ρ.

Putting it together I'd use something like this:

BeginPackage@"myThermodynamics`"; Unprotect@"`*"; ClearAll@"`*"

T::usage = "T is something";
ρ::usage = "ρ is something else";
Ttrho::usage = "Ttrho[pTt] returns something";

Begin@"`Private`"; ClearAll@"`*"

someConstant = 5 0.069;

Ttrho@pTt_ := someConstant pTt/ρ

End[]; Protect@"`*"; EndPackage[];

So now you can calculate symbolic expression for Ttrho and insert numeric values like this:

Ttrho[T]
% /. {T -> 1, ρ -> 0.1}
(* (0.345 T)/ρ *)
(* 3.45 *)

Ttrho[ρ^3/T^2]
% /. {T -> 1, ρ -> 0.1}
(* (0.345 ρ^2)/T^2*)
(* 0.0345 *)

If you really need to extract body from already existing Function expression you could use something like following:

funcToExpr // ClearAll
funcToExpr@func:HoldPattern@Function[
    Except[Null, var_Symbol] | {vars___Symbol},
    Repeated[_, 2]
] :=
    func @@ Flatten@Replace[HoldComplete[vars, var],
        s_ :> With[{name = SymbolName@Unevaluated@s},
            RuleCondition[
                ToExpression[Context@Unevaluated@s <> StringDrop[name, -1],
                    InputForm, HoldComplete
                ],
                StringEndsQ[name, "$"]
            ]
        ],
        {1}
    ]

Which should work also for Functions with renamed variables:

Function[T, T]
% // funcToExpr
(* Function[T, T] *)
(* T *)

With[{x = 2}, Function[T, x T]]
% // funcToExpr
(* Function[T$, 2 T$] *)
(* 2 T *)

Function[{T, ρ}, T ρ]
% // funcToExpr
(* Function[{T, ρ}, T ρ] *)
(* T ρ *)

With[{x = 2}, Function[{T, ρ}, x T ρ]]
% // funcToExpr
(* Function[{T$, ρ$}, 2 T$ ρ$] *)
(* 2 T ρ *)

Putting all things together:

Ttrho@funcToExpr@#3 /. {T -> #1, ρ -> #2} & @@@ {
  {1, 0.1, Function[{T, ρ}, T]},
  {1, 0.1, Function[{ρ, T}, T]},
  {1, 0.1, Function[T, T]}
}
(* {3.45, 3.45, 3.45} *)
jkuczm
  • 15,078
  • 2
  • 53
  • 84
0

Thanck you very much for the different propositions. By using the blockSet function of Mr Wizard with the two additional conditions:

ClearAll[Ttrho]

ConstanteQ[f_] := 
NumberQ[f] == 
True || (QuantityQ[f] == True && (Head@f[[1]] === Function) == 
  False);
QuantityFunctionQ[f_] := 
 QuantityQ[f] == True && Head@f[[1]] === Function;

blockSet[
Ttrho[T_, \[Rho]_, pTt_: 1] := 
With[{fpTt = 
 If[ConstanteQ[pTt], pTt, 
  If[QuantityFunctionQ[pTt], Quantity[Last@pTt[[1]], pTt[[2]]], 
   pTt[[2]]]]},
fpTt*5 0.069/\[Rho]]
]

I obtained a great flexibility on the way in which the user can enter the function (or the constant) ptT. For the different examples below:

Ttrho[Quantity[2, "Kelvins"], 0.1, Function[{T, \[Rho]}, 2 T]]
Ttrho[Quantity[2, "Kelvins"], 0.1, Function[{\[Rho], T}, 2 T]]
Ttrho[Quantity[2, "Kelvins"], 0.1, Function[T, 2 T]]

Ttrho[2, 0.1, Quantity[Function[T, 2 T], "Kelvins"]]
Ttrho[2, 0.1, Quantity[Function[{T, \[Rho]}, 2 T], "Kelvins"]]
Ttrho[2, 0.1, Quantity[Function[{\[Rho], T}, 2 T], "Kelvins"]]
Ttrho[2, 0.1, Function[T, Quantity[2 T, "Kelvins"]]]
Ttrho[2, 0.1, Function[{T, \[Rho]}, 2 T]]

Ttrho[Quantity[2, "Kelvins"], 0.1, Function[T, 2]]
Ttrho[Quantity[2, "Kelvins"], 0.1, 2]
Ttrho[Quantity[2, "Kelvins"], 0.1, Function[T, Quantity[2, "Kelvins"]]]
Ttrho[Quantity[2, "Kelvins"], 0.1, Quantity[2, "Kelvins"]]

Ttrho[2, 0.1, Quantity[Function[\[Rho], 2 \[Rho]^2], "g/cm^3"]]
Ttrho[2, 0.1, Quantity[Function[{\[Rho]}, 2 \[Rho]], "g/cm^3"]]

You get the following results:

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

Quantity[13.8, "Kelvins"]

13.8

6.9

6.9

Quantity[6.9, "Kelvins"]

Quantity[6.9, "Kelvins"]

Quantity[0.069, ("Grams")/("Centimeters")^3]

Quantity[0.69, ("Grams")/("Centimeters")^3]

Sorry I have not yet looked at the solution proposed by jkuczm and perhaps it has a greater flexibility than above?

F. Aitken
  • 111
  • 5