I may have a solution but it is not slick! See below
Doing numerical calculations with functions that use units can be very slow. I would like to have a function or operator that can turn my functions into unitless functions. I have a partial solution but it doesn't work in all situations.
The blackbody radiation equation:
Clear[BB];
BB[l_Quantity, T_Quantity] := (
2 Quantity["PlanckConstant"] Quantity[
"SpeedOfLight"]^2)/(l^5 Quantity[1, "Steradians"]) 1/(
Exp[Quantity[
"PlanckConstant"] Quantity[
"SpeedOfLight"]/(l Quantity["BoltzmannConstant"] T)] -
1)
Now if I want to do many calculations with this equation it is very slow as can be seen from just making a plot:
AbsoluteTiming[
Plot[QuantityMagnitude[
UnitConvert[
BB[Quantity[l, "Micrometers"], Quantity[333, "Kelvins"]],
"Microflicks"]], {l, .1, 15}
, Frame -> True
, FrameLabel -> {"\[Lambda] (\[Mu]m)",
"\[ScriptCapitalL] (\[Mu]flicks)"}
, ImageSize -> Medium
]
]
Please keep in mind that I want to do much more than just make plots quickly. I need to do Monte Carlo simulations where I will make compiled versions of my equations. Also in my real work, the equations are much more complicated and automatically generated.
The function that I have come up with that works some of the time is the following:
Clear[NoUnits]
NoUnits[fun_,
outunits_] := ((UnitConvert[#, outunits] & /@
fun) /. {UnitConvert[a_, outunits] :> a}) /. {Quantity[q_,
u_] :> q};
I can apply this function to BB[l,T]
to get:
Clear[BBnu];
BBnu[l_, T_] :=
Evaluate[NoUnits[
BB[\[Lambda],
T] /. {l -> Quantity[l, "Micrometers"],
T -> Quantity[T, "Kelvins"]}, "Microflicks"]]
Where I have to tell my NoUnits[]
function what units to use for each variable and the units I want for the final output. The new function BBnu[l,T]
is just a function of numbers and gives a numerical output. Now the calculations go much faster!
AbsoluteTiming[Plot[BBnu[l, 333], {l, .1, 15}
, Frame -> True
, FrameLabel -> {"\[Lambda] (\[Mu]m)",
"\[ScriptCapitalL] (\[Mu]flicks)"}
, ImageSize -> Medium
]
]
But now if I try my function NoUnits[]
on something more complex like a linear combination of blackbodies it fails:
NoUnits[(a BB[l, T1] + b BB[l, T2]) /. {l ->
Quantity[l, "Micrometers"], T1 -> Quantity[T1, "Kelvins"],
T2 -> Quantity[T2, "Kelvins"]}, "Microflicks"]
I have tried what I have learned from: How to create fast functions based on units? Converting the units of an equation or expression (how to become 3 times heavier…) Yet these do not solve my full problem.
Edit The following are some stressing testing functions:
The blackbody equation again B[l,T]
:
BB[l_Quantity, T_Quantity] := (
2 Quantity["PlanckConstant"] Quantity["SpeedOfLight"]^2)/(
l^5 Quantity[1, "Steradians"]) 1/(
Exp[Quantity[
"PlanckConstant"] Quantity[
"SpeedOfLight"]/(l Quantity["BoltzmannConstant"] T)] - 1);
A made up trasnsmsion equation trans[l]
:
trans[l_Quantity] :=
1/(1 + Exp[-
Quantity[1, ("Micrometers")^-1] (l -
Quantity[4, "Micrometers"])]) 1/(
1 + Exp[Quantity[3, (
"Micrometers")^-1] (l - Quantity[10, "Micrometers"])]);
A madeup source equation that includes an interpolation function with compound units Ladd[l,T]
:
With[{
fun = Interpolation[{{4/300, 0}, {5/300,
1 10^4}, {6/300, .5 10^6}, {8/300, 8 10^6}, {10/300,
4 10^6}, {12/300, 2 10^6}, {14/300, 1 10^6}, {15/300, 0}},
InterpolationOrder -> 3]
},
Ladd[l_Quantity, T_Quantity] := Piecewise[{
{Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
l/T <= Quantity[4, "Micrometers"]/Quantity[300, "Kelvins"]}
, {Quantity[
fun[QuantityMagnitude[
UnitConvert[l, "Micrometers"]/UnitConvert[T, "Kelvins"]]],
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
Quantity[4, "Micrometers"]/Quantity[300, "Kelvins"] < l/T <=
Quantity[15, "Micrometers"]/Quantity[300, "Kelvins"]}
, {Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
l/T > Quantity[15, "Micrometers"]/Quantity[300, "Kelvins"]}
}, Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1]]
];
Another made up function: LaddTwo[l]
With[{
fun = Interpolation[{{4, 0}, {5, 1 10^4}, {6, .5 10^6}, {8,
8 10^6}, {10, 4 10^6}, {12, 2 10^6}, {14, 1 10^6}, {15, 0}},
InterpolationOrder -> 3]
},
LaddTwo[l_Quantity] := Piecewise[{
{Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
l <= Quantity[4, "Micrometers"]}
, {Quantity[
fun[QuantityMagnitude[UnitConvert[l, "Micrometers"]]],
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
Quantity[4, "Micrometers"] < l <= Quantity[15, "Micrometers"]}
, {Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1],
l > Quantity[15, "Micrometers"]}
}, Quantity[0,
"Kilograms" ("Meters")^-1 ("Seconds")^-3 ("Steradians")^-1]]
];
and finally, a full system equation that brings it all together Lfull[l,T]
:
Lfull[l_Quantity, T_Quantity, a_, b_] :=
a trans[l] BB[l, T] + b trans[l] Ladd[l, T]+ a b LaddTwo[l];
I'm not trying to make this overly completed but this is the smallest full-featured example.
The goal would be to get this "units to unitless" operator to work on the Lfull[l,T,a,b]
function.
My poor solution I'm still very interested in a better solution! I can think of some cases where this may fail. Update: It has not failed yet but I still do not like the implementation. I have updated my function complete with some documentation:
Clear[NoUnits];
NoUnits::usage =
"NoUnits[fun, rp, outunits] Will take a function fun that is \
defined with units and convert it into just a numerical function. Of \
course the user is now responsible for providing the numbers in the \
correct \"Units\". rp is a list of variables and the corresponding \
units the user wants to use for the resulting function. The units on
the output is the outunits.
Options:
\"MaxPower\" What maximum power to use when looking for match \
units. Default is 3. If you have something like l^5/a^2 in your \
equation you may wat to use 5 for your maximum power.
Example:
fun[v_Quantity,m_Quantity,c_]:= c m \!\(\*SuperscriptBox[\(v\), \(2\
\)]\);
Kinetic energy equation were we have to put in c=0.5;
Test it for some inputs:
e=fun[Quantity[2,\"Meters\"/\"Seconds\"],Quantity[12,\"Kilograms\"],\
.5];
UnitConvert[e,\"Ergs\"]
\!\(\*TemplateBox[{\"2.4`*^8\",\"\\\"ergs\\\"\",\"ergs\",\"\\\"Ergs\
\\\"\"},\n\"Quantity\"]\)
Lets say we want just a numbers equation where we input mph, pounds \
and want Ergs out Then do:
eq=NoUnits[fun[v,m,c],{{v,\"Miles\"/\"Hours\"},{m,\"Pounds\"}},\"\
Ergs\"]
\!\(\*FractionBox[\(88523495162333\\\ c\\\ m\\\ \
\*SuperscriptBox[\(v\), \(2\)]\), \(97656250\)]\)
Test it:
eq/.{c\[Rule].5,m\[Rule] 10, v\[Rule] 55}
1.3710518930742134`*^10
Use the original equation:
e=fun[Quantity[55,\"Miles\"/\"Hours\"],Quantity[10,\"Pounds\"],.5];
UnitConvert[e,\"Ergs\"]
\!\(\*TemplateBox[{\"1.3710518930742136`*^10\",\"\\\"ergs\\\"\",\"\
ergs\",\"\\\"Ergs\\\"\"},\n\"Quantity\"]\)";
Options[NoUnits] = {
"MaxPower" -> Automatic
};
NoUnits[fun_, rp_List, outunits_, opts : OptionsPattern[NoUnits]] :=
Module[{eq, list, u, pow, unit, a, b, mp},
mp = OptionValue["MaxPower"];
If[Not[NumericQ[mp] && IntegerQ[mp]], mp = 3];
If[mp <= 0, mp = 3];
eq = fun /.
Evaluate[Rule[#[[1]], Quantity[#[[1]], #[[2]]]] & /@ rp];
eq = eq /. {Quantity[a_, b_] :>
UnitConvert[Quantity[a, b]]} /. {Quantity[a_,
"PureUnities"] :> a, Quantity[a_, "DimensionlessUnit"] :> a};
eq = eq /. {Quantity[a_, b_] /; CompatibleUnitQ[b, outunits] :>
QuantityMagnitude[UnitConvert[Quantity[a, b], outunits]]};
list =
DeleteDuplicates[Cases[eq, Quantity[a_, b_] :> b, \[Infinity]]];
u = rp[[All, 2]];
pow = Tuples[Range[-mp, mp], Length[u]];
pow = SortBy[pow, Total[Abs[#]] &];
pow = {u, #}\[Transpose] & /@ pow;
Do[
unit =
Select[Times[
Sequence @@ #] & /@ (pow /. {{a_String, b_} :>
Power[a, b]}), CompatibleUnitQ[#, ele] &, 1][[1]];
eq = eq /. {Quantity[a_, b_] /; CompatibleUnitQ[b, unit] :>
QuantityMagnitude[UnitConvert[Quantity[a, b], unit]]};
, {ele, list}];
Return[eq]
];
Here is an example of the usage: