~ubuntu-branches/ubuntu/wily/octave-miscellaneous/wily-proposed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
## Copyright (C) 2005 Carl Osterwisch <osterwischc@asme.org>
## Copyright (C) 2013 Carnë Draug <carandraug@octave.org>
##
## This program is free software; you can redistribute it and/or modify it under
## the terms of the GNU General Public License as published by the Free Software
## Foundation; either version 3 of the License, or (at your option) any later
## version.
##
## This program is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public License along with
## this program; if not, see <http://www.gnu.org/licenses/>.

## -*- texinfo -*-
## @deftypefn  {Function File} {} units (@var{fromUnit}, @var{toUnit})
## @deftypefnx {Function File} {} units (@var{fromUnit}, @var{toUnit}, @var{x})
## Return the conversion factor from @var{fromUnit} to @var{toUnit} measurements.
##
## This is an octave interface to the @strong{GNU Units} program which comes
## with an annotated, extendable database defining over two thousand 
## measurement units.  See @code{man units} or 
## @url{http://www.gnu.org/software/units} for more information.
## If the optional argument @var{x} is supplied, return that argument
## multiplied by the conversion factor.  For example, to 
## convert three values from miles per hour into meters per second:
##
## @example
## units ("mile/hr", "m/sec", [30, 55, 75])
## ans =
##
##   13.411  24.587  33.528
## @end example
## @end deftypefn

function y = units (fromUnit, toUnit, x = 1)

  if (nargin < 2 || nargin > 3)
    print_usage ();
  elseif (! ischar (fromUnit))
    error ("units: FromUNIT must be a string");
  elseif (! ischar (toUnit))
    error ("units: ToUNIT must be a string");
  elseif (! isnumeric (x))
    error ("units: X must be numeric");
  endif

  persistent available  = check_units ();
  persistent compact    = has_compact_option ();
  persistent template   = template_cmd (compact);

  ## We have to insert the template on the string this way, because it may have
  ## a %%.16g which we may want to keep for later use in non-linear conversion
  cmd = sprintf ([template ' "%s" "%s"'], fromUnit, toUnit);
  [status, rawoutput] = system (cmd);
  if (status)
    error ("units: %s", rawoutput);
  endif

  if (! compact)
    ## No compact or one-line option, we need to find the conversion factor
    ## from the text ourselves
    ini_factor = index (rawoutput, "*");
    end_factor = index (rawoutput, "\n") - 1;
    if (isempty (ini_factor) || ini_factor > end_factor)
        error ("units: unable to parse output from units:\n%s", rawoutput);
    endif
    rawoutput = rawoutput(ini_factor+1:end_factor);
  endif

  c_factor = str2double (rawoutput);
  if (any (isnan (c_factor(:))))
    if (index (rawoutput, "=") || index (rawoutput, "+") ||
        index (rawoutput, "-") || index (rawoutput, "*") ||
        index (rawoutput, "/"))
      ## If there's a mathematical operator in the output, it may be a formula
      ## for a non-linear conversion such as "tempC(x) = x K + stdtemp"
      ## We don't check for the equal only because some versions of units data
      ## file (not version of the units application, see bug #38270) have a
      ## very different syntax.
      if (nargin < 3)
        ## for a non-linear unit conversion, we need a value to convert
        error ("units: argument X is required for non-linear unit conversion");
      endif
      y = zeros (size (x));
      template_non_linear = function_template (template, fromUnit, toUnit);
      for ind = 1:numel(y)
        cmd = sprintf (template_non_linear, x(ind));
        [status, rawoutput] = system (cmd);
        if (status)
          error ("units: %s", rawoutput);
        endif
        y(ind) = str2double (rawoutput);
        if (isnan (y(ind)))
          error ("units unable to parse non-linear conversion `%s'", rawoutput);
        endif
      endfor
    else
      error ("units: unable to parse output `%s' from units.", rawoutput);
    endif
  else
    y = x * c_factor;
  endif
endfunction

function fpath = check_units ()
  ## See bug #38270 about why we're checking this way.
  fpath = file_in_path (getenv ("PATH"), sprintf ("units%s", octave_config_info ("EXEEXT")));
  if (isempty (fpath))
    error ("units: %s\nVerify that GNU units is installed in the current path.",
           rawoutput);
  endif
endfunction

function compact = has_compact_option ()
  compact = true;
  ## We must give some units to convert because the only thing that would
  ## make it not do any work (--version) actually exits with exit value 3
  [status, rawoutput] = system ('units --compact --one-line "in" "cm"');
  if (status)
    compact = false;
  endif
endfunction

function template = template_cmd (compact)
  ## do we have the format option?
  format = true;
  [status, rawoutput] = system ('units --output-format "%.16g" "in" "cm"');
  if (status)
    format = false;
  endif

  template = "units ";
  if (format)
    template = [template '--output-format "%%.16g" '];
  endif
  if (compact)
    template = [template '--compact --one-line '];
  endif
endfunction

## Test the correct way to do non-linear conversion.
function template = function_template (template, from, to)

  ## First try the most common syntax distributed with the most recent
  ## units.dat file using parentheses "from(x)" as if it was a function.
  if (! system (sprintf ([template '"%s(1)" "%s"'], from, to), true))
    template = sprintf ('%s "%s(%%.16g)" "%s"', template, from, to);

  ## If it doesn't work, try the oldest syntax of the style "x from"
  elseif (! system (sprintf ([template '"100 %s" "%s"'], from, to), true))
    template = sprintf ('%s "%%.16g %s" "%s"', template, from, to);

  ## If it doesn't work, give up
  else
    error ("units: unable to identify correct syntax for non-linear conversion");
  endif
endfunction

%!demo
%! a.value = 100; a.unit = 'lb';
%! b.value =  50; b.unit = 'oz';
%! c.unit = 'kg';
%! c.value = units(a.unit, c.unit, a.value) + units(b.unit, c.unit, b.value)

# test normal usage
%!assert (units ("in", "mm"), 25.4)
# multiple values
%!assert (units ("in", "mm", [5 7; 8 9]), 25.4 * [5 7; 8 9])
# a non-linear conversion
%!assert (units ("tempC", "tempF", 100), 212)
%!error <non-linear unit conversion> units ("tempC", "tempF")