source: trunk/gui/scripts/units.tcl @ 11

Last change on this file since 11 was 11, checked in by mmc, 19 years ago

Major reorganization of the entire package. The config.xml file
is now irrelevant. All the action is in the tool.xml file. The
main program now organizes all input into 1) side-by-side pages,
2) input/result (wizard-style) pages, or 3) a series of wizard-
style pages. The <input> can have <phase> parts representing
the various pages.

Added a new ContourResult? widget based on Swaroop's vtk plotting
code.

Also, added easymesh and showmesh to the "tools" directory.
We need these for Eric Polizzi's code.

File size: 16.4 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: units - mechanism for converting numbers with units
3#
4#  These routines make it easy to define a system of units, to decode
5#  numbers with units, and convert a number from one set of units to
6#  another.
7# ======================================================================
8#  AUTHOR:  Michael McLennan, Purdue University
9#  Copyright (c) 2004-2005
10#  Purdue Research Foundation, West Lafayette, IN
11# ======================================================================
12package require Itcl
13
14namespace eval Rappture { # forward declaration }
15namespace eval Rappture::Units { # forward declaration }
16
17# ----------------------------------------------------------------------
18# USAGE: define units ?-type name? ?-metric boolean?
19# USAGE: define units1->units2 {expr}
20#
21# Used to define a new fundamental type of units, or to define another
22# system of units based on a fundamental type.  Once units are defined
23# in this manner, the "convert" function can be used to convert a number
24# in one system of units to another system.
25# ----------------------------------------------------------------------
26proc Rappture::Units::define {what args} {
27    if {[regexp {(.+)->(.+)} $what match new fndm]} {
28        if {[llength $args] != 2} {
29            error "wrong # args: should be \"define units1->units2 exprTo exprFrom\""
30        }
31        #
32        # Convert the units variables embedded in the conversion
33        # expressions to something that Tcl can handle.  We'll
34        # use ${number} to represent the variables.
35        #
36        foreach {exprTo exprFrom} $args { break }
37        regsub -all $new $exprTo {${number}} exprTo
38        regsub -all $fndm $exprFrom {${number}} exprFrom
39
40        Rappture::Units::System #auto $new \
41            -basis [list $fndm $exprTo $exprFrom]
42
43    } elseif {[regexp {^/?[a-zA-Z]+[0-9]*$} $what]} {
44        array set opts {
45            -type ""
46            -metric 0
47        }
48        foreach {key val} $args {
49            if {![info exists opts($key)]} {
50                error "bad option \"$key\": should be [join [lsort [array names opts]] {, }]"
51            }
52            set opts($key) $val
53        }
54        eval Rappture::Units::System #auto $what [array get opts]
55    } else {
56        error "bad units definition \"$what\": should be something like m or /cm3 or A->m"
57    }
58}
59
60# ----------------------------------------------------------------------
61# USAGE: convert value ?-context units? ?-to units? ?-units on/off?
62#
63# Used to convert one value with units to another value in a different
64# system of units.  If the value has no units, then the units are taken
65# from the -context, if that is supplied.  If the -to system is not
66# specified, then the value is converted to fundamental units for the
67# current system.
68# ----------------------------------------------------------------------
69proc Rappture::Units::convert {value args} {
70    array set opts {
71        -context ""
72        -to ""
73        -units "on"
74    }
75    foreach {key val} $args {
76        if {![info exists opts($key)]} {
77            error "bad option \"$key\": should be [join [lsort [array names opts]] {, }]"
78        }
79        set opts($key) $val
80    }
81
82    #
83    # Parse the value into the number part and the units part.
84    #
85    set value [string trim $value]
86    if {![regexp {^([-+]?[0-9]+\.?([0-9]+)?([eEdD][-+]?[0-9]+)?) *(/?[a-zA-Z]+[0-9]*)?$} $value match number dummy1 dummy2 units]} {
87        set mesg "bad value \"$value\": should be real number with units"
88        if {$opts(-context) != ""} {
89            append mesg " of [Rappture::Units::description $opts(-context)]"
90        }
91        error $mesg
92    }
93    if {$units == ""} {
94        set units $opts(-context)
95    }
96
97    #
98    # Try to find the object representing the current system of units.
99    #
100    set units [Rappture::Units::System::regularize $units]
101    set oldsys [Rappture::Units::System::for $units]
102    if {$oldsys == ""} {
103        set mesg "value \"$value\" has unrecognized units"
104        if {$opts(-context) != ""} {
105            append mesg ".\nShould be units of [Rappture::Units::description $opts(-context)]"
106        }
107        error $mesg
108    }
109
110    #
111    # Convert the number to the new system of units.
112    #
113    if {$opts(-to) == ""} {
114        # no units -- return the number as is
115        return "$number$units"
116    }
117    return [$oldsys convert "$number$units" $opts(-to) $opts(-units)]
118}
119
120# ----------------------------------------------------------------------
121# USAGE: description <units>
122#
123# Returns a description for the specified system of units.  The
124# description includes the abstract type (length, temperature, etc.)
125# along with a list of all compatible systems.
126# ----------------------------------------------------------------------
127proc Rappture::Units::description {units} {
128    set sys [Rappture::Units::System::for $units]
129    if {$sys == ""} {
130        return ""
131    }
132    set mesg [$sys cget -type]
133    set ulist [Rappture::Units::System::all $units]
134    if {"" != $ulist} {
135        append mesg " ([join $ulist {, }])"
136    }
137    return $mesg
138}
139
140# ----------------------------------------------------------------------
141itcl::class Rappture::Units::System {
142    public variable basis ""
143    public variable type ""
144    public variable metric 0
145
146    constructor {name args} { # defined below }
147
148    public method basic {}
149    public method fundamental {}
150    public method convert {value units showUnits}
151    private variable _system ""  ;# this system of units
152
153    public proc for {units}
154    public proc all {units}
155    public proc regularize {units}
156
157    private common _base  ;# maps unit name => System obj
158
159    # metric conversion prefixes
160    private common _prefix2factor
161    array set _prefix2factor {
162        c  1e-2
163        m  1e-3
164        u  1e-6
165        n  1e-9
166        p  1e-12
167        f  1e-15
168        a  1e-18
169        k  1e+3
170        M  1e+6
171        G  1e+9
172        T  1e+12
173        P  1e+15
174    }
175}
176
177# ----------------------------------------------------------------------
178# CONSTRUCTOR
179# ----------------------------------------------------------------------
180itcl::body Rappture::Units::System::constructor {name args} {
181    if {![regexp {^/?[a-zA-Z]+[0-9]*$} $name]} {
182        error "bad units declaration \"$name\""
183    }
184    eval configure $args
185
186    #
187    # The -basis is a list {units exprTo exprFrom}, indicating the
188    # fundamental system of units that this new system is based on,
189    # and the expressions that can be used to convert this new system
190    # to and from the fundamental system.
191    #
192    if {$basis != ""} {
193        foreach {base exprTo exprFrom} $basis { break }
194        if {![info exists _base($base)]} {
195            error "fundamental system of units \"$base\" not defined"
196        }
197        while {$type == "" && $base != ""} {
198            set obj $_base($base)
199            set type [$obj cget -type]
200            set base [lindex [$obj cget -basis] 0]
201        }
202    }
203    set _system $name
204    set _base($name) $this
205}
206
207# ----------------------------------------------------------------------
208# USAGE: basic
209#
210# Returns the basic system of units for the current system.  The
211# basic units may be the only units in this system.  But if this
212# system has "-metric 1", the basic system is the system without
213# any metric prefixes.
214# ----------------------------------------------------------------------
215itcl::body Rappture::Units::System::basic {} {
216    return $_system
217}
218
219# ----------------------------------------------------------------------
220# USAGE: fundamental
221#
222# Returns the fundamental system of units for the current system.
223# For example, the current units might be degrees F, but the
224# fundamental system might be degrees C.  The fundamental system
225# depends on how each system is defined.  You can see it as the
226# right-hand side of the -> arrow, as in "F->C".
227# ----------------------------------------------------------------------
228itcl::body Rappture::Units::System::fundamental {} {
229    if {$basis != ""} {
230        set sys [Rappture::Units::System::for [lindex $basis 0]]
231        return [$sys fundamental]
232    }
233    return $_system
234}
235
236# ----------------------------------------------------------------------
237# USAGE: convert value newUnits showUnits
238#
239# Converts a value with units to another value with the specified
240# units.  The value must have units that are compatible with the
241# current system.  Returns a string that represented the converted
242# number and its new units.
243# ----------------------------------------------------------------------
244itcl::body Rappture::Units::System::convert {value newUnits showUnits} {
245    if {![regexp {^([-+]?[0-9]+\.?([0-9]+)?([eEdD][-+]?[0-9]+)?) *(/?[a-zA-Z]+[0-9]*)?$} $value match number dummy1 dummy2 units]} {
246        error "bad value \"$value\": should be real number with units"
247    }
248
249    #
250    # Check the base units coming in.  They should match the base units
251    # for the current system, or the base units for the fundamental basis.
252    # If not, something went wrong with the caller.
253    #
254    set slash ""
255    set prefix ""
256    set power "1"
257    if {$metric && [regexp {^(/?)([cmunpfakMGTP])([a-zA-Z]+)([0-9]*)$} $units match slash prefix base power]} {
258        set baseUnits "$slash$base$power"
259    } else {
260        set baseUnits $units
261    }
262    if {![string equal $baseUnits $_system]
263          && ![string equal $baseUnits [lindex $basis 0]]} {
264        error "can't convert value \"$value\": should have units \"$_system\""
265    }
266
267    #
268    # If the number coming in has a metric prefix, convert the number
269    # to the base system.
270    #
271    if {$prefix != ""} {
272        if {$power == ""} {
273            set power 1
274        }
275        if {$slash == "/"} {
276            set number [expr {$number/pow($_prefix2factor($prefix),$power)}]
277        } else {
278            set number [expr {$number*pow($_prefix2factor($prefix),$power)}]
279        }
280    }
281
282    #
283    # If the incoming units are a fundamental basis, then convert
284    # the number from the basis to the current system.
285    #
286    if {[string equal $baseUnits [lindex $basis 0]]} {
287        foreach {base exprTo exprFrom} $basis { break }
288        set number [expr $exprFrom]
289    }
290
291    #
292    # Check the base units for the new system of units.  If they match
293    # the current system, then we're almost done.  Just handle the
294    # metric prefix, if there is one.
295    #
296    set slash ""
297    set prefix ""
298    set power "1"
299    if {$metric && [regexp {^(/?)([cmunpfakMGTP])([a-zA-Z]+)([0-9]*)$} $newUnits match slash prefix base power]} {
300        set baseUnits "$slash$base$power"
301    } else {
302        set baseUnits $newUnits
303    }
304    if {[string equal $baseUnits $_system]} {
305        if {$prefix != ""} {
306            if {$power == ""} {
307                set power 1
308            }
309            if {$slash == "/"} {
310                set number [expr {$number*pow($_prefix2factor($prefix),$power)}]
311            } else {
312                set number [expr {$number/pow($_prefix2factor($prefix),$power)}]
313            }
314        }
315        if {$showUnits} {
316            return "$number$newUnits"
317        }
318        return $number
319    }
320
321    #
322    # If we want a different system of units, then convert this number
323    # to the fundamental basis.  If there is no fundamental basis, we
324    # must already be in the fundamental basis.
325    #
326    set base $_system
327    if {"" != $basis} {
328        foreach {base exprTo exprFrom} $basis { break }
329        set number [expr $exprTo]
330    }
331
332    set newsys [Rappture::Units::System::for $newUnits]
333    return [$newsys convert "$number$base" $newUnits $showUnits]
334}
335
336# ----------------------------------------------------------------------
337# CONFIGURATION OPTION: -basis
338# ----------------------------------------------------------------------
339itcl::configbody Rappture::Units::System::basis {
340    if {[llength $basis] != 3} {
341        error "bad basis \"$name\": should be {units exprTo exprFrom}"
342    }
343}
344
345# ----------------------------------------------------------------------
346# CONFIGURATION OPTION: -metric
347# ----------------------------------------------------------------------
348itcl::configbody Rappture::Units::System::metric {
349    if {![string is boolean -strict $metric]} {
350        error "bad value \"$metric\": should be boolean"
351    }
352}
353
354# ----------------------------------------------------------------------
355# USAGE: for units
356#
357# Returns the System object for the given system of units, or ""
358# if there is no system that matches the units string.
359# ----------------------------------------------------------------------
360itcl::body Rappture::Units::System::for {units} {
361    #
362    # See if the units are a recognized system.  If not, then try to
363    # extract any metric prefix and see if what's left is a recognized
364    # system.  If all else fails, see if we can find a system without
365    # the exact capitalization.  The user might say "25c" instead of
366    # "25C".  Try to allow that.
367    #
368    if {[info exists _base($units)]} {
369        return $_base($units)
370    } else {
371        set orig $units
372        if {[regexp {^(/?)[cCmMuUnNpPfFaAkKgGtT](.+)$} $units match slash tail]} {
373            set base "$slash$tail"
374            if {[info exists _base($base)]} {
375                set sys $_base($base)
376                if {[$sys cget -metric]} {
377                    return $sys
378                }
379            }
380
381            # check the base part for improper capitalization below...
382            set units $base
383        }
384
385        set matching ""
386        foreach u [array names _base] {
387            if {[string equal -nocase $u $units]} {
388                lappend matching $_base($u)
389            }
390        }
391        if {[llength $matching] == 1} {
392            set sys [lindex $matching 0]
393            #
394            # If we got rid of a metric prefix above, make sure
395            # that the system is metric.  If not, then we don't
396            # have a match.
397            #
398            if {[string equal $units $orig] || [$sys cget -metric]} {
399                return $sys
400            }
401        }
402    }
403    return ""
404}
405
406# ----------------------------------------------------------------------
407# USAGE: all units
408#
409# Returns a list of all units compatible with the given units string.
410# Compatible units are determined by following all conversion
411# relationships that lead to the same base system.
412# ----------------------------------------------------------------------
413itcl::body Rappture::Units::System::all {units} {
414    set sys [Rappture::Units::System::for $units]
415    if {$sys == ""} {
416        return ""
417    }
418
419    if {"" != [$sys cget -basis]} {
420        set basis [lindex [$sys cget -basis] 0]
421    } else {
422        set basis $units
423    }
424
425    set ulist $basis
426    foreach u [array names _base] {
427        set obj $_base($u)
428        set b [lindex [$obj cget -basis] 0]
429        if {$b == $basis} {
430            lappend ulist $u
431        }
432    }
433    return $ulist
434}
435
436# ----------------------------------------------------------------------
437# USAGE: regularize units
438#
439# Examines the given expression of units and tries to regularize
440# it so it has the proper capitalization.  For example, units like
441# "/CM3" are converted to "/cm3".  If the units are not recognized,
442# then they are returned as-is.
443# ----------------------------------------------------------------------
444itcl::body Rappture::Units::System::regularize {units} {
445    set sys [for $units]
446    if {$sys == ""} {
447        return $units
448    }
449    # note: case-insensitive matching for metric prefix
450    if {[regexp {^(/?)([cCmMuUnNpPfFaAkKgGtT])(.+)$} $units match slash prefix tail]} {
451        if {[regexp {^[CUNFAK]$} $prefix]} {
452            # we know that these should be lower case
453            set prefix [string tolower $prefix]
454        } elseif {[regexp {^[GT]$} $prefix]} {
455            # we know that these should be upper case
456            set prefix [string toupper $prefix]
457        }
458        return "$slash$prefix[string trimleft [$sys basic] /]"
459    }
460    return [$sys basic]
461}
462
463# ----------------------------------------------------------------------
464
465# ----------------------------------------------------------------------
466# Define common units...
467# ----------------------------------------------------------------------
468Rappture::Units::define m -type length -metric yes
469Rappture::Units::define A->m {A*1.0e-10} {m*1.0e10}
470
471Rappture::Units::define /m3 -type density -metric yes
472
473Rappture::Units::define C -type temperature -metric no
474Rappture::Units::define K->C {K-273.15} {C+273.15}
475Rappture::Units::define F->C {(F-32)/1.8} {(1.8*C)+32}
476
477Rappture::Units::define eV -type energy -metric yes
478
479Rappture::Units::define V -type voltage -metric yes
Note: See TracBrowser for help on using the repository browser.