While the approaches in the comments (thanks a lot!) solve the issue, the question triggered me to try to "have my cake and eat it as well", i.e., enter quantities but decide globally whether they are used and what will happen if I just want magnitudes—after all using Quantity
slows down things.
A Litte Package For Conditional Quantities
The use of quantities with appropriate units should be mandatory for any mathematical model of reality, so that we may have a chance to validate equations.
While using Quantity
is the way to go, there is a price to pay with regard to performance—likely a reason that using Quantity
may be avoided and mere comments are used for units, which have the downside, that we can't let Mathematica help us.
So a nice thing to have may actually be to allow the verbose use of Quantity
so that users can enter 10 Quantity["Percent"]
instead of 0.1
(don't laugh, it happens). But at the same time, we may use ConditionalQuantity
as a wrapper to guide conversion with regard to the use in our models.
The following package thus has two main functions:
- Allow to use
On["Quantities"]
and Off["Quantities"]
to switch between UnitConvert
and QuantityMagnitude
for quanties as appropriate.
- Tell Mathematica how we would like to have our verbose units be interpreted either for
UnitConvert
or QuantityMagnitude
.
We can achieve (2) by using:
ConditionalQuantity[quantity]
to use QuantityMagnitude
as is or to keep quantity as is, i.e., no target units are provided.
ConditionalQuantity[quantity, "Canonical" ]
to try to go for the canonical unit, if it exists.
ConditionalQuantity[quantity, "Normal" ]
to get rid of all units that are compatible to "DimensionlessUnit"
(i.e., replace those units by 1
), but leave the other units unchanged.
ConditionalQuantity[quantity, unit]
to tell QuantityMagnitude
and UnitConvert
that unit
is the target unit. (Instead of unit
we also provide a reference Quantity
.)
Code
BeginPackage[ "ConditionalQuantities`" ]
Quantity::inuse = "Quantities in use."
QuantitiesOffQ::usage = "\
QuantitiesOffQ[] returns True, if messages related to Quantities have been switched off using Off[\"Quantities\"].\
The function will by default be used by ConditionalQuantity to decide whether a magnitude or a quantity is to be returned."
$numericalUnitRules = "\
$numericalUnitRules is a list of replacement rules for numerical units like Percent, BasisPoints, Thousand etc."
ConditionalQuantity::usage = "\
ConditionalQuantity[quantity] will return QuantityMagnitude[quantity] if QuantitiesOffQ[] is True and quantity, if this is not the case.\n\
ConditionalQuantity[quantity, \"Canonical\" ] will call ConditionalQuantity[quantity, unit], where unit is the canonical unit.\n\
ConditionalQuantity[quantity, \"Normal\" ] will call ConditionalQuantity[quantity, unit], where unit is QuantityUnit[quantity] where all parts\
compatible to \"DimensionlessUnit\" have been replaced by 1.\n\
ConditionalQuantity[quantity, refQuantity] will call ConditionalQuantity[quantity, QuantityUnit[refQuantity]].\n\
ConditionalQuantity[quantity, unit] will return QuantityMagnitude[quantity,unit] or UnitConversion[quantity, unit] depending on QuantitiesOffQ[].\
If units is are not compatible, unit input is disregarded and ConditionalQuantity[quantity] is called."
Begin["`Private`"]
$numericalUnitRules = Map[
Rule[ #, 1 ]&,
{
"Percent",
"BasisPoints",
"Dozen",
"Hundred",
"Thousand",
"HundredThousand",
"Million",
"Billion",
"Trillion"
}
]
$MessageGroups = Join[ $MessageGroups, { "Quantities" :> { Quantity::inuse } } ]
QuantitiesOffQ[] := HoldPattern[ Quantity::inuse ] /. Messages[ Quantity ] // Not @* FreeQ[ $Off ]
ConditionalQuantity[ q_Quantity ] := If[ QuantitiesOffQ[], QuantityMagnitude @ q, q ]
ConditionalQuantity[ q_Quantity, refq_Quantity ] := ConditionalQuantity[ q, QuantityUnit @ refq ]
ConditionalQuantity[ q_Quantity, "Canonical" ] := Enclose[
With[
{
canonicalUnit = ConfirmQuiet[
QuantityVariableCanonicalUnit @ QuantityVariable @ UnitDimensions @ q
]
}
,
ConditionalQuantity[ q, canonicalUnit ]
]
]
ConditionalQuantity[ q_Quantity, "Normal" ] := With[
{
normalUnit = QuantityUnit[ q ] /. $numericalUnitRules
}
,
If[ normalUnit === 1,
(* then *) ConditionalQuantity[ q, "DimensionlessUnit" ],
(* else *) ConditionalQuantity[ q, normalUnit ]
]
]
ConditionalQuantity[ q_Quantity, unit_ ] /; CompatibleUnitQ[ q, unit ] unit := If[ QuantitiesOffQ[],
(* then *)
QuantityMagnitude[ q, unit ],
(* else *)
UnitConvert[ q, unit ]
]
ConditionalQuantity[ q_Quantity, unit_ ] /; Not @ CompatibleUnitQ[ q, unit ] := ConditionalQuantity[q]
End[]
EndPackage[]
Examples
Assuming that we loaded the package with <<ConditionalQuantities`
or Needs
, we can now do the following:
On["Quantities"] (* Not really necessary since it is default *)
r := ConditionalQuantity[ Quantity[5., "Percent"/"Years"], "Normal" ];
t := ConditionalQuantity[ Quantity[10, "Years"] ];
initialCapital := ConditionalQuantity[ Quantity[1000, "USDollars"] ];
initialCapital Exp[r t]
(* $1648.72 *)
r
(* 0.05 per year *)
Do[ initialCapital Exp[r t], 1000] // RepeatedTiming
{0.957131, Null}
Off["Quantities"]
initialCapital Exp[ r t ]
(* 1648.72 *)
r
(* 0.05 *)
Do[ initialCapital Exp[r t], 1000] // RepeatedTiming
{0.413421, Null}
Log
andExp
do seem to have different opinions on whether "Percent" quantities should go throughNormal
or not. UntilExp
figures it out you might be better off always normalizing it yourself:r = Normal[Quantity[5, "Percent"]]/Quantity["Years"]
$\endgroup$1. + 5 Quantity["Percent"]
evaluates to a pure number,1.05
. SoExp[1. + 5 Quantity["Percent"]]
evaluates to a pure number.Log
andExp
aren't really involved with that example, since theQuantity[]
goes away. For the last example,Log[Quantity[5, "Percent"]]
also does not evaluate. I thoughtExp
andLog
took dimensionless arguments, although percent could be argued to be dimensionless. PerhapsQuantity[]
objects are treated as having dimensions, but0. + Quantity[5, "Percent"]
suggests special cases exist. $\endgroup$"Percent"
as a pure number, includingPower
. Some exceptions are the trig. functions exceptSec
andInverseCDF[NormalDistribution[], r t]
. There may be others but I stopped looking. It's odd that five of the six trig. funs. handle"Percent"
. -- As for aiding humanity: (1b) Strip"Percent"
from input and internally avoid percent; put it back in output when appropriate...(5) Report to WRI and wait for them to fix it (probably the slowest workaround, but also the best solution assuming no other). $\endgroup$Exp
orLog
a quantity with units. There may be an equivalence between a percentage and a pure number, but the moment you stateQuantity[5,"Percent"]
you are stating a unit not a number. I don't think it's reasonable to expect Mathematica to deal with the concept behind the units, or attempts to enforce normal use or physics. I think that what the OP is asking is not something that would be desirable for Mathematica to provide. $\endgroup$Quantity[ 5, "Percent" ]
is"DimensionlessUnit"
and there is a reason that it can also be represented by1
. All we should ask the WL to do is to consistently treat such quantities. Your appraoch is fair, but it is not the way WL treats all of these cases, which is clearly confusing. $\endgroup$