formula calculators

A small calculator can be useful to evaluate a single formula involving a few parameters/variables. This formula calculator class constructs a small calculator which evaluates the formula or its inverse with respect to a particular parameter, according to specified code for each result. RVB

First, in incr tcl:

 class ForCalc {
     public variable title "formula calculator" {
         if {[info exists _Comp(text)]} {
             configure $_Comp(text) $title
         }
     }
     private variable _Info
     private variable _Comp
     # ========================================================================
     # constants that can be used in recalculation code
     # ========================================================================
     common _pi_   [expr {acos(-1)}]
     common _tabs_ 273.16          ;# T(O deg C) deg K
     common _kb_   1.380622e-23    ;# (J/K)  : boltzmann constant
     common _kbev_ 8.61708e-05     ;# (eV/K) : boltzmann constant
     common _hbar_ 6.58218e-16     ;# (eV-s) : planck constant
     common _co_   2.99792e+10     ;# (cm/s) : speed of light in vacuum
     common _qe_   1.602192e-19    ;# (C)    : unit charge
     common _eo_   8.854215e-14    ;# (F/cm) : permittivity of free space
     common _eox_  3.45314385e-13  ;# F/cm   : permittivity of SiO2
     common _esi_  1.03594315e-12  ;# F/cm   : permittivity of Si
     common _mo_   9.10956e-31     ;# (kg)   : electron rest mass
     # ========================================================================
     # METHOD: constructor
     # ARGUMENTS:
     #   % par_specs
     #     list of lists of:
     #       parameter name
     #       parameter label for calculator display
     #       unit label for calculator display
     #       default value of the parameter
     #       recalculation key: pointer to code to evaluate
     #         when return is typed in parameter entry box
     #   % recalc_specs
     #     list of lists of:
     #       recalculation key
     #       recalculation code
     #     recalculation code should set the value(s) of one or more of the
     #       parameters specified in par_specs
     #   % args
     #       CONFIGURATION-OPTIONS
     # CONFIGURATION-OPTIONS:
     #   % -title <title>
     #       specify the calculator title
     # ========================================================================
     constructor {par_specs recalc_specs args} {
       foreach {par label unit value recalc} $par_specs {
           lappend _Info(pars) $par
           set _Info($par-label)  $label
           set _Info($par-unit)   $unit
           set _Info($par-value)  $value
           set _Info($par-recalc) $recalc
       }
       foreach {key code} $recalc_specs {
           set _Info($key-code)   $code
       }
       eval configure $args
       _gui
     }
     # ========================================================================
     # METHOD: destructor 
     # RESULTS:
     #   * destroys calculator gui
     # ========================================================================
     destructor {
         if {[info exists _Comp(top)]} {
           destroy $_Comp(top)
         }
     }
     # ========================================================================
     # METHOD: _gui (private)
     # PURPOSE: build calculator gui
     # ========================================================================
     private method _gui {} {
         set top .[namespace tail $this]
         toplevel $top
         set _Comp(top) $top
         #---------------------------------------------------------------------
         # main layout
         #---------------------------------------------------------------------
         frame   $top.text -borderwidth 2 -relief raised
         pack    $top.text -side top -expand 1 -fill both
         frame   $top.pars -borderwidth 2 -relief raised
         pack    $top.pars -side top -expand 1 -fill both
         set _Comp(text) $top.text.text
         set _Comp(quit) $top.text.quit
         message $_Comp(text) -justify center -text $title -aspect 800
         pack    $_Comp(text) -side left -expand 1 -fill both
         button  $_Comp(quit) -text quit -command "delete object $this"
         pack    $_Comp(quit) -side left -padx 10 -pady 10 -expand 1 -fill x
         #---------------------------------------------------------------------
         # parameter entry
         #---------------------------------------------------------------------
         foreach par $_Info(pars) {
           set _Comp($par-frame) $top.pars.$par
           set _Comp($par-label) $_Comp($par-frame).l
           set _Comp($par-entry) $_Comp($par-frame).e
           set _Comp($par-unit)  $_Comp($par-frame).u
           frame $_Comp($par-frame)
           pack  $_Comp($par-frame) -side top   -expand 1 -fill x
           label $_Comp($par-label) -text $_Info($par-label) -width 30 -anchor w
           pack  $_Comp($par-label) -side left  -expand 1 -fill x
           entry $_Comp($par-entry) -relief sunken
           pack  $_Comp($par-entry) -side left  -expand 1 -fill x
           label $_Comp($par-unit)  -text $_Info($par-unit)  -width 15 -anchor c
           pack  $_Comp($par-unit)  -side left  -expand 1 -fill x
           $_Comp($par-entry) insert 0 $_Info($par-value)
           bind  $_Comp($par-entry) <Return> [code $this _recalculate $par]
         }
         _recalculate [lindex $_Info(pars) 0]
         wm title $top $title
     }
     # ========================================================================
     # METHOD: _recalculate (private)
     # PURPOSE: recalculate parameter values
     # ARGUMENTS:
     #   % par_changed
     #       name of parameter which had <Return> in entry
     # ========================================================================
     private method _recalculate {par_changed} {
         foreach par $_Info(pars) {
             set $par [$_Comp($par-entry) get]
         }
         set recalc $_Info($par_changed-recalc)
         if {[info exists _Info($recalc-code)]} {
             eval $_Info($recalc-code)
         }
         foreach par $_Info(pars) {
             $_Comp($par-entry) delete 0 end
             $_Comp($par-entry) insert 0 [set $par]
         }
     }
 }

Here is a basic tcl version

 namespace eval ::forcalc {
     namespace export ForCalc
     variable Info
     variable Comp
     # ========================================================================
     # constants that can be used in recalculation code
     # ========================================================================
     variable _pi_   [expr {acos(-1)}]
     variable _tabs_ 273.16          ;# T(O deg C) deg K
     variable _kb_   1.380622e-23    ;# (J/K)  : boltzmann constant
     variable _kbev_ 8.61708e-05     ;# (eV/K) : boltzmann constant
     variable _hbar_ 6.58218e-16     ;# (eV-s) : planck constant
     variable _co_   2.99792e+10     ;# (cm/s) : speed of light in vacuum
     variable _qe_   1.602192e-19    ;# (C)    : unit charge
     variable _eo_   8.854215e-14    ;# (F/cm) : permittivity of free space
     variable _eox_  3.45314385e-13  ;# F/cm   : permittivity of SiO2
     variable _esi_  1.03594315e-12  ;# F/cm   : permittivity of Si
     variable _mo_   9.10956e-31     ;# (kg)   : electron rest mass
     # ========================================================================
     # PROC: ForCalc
     # PURPOSE: create and configure a new calculator
     # ARGUMENTS:
     #   % this
     #     a unique calculator handle
     #   % par_specs
     #     list of lists of:
     #       parameter name
     #       parameter label for calculator display
     #       unit label for calculator display
     #       default value of the parameter
     #       recalculation key: pointer to code to evaluate
     #         when return is typed in parameter entry box
     #   % recalc_specs
     #     list of lists of:
     #       recalculation key
     #       recalculation code
     #     recalculation code should set the value(s) of one or more of the
     #       parameters specified in par_specs
     #   % args
     #       CONFIGURATION-OPTIONS
     # CONFIGURATION-OPTIONS:
     #   % -title <title>
     #       specify the calculator title
     # ========================================================================
     proc ForCalc {this par_specs recalc_specs args} {
         variable Info
         variable Comp
         if {[info exists Comp($this-top)]} {
             error "\"$this\" calculator already exists"
         }
         set title "formula calculator"
         while {[llength $args] > 0} {
           set arg  [lindex $args 0]
           set args [lrange $args 1 end]
           switch -- $arg {
             -title {
               set title [lindex $args 0]
               set args  [lrange $args 1 end]
             }
             default {
               error "option \"$arg\" is not supported"
             }
           }
         }
         foreach {par label unit value recalc} $par_specs {
             lappend Info($this-pars) $par
             set Info($this-$par-label)  $label
             set Info($this-$par-unit)   $unit
             set Info($this-$par-value)  $value
             set Info($this-$par-recalc) $recalc
         }
         foreach {key code} $recalc_specs {
             set Info($this-$key-code)   $code
         }
         set top .$this
         toplevel $top
         set Comp($this-top) $top
         frame   $top.text -borderwidth 2 -relief raised
         pack    $top.text -side top -expand 1 -fill both
         frame   $top.pars -borderwidth 2 -relief raised
         pack    $top.pars -side top -expand 1 -fill both
         message $top.text.msg -justify center -text $title -aspect 800
         pack    $top.text.msg -side left -expand 1 -fill both
         button  $top.text.but -text quit \
           -command [list ::forcalc::_destroy $this]
         pack    $top.text.but -side left -padx 10 -pady 10 -expand 1 -fill x
         #---------------------------------------------------------------------
         # parameter entry
         #---------------------------------------------------------------------
         foreach par $Info($this-pars) {
           set Comp($this-$par-frame) $top.pars.$par
           set Comp($this-$par-label) $Comp($this-$par-frame).l
           set Comp($this-$par-entry) $Comp($this-$par-frame).e
           set Comp($this-$par-unit)  $Comp($this-$par-frame).u
           frame $Comp($this-$par-frame)
           pack  $Comp($this-$par-frame) -side top   -expand 1 -fill x
           label $Comp($this-$par-label) -text $Info($this-$par-label) \
             -width 30 -anchor w
           pack  $Comp($this-$par-label) -side left  -expand 1 -fill x
           entry $Comp($this-$par-entry) -relief sunken
           pack  $Comp($this-$par-entry) -side left  -expand 1 -fill x
           label $Comp($this-$par-unit)  -text $Info($this-$par-unit)  \
             -width 15 -anchor c
           pack  $Comp($this-$par-unit)  -side left  -expand 1 -fill x
           $Comp($this-$par-entry) insert 0 $Info($this-$par-value)
           bind  $Comp($this-$par-entry) <Return> \
             [list forcalc::_recalculate $this $par]
         }
         _recalculate $this [lindex $Info($this-pars) 0]
         wm title $top $title
         return $this
     }
     # ========================================================================
     # METHOD: _destroy
     # ARGUMENTS:
     #   % this
     #       The calculator handle
     # RESULTS:
     #   * destroys calculator gui
     #   * unregisters calculator handle
     # ========================================================================
     proc _destroy {this} {
         variable Info
         variable Comp
         destroy $Comp($this-top)
         foreach item [array names Info $this-*] {
             unset Info($item)
         }
         foreach item [array names Comp $this-*] {
             unset Comp($item)
         }
     }
     # ========================================================================
     # PROC: _recalculate
     # PURPOSE: recalculate parameter values
     # ARGUMENTS:
     #   % this
     #       the calculator handle
     #   % par_changed
     #       name of parameter which had <Return> in entry
     # ========================================================================
     proc _recalculate {this par_changed} {
         variable Info
         variable Comp
         variable _pi_
         variable _tabs_
         variable _kb_
         variable _kbev_
         variable _hbar_
         variable _co_
         variable _qe_
         variable _eo_
         variable _eox_
         variable _esi_
         variable _mo_
         foreach par $Info($this-pars) {
             set $par [$Comp($this-$par-entry) get]
         }
         set recalc $Info($this-$par_changed-recalc)
         if {[info exists Info($this-$recalc-code)]} {
             eval $Info($this-$recalc-code)
         }
         foreach par $Info($this-pars) {
             $Comp($this-$par-entry) delete 0 end
             $Comp($this-$par-entry) insert 0 [set $par]
         }
     }
 }
 namespace import forcalc::ForCalc



Here are several examples:

A field mowing calculator.  Calculates the total time required to mow
a field at a constant speed and swath and number of turns, or the number
of turns required given total time, swath, and speed.

 ForCalc mowcalc {
   n     "number of turns"  {}    43  t
   dr    "swath"            ft     3  n
   s     "speed"            ft/s   3  n
   t     "total time"       min  100  n
 } {
   t {set t [expr {$n*($n+1)*($dr*$_pi_)/($s*60.0)}]}
   n {set n [expr {sqrt(0.25 + ($s*60.0*$t)/($dr*$_pi_)) - 0.5}]}
 } -title "field mow calculator"
 
An integrated resistor calculator.  Given a target resistance and width,
calculates the required resistor length.  Given a specified length and width,
calculates the resistance.

 ForCalc rescalc {
   r    "Resistance"       ohms           1000 l
   rho  "Sheet Resistance" ohms/square    200  r
   dw   "delta W"          um             0.02 r
   dl   "delta L"          um            -0.10 r
   l    "Length"           um             10.0 r
   w    "Width"            um              1.0 l
 } {
   l {set l [expr {1.0*$r*($w-$dw)/$rho + $dl}]}
   r {set r [expr {1.0*$rho*($l-$dl)/($w-$dw)}]}
 } -title "integrated circuit resistance calculator"
 

A fixed-rate mortgage payment calculator.
 
 ForCalc mortcalc {
   principal      "Principal"     $   100000    payment
   years          "Years"         y   30        payment
   rate           "Rate"          %   8.00      payment
   payments_year  "Payments/Year" {}  12        payment
   payment        "Payment"       $   733.76    principal
 } {
   payment {
     set a            [expr {1.0e-2*$rate/$payments_year}]
     set N            [expr {1.0*$payments_year*$years}]
     set per_thousand [expr {1.0e+3*$a/(1-pow(1+$a,-$N))}]
     set payment      [expr {1.0e-3*$principal*$per_thousand}]
     set payment      [format "%-10.2f" $payment]
   }
   principal {
     set a            [expr {1.0e-2*$rate/$payments_year}]
     set N            [expr {1.0*$payments_year*$years}]
     set per_thousand [expr {1.0e+3*$a/(1-pow(1+$a,-$N))}]
     set principal    [expr {1.0e+3*$payment/$per_thousand}]
     set principal    [format "%-10.2f" $principal]
   }
 } -title "fixed rate mortgage calculator"

A watering calculator.

 ForCalc watercalc {
   area  "area"             ft^2   1000 t
   gpm   "gallons/minute"   gal/min 1.5 t
   d     "depth"            in      0.5 t
   t     "total time"       hour    1.5 d
 } {
   t {
     set ft3_g [expr {231.0/1728.0}]
     set t [expr {(1.0*$area*(1.0*$d/12))/(60.0*$gpm*$ft3_g)}]
   }
   d {
     set ft3_g [expr {231.0/1728.0}]
     set d [expr {(12.0*($t*60.0)*$gpm*$ft3_g)/$area}]
   }
 } -title "watering calculator"