Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I've been playing with the new unit support in Mathematica 9. It seems very useful, but the syntax is very verbose. Instead of typing:

UnitConvert[Quantity[1, "Meters"/"Seconds"^2]*Quantity[1, "Minutes"]^2, "Kilometers"]

I would much rather type and read something like:

UnitConvert[1 m/s^2*(1 min)^2, km]

My first idea was to simply define variables for the units I'm going to use:

m = Quantity["Meters"];
km = Quantity["Kilometers"];
s = Quantity["Seconds"];
min = Quantity["Minutes"];

but unfortunately, this doesn't really work: The term 0 [any unit] is always simplified to 0, and following computations won't work because the units don't match. So for example UnitConvert[1 m/s^2*(1 min)^2, km] works fine, but UnitConvert[1 m/s^2*(0 min)^2, km] doesn't work, because the first argument to UnitConvert evaluates to 0

Are there other ways to achieve this? For example,

  • is it possible to prevent the simplification 0 * 1 Meters -> 0
  • is it possible to adjust generalized input so that entering "5 s" would evaluate to Quantity[5, "Seconds"], (like entering $d_x y$ evaluates to Dt[y,x] or n! evaluates to Factorial[n])

Of course, I've tried the Ctrl= input form first. It's a great way to learn new syntax by example, but I don't think it's practical for day-to-day use, for a number of reasons:

  • I can't use notebook expressions in the freeform input. For example:

    enter image description here

  • I can't use 2D input, so I can't even type e.g. $\partial _t$ or $\int _a^b$ within a freeform expression

  • Which means that for a longer expression I might have to enter several freeform-input in a single line, which doesn't make it more readable.
  • If I do use 2D input like $\int _a^b$, I can't enter freeform-input for a and b (EDIT: Turns out I can. I just have to enter a space before Ctrl=. Thanks @Itai Seggev)
  • I've been playing with it for an hour. It hung several times and crashed once (Not reproducible) and I had to restart it once.
  • This may be a bit philosophical: I'm using a programming language because I want to express an idea unambiguously. I don't want it to guess whether the symbol t means a variable for time or metric tons.
  • The freeform-boxes look weird in a presentation or publication. Of course, I can convert them to input or display form easily, but (in the right context) an expression like 1920*1080 Bytes*24/s might mean something to the reader, but 2.0739999999999998*^6B*(24/1s) doesn't, even if it's the same value.

UPDATE: Based on @Leonid's code, this is the best solution I've come up with so far:

ClearAll[withUnits];
SetAttributes[withUnits, HoldAll];
withUnits[code_] := ReleaseHold[(Hold[code] /.
      {
       m -> Quantity["Meters"],
       s -> Quantity["Seconds"],
       km -> Quantity["Kilometers"]
       })
    //.
    {
     Power[Quantity[m_, u_]^i_, j_] :> Quantity[m^(i*j), u^(i*j)],
     Times[x_, Quantity[m_, u_]^(i_: 1)] :> Quantity[x*m^i, u^i]
     }];

It works for the (few) examples I've tried, like

withUnits[a m/s^2 * (3s)^2] /. a -> {0, 1, 2}

but I'm not sure if the Power/Times replacement really covers all cases. Maybe someone can find counterexamples or improve it.


Using @Leonid's answer and this answer by rm -rf, I started a package MyUnits that looks like this:

BeginPackage["MyUnits`"]

Unprotect/@{Quantity,Times};
Quantity/:(0|0.) Quantity[_,unit_]:=Quantity[0,unit]
Protect/@{Quantity,Times};

meter=Quantity["Meters"];
second=Quantity["Seconds"];
hertz=Quantity["Hertz"];
minute=Quantity["Minutes"];
hour=Quantity["Hours"];
byte=Quantity["Bytes"];
kilobyte=Quantity["Kilobytes"];
megabyte=Quantity["Megabytes"];

EndPackage[ ]

Using that, I get the simple input I had with the old Units package (including command completion) and things like 0 second + 1 hour still work.

share|improve this question
1  
I wouldn't like to turn so many symbols into operators. But if you can live with a couple of Esc, it is definately simple to add an input alias for a nice small box that displays as you like and parses as Quantity –  Rojo Nov 28 '12 at 20:15
    
@Rojo: I have no idea how to do that. Could you add your suggestion as an answer? –  nikie Nov 28 '12 at 20:25
    
Definately but I won't have either time or a PC for another 2 hours. If that happens and noone has posted it I will –  Rojo Nov 28 '12 at 20:43
    
@nikie here's a post where I show how to setup input aliases. There are a few others on the site. I won't be able to post something for a while either, but perhaps you can try it out or Rojo will be back –  rm -rf Nov 28 '12 at 20:44
    
@IstvánZachar while this is new functionality, it was decided that the current version does not need a version tag, and that the most recent version should be automatically assumed. –  rcollyer Nov 28 '12 at 21:57

7 Answers 7

up vote 14 down vote accepted

Here is a cheap way which does not involve WA, but will only be as good as you make it to be (so that you'd have to customize it yourself): create a dynamic environment:

ClearAll[withUnits];
SetAttributes[withUnits, HoldAll];
withUnits[code_] :=
  Function[Null,
     Block[{Quantity},
       SetAttributes[Quantity, HoldRest];
       Quantity /: UnitConvert[arg_, Quantity[_, unit_]] :=
          UnitConvert[arg, unit];
       Quantity /: Times[0, Quantity[_, unit_]] :=
          Quantity[0, unit];
       With[{
          m = Quantity[1, "Meters"], 
          s = Quantity[1, "Seconds"],       
          min =  Quantity[1, "Minutes"],
          km = Quantity[1, "Kilometers"]
        },
       #]],
    HoldAll][code];

So that

withUnits[UnitConvert[1 m/s^2*(1 min)^2,km]]

(*  18/5km  *)

You can set $Pre = withUnits, if you want to save some typing. The above function is a hack, of course, but it does dynamic code generation, uses Block trick and local UpValues, so I decided to post it still.

share|improve this answer
    
How would you solve the withUnits[UnitConvert[0 m/s^2*(1 min)^2, km]] problem? –  nikie Nov 28 '12 at 20:24
    
@nikie I would add this: Quantity /: Times[0, Quantity[_, unit_]] := Quantity[0, unit];. Not sure it will always work though. –  Leonid Shifrin Nov 28 '12 at 20:29
    
Neat trick (+1), but if it's only used with UnitConvert, wouldn't it be easier to just write a new myUnitConvert function instead and substitute it for UnitConvert? In other words, what's the advantage of withUnit here? I don't have version 9, so maybe I'm overlooking something. (BTW I definitely think online access shouldn't be needed for this). –  Jens Nov 28 '12 at 21:02
    
@Jens Thanks. The advantage is that this allows you to piggyback on the built-in UnitConvert without knowing how it operates, meaning resusing the higher-level abstraction, and that is always better. In essence, my code just generates the same exact code that would be otherwise written by hand, so it can be interpreted as a custom preprocessor written on the level of already parsed code (a macro). –  Leonid Shifrin Nov 28 '12 at 21:07
    
@Jens Perhaps, let me clarify: the code is being generated inside the Block, but then UnitConvert fires normally - if it is present in code. –  Leonid Shifrin Nov 28 '12 at 21:09

As noted in the documentation for Quantity, you can use ctrl-= to input units. This uses Wolfram|Alpha, so needs an internet connection.

enter image description here

Quantity will also use Wolfram|Alpha to try to interpret strings, so you could also use:

In[8]:= UnitConvert[Quantity["1 m/s^2*(1 min)^2"], Quantity["km"]]

Out[8]= Quantity[18/5, "Kilometers"]
share|improve this answer
6  
I've tried that first, but I can't imagine entering long formulas with that. –  nikie Nov 28 '12 at 20:21
    
@nikie Why? You only need 1 Ctrl= call more than the characters you're already typing in your preferred version... If you click the lower box in the above, it'll display the formula exactly as you want in the question. –  rm -rf Nov 28 '12 at 20:42
    
@rm-rf WolframAlpha inside Mathematica is really slow for me. –  Tyilo Aug 27 '13 at 20:35

You could set an input alias such as

With[{rules = {"m" -> "Meters", "km" -> "Kilometers"}},
 AppendTo[CurrentValue[InputNotebook[], InputAliases], 
  "qu" -> TemplateBox[{"\[SelectionPlaceholder]", "\[Placeholder]"}, 
    "QuantityUnit", 
    DisplayFunction -> (PanelBox[RowBox[{##}], FrameMargins -> 2] &), 
    InterpretationFunction -> (ToBoxes@
        Quantity[ToExpression@#1, Evaluate[#2 /. rules]] &)]]]

Then, escquesc brings up a little panel with a couple of placeholders where you write, for example

Mathematica graphics

You could set the styles on a stylesheet, add units to your list of rules, or even not add any. If it doesn't recognize it, it queries WolframAlpha just like Quantity, and then caches the result. Setting the input alias to $FrontEnd instead of InputNotebook[] would make it global and permanent. Or you could add it to the Notebook style of some stylesheet instead

share|improve this answer
    
Thanks for the typos and neat keys edits. But why is Evaluate necessary? –  Rojo Dec 4 '12 at 15:14
    
Because Quantity has the attribute HoldRest, so code like Quantity[5, "m" /. "m" -> "Meters"] remains unevaluated. Maybe you had a couple of copies of the InputAlias in your test notebook, so you didn't notice the problems... –  Simon Dec 5 '12 at 0:55
    
@Simon, I could swear Quantity didn't have that attribute earlier. I had probably been messing with it –  Rojo Dec 5 '12 at 1:01
    
@Simon!!! After the first TemporalData use, it's attributes get updated to a simple NHoldAll! –  Rojo Dec 5 '12 at 4:08

For those still preferring the use of AutomaticUnits, Jon has posted a work around to allow its use with v.9 at http://blog.wolfram.com/2010/12/09/automatic-physical-units-in-mathematica/#comments

share|improve this answer

Here's my extensions to Rojo's answer. I've moved the replacement rules into a global variable, to make them easier to modify on the fly

$UnitReplacementRules = {"fm"->"Femtometers","nm"->"Nanometers","\[Mu]m"->"Micrometers","mm"->"Millimeters","cm"->"Centimeters","m"->"Meters","km"|"kms"->"Kilometers","mi"->"Miles", "s"|"sec"->"Seconds","min"|"mins"->"Minutes","h"|"hr"|"hrs"->"Hours","yr"|"yrs"->"Years", "\[Mu]g"->"Micrograms","mg"->"Milligrams","g"->"Grams","kg"->"Kilograms", "L"|"l"->"Liters","mL"|"ml"->"Milliliters", "N" -> "Newtons", "K"->"Kelvins","mol"->"Moles","M"->"Molar"}

and I've made it cope with compound units with (hopefully) arbitrary 2d structures (e.g., powers and fractions). It also overwrites any existing qu InputAliases

CurrentValue[InputNotebook[], InputAliases] = 
 Append[DeleteCases[CurrentValue[InputNotebook[], InputAliases], "qu" -> _],
  "qu" -> TemplateBox[{"\[SelectionPlaceholder]", "\[Placeholder]"}, 
    "QuantityUnit", Tooltip -> "Unit Template", 
    DisplayFunction -> (PanelBox[RowBox[{#1, StyleBox[#2, "QuantityUnitTraditionalLabel"]}], FrameMargins -> 2] &), 
    InterpretationFunction -> (With[{unit = #2 /. s_String?LetterQ :> "\""~~(s/.$UnitReplacementRules)~~"\""},
        (*Print[{#2, unit, StringTake[ToString[MakeExpression@#2, InputForm], {14, -2}], KnownUnitQ @@ MakeExpression@unit}];*)
        If[KnownUnitQ@@MakeExpression@unit,
         RowBox[{"Quantity", "[", #1, ",", unit, "]"}],
         RowBox[{"Quantity", "[", #1, ",", "\""~~StringTake[ToString[MakeExpression@#2, InputForm], {14, -2}]~~"\"", "]"}]
        ]] &)]]

Some examples: Examples

The same comments as in Rojo's answer apply. This is currently set up to add the alias to only the InputNotebook, it could be made to add the alias to $FrontEnd which would make it work in all notebooks (until it is removed). Note that $UnitReplacementRules would have to be added to the init.m or something similar if you want this code to work across sessions.

share|improve this answer
    
I just wanted to add a couple of things to the unit handling, so came here to check for new answers. Glad to find you've worked on it. –  Rojo Dec 16 '12 at 5:52
    
I'll probably take your version, move the DisplayFunction and the InputAlias to a stylesheet, add some way (no idea how yet) to make the "$UnitReplacementRules" remember for ever every conversion made with WolframAlpha. And perhaps if I use it often, make it a shortcut intead of an input alias –  Rojo Dec 16 '12 at 6:08
    
Any other ideas of improvement? Btw, it is not necessarily better but using Prepend instead of Append I think would effectively override the previous InputAlias without actually deleting it –  Rojo Dec 16 '12 at 6:11
    
Those are all good ideas. I think another thing to do would be to make a palette to quickly view and modify the $UnitReplacementRules. See the Grassmann Algebra Package's palette for something similar. –  Simon Dec 20 '12 at 3:35
    
@Simon I have greatly improved the number of units supported with this without WolframAlpha. See my answer :). –  Tyilo Aug 18 '13 at 4:42

Improved version of Simon's answer which tries several different transformation of the input string to convert it to the full form that Mathematica needs:

Begin["System`"]

(* Deca is intentionally left out as only one character prefixes are supported *)
$SIPrefixes={"Y"->"Yotta","Z"->"Zetta","E"->"Exa","P"->"Peta","T"->"Tera","G"->"Giga","M"->"Mega","k"->"Kilo","h"->"Hecto","d"->"Deci","c"->"Centi","m"->"Milli","\[Mu]"|"\[Micro]"->"Micro","n"->"Nano","p"->"Pico","f"->"Femto","a"->"Atto","z"->"Zepto","y"->"Yocto"};
$UnitAbbreviations={"\[Degree]"->"angularDegrees","\[Degree]C"->"degreesCelsius","\[CapitalOmega]"->"ohms","A"->"amperes","Bq"->"becquerels","C"->"coulombs","F"->"farads","Gy"->"grays","H"->"henries","Hz"->"hertz","J"->"joules","K"->"kelvins","L"->"liters","M"->"molar","N"->"newtons","Pa"->"pascals","S"->"siemens","Sv"->"sieverts","T"->"teslas","V"->"volts","W"->"watts","Wb"->"webers","a"->"julianYears","atm"->"atmospheres","au"->"astronomicalUnit","bar"->"bars","cd"->"candelas","d"->"days","eV"->"electronvolts","g"->"grams","h"->"hours","kat"->"katals","lm"->"lumens","lx"->"lux","m"->"meters","min"->"minutes","mol"->"moles","rad"->"radians","s"->"seconds","sr"->"steradians"};

FirstDropWhile[list_, cond_] := (
    l = LengthWhile[list,cond];
    If[l == Length[list],
        None,
        list[[l+1]]
    ])
StringCapitalize[str_] := ToUpperCase @ Characters[str][[1]] <> StringDrop[str, 1]
ReplaceUnit[str_] := str /. $UnitAbbreviations
    ReplaceSIPrefix[str_] := (Characters[str][[1]] /. $SIPrefixes) <> StringDrop[str, 1]

UnitFullName[str_]:=(
    transformations = {Identity, StringCapitalize,
        Composition[StringCapitalize,ReplaceUnit], ReplaceSIPrefix,
        (ReplaceSIPrefix@Characters[#][[1]]) <> ReplaceUnit[StringDrop[#,1]]&
    };
    candidates = Flatten[{#, # <> "s"}& /@ Through[transformations[str]]];
    FirstDropWhile[candidates, !KnownUnitQ@# &]
)

CurrentValue[$FrontEnd, InputAliases] = 
    	Append[DeleteCases[CurrentValue[$FrontEnd, InputAliases], "qu" -> _],
    "qu" -> TemplateBox[{"\[SelectionPlaceholder]", "\[Placeholder]"}, 
    "QuantityUnit", Tooltip -> "Unit Template", 
    DisplayFunction -> (PanelBox[RowBox[{#1, StyleBox[#2, "QuantityUnitTraditionalLabel"]}], FrameMargins -> 2] &), 
    InterpretationFunction -> (With[{unit = #2 /. s_String?LetterQ :> "\""~~(UnitFullName[s])~~"\"" /. s_String :> (s /. "\[CenterDot]" -> "*")},
        If[KnownUnitQ@@MakeExpression@unit,
            RowBox[{"Quantity", "[", #1, ",", unit, "]"}],
            RowBox[{"Quantity", "[", #1, ",", "\""~~StringTake[ToString[MakeExpression@#2, InputForm], {14, -2}]~~"\"", "]"}]
        ]] &)]]

End[]

Sorry for the lack of whitespace. I made this using TraditionalForm.

share|improve this answer

I gave up trying to use Quantity and the units package in version 9 because of the drop in speed and the clumsy way of incorporating units that makes long expressions difficult to read. I have therefore stuck with Jon McLoone's package that works very well. It can found at http://blog.wolfram.com/2010/12/09/automatic-physical-units-in-mathematica/#comments

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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