3
$\begingroup$

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.

$\endgroup$
2
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ 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? $\endgroup$ – F. Aitken Feb 3 '17 at 16:43
  • $\begingroup$ Does with the blockset function, ptT can be any kind of function? $\endgroup$ – F. Aitken Feb 3 '17 at 16:56
  • $\begingroup$ @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 ρ. $\endgroup$ – Mr.Wizard Feb 3 '17 at 17:43
1
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – Mr.Wizard Feb 7 '17 at 18:31
  • $\begingroup$ @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? $\endgroup$ – Edmund Feb 7 '17 at 20:39
  • $\begingroup$ 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. $\endgroup$ – Mr.Wizard Feb 7 '17 at 22:03
  • $\begingroup$ @Mr.Wizard So true. Trace reveals all. That would have been one nasty bug to locate. Thanks. $\endgroup$ – Edmund Feb 7 '17 at 23:59
1
$\begingroup$

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} *)
$\endgroup$
0
$\begingroup$

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?

$\endgroup$
  • $\begingroup$ It would be better, if you combined this with your original question. $\endgroup$ – bbgodfrey Feb 11 '17 at 3:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.