;+ ; NAME: ; COINTOSS__DEFINE ; ; PURPOSE: ; ; This simple widget program is a demonstration of combining objects and ; widgets in an application. The program simulates flipping a (possibly ; biased) coin and displays the distribution (normalized so that its ; maximum value is 1 for ease of visualization). ; ; The particular distribution here is the conditional probability density ; that one obtains R heads in N tosses of a coin. This is of course the ; binomial distribution. Widget timer events are used to illustrate the ; evolution of the probability density with successive coin tosses. As the ; number of coin tosses increases, the distribution becomes narrower indicating ; more confidence in the mean as the actual bias for the coin. ; ; The idea for this program originated in an example in the math text: ; "Data Analysis: A Bayesian Tutorial", by Devinder Sivia (Oxford University ; Press, 1996). ; ; Users of the program can control the program using the widget controls ; (which call the object's methods) or they can call the object's methods ; directly (see example below). Some example code is included in the ; procedure called COIN_EXAMPLE after the object definition module. ; To run the code, compile this file and type COIN_EXAMPLE. ; ; AUTHOR: ; ; Robert M. Dimeo, Ph.D. ; NIST Center for Neutron Research ; 100 Bureau Drive ; Gaithersburg, MD 20899 ; Phone: (301) 975-8135 ; E-mail: robert.dimeo@nist.gov ; http://www.ncnr.nist.gov/staff/dimeo ; ; CATEGORY: ; ; Objects, widgets ; ; CALLING SEQUENCE: ; ; object = obj_new('COINTOSS', NTOSS = ntoss) ; ; ; INPUT PARAMETERS: ; ; NONE ; ; INPUT KEYWORDS: ; ; NTOSS: number of coin tosses at the outset of the program. ; ; REQUIRED PROGRAMS: ; ; NONE ; ; COMMON BLOCKS: ; ; NONE ; ; RESTRICTIONS ; ; NONE ; ; OBJECT METHODS: ; ; There are no explicitly private object methods in IDL but the ; methods used in this class are divided into PUBLIC and PRIVATE ; to indicate which ones should be used by users who wish to run ; the program from the command line, for instance. Execution of ; the program's controls from the command line is described in the ; example below. ; ; PUBLIC OBJECT PROCEDURE METHODS: ; ; setBias -- Allows user to change value of the coin's bias ( 0 < bias < 1.0 ). ; Can be called with BIAS keyword set to a value or it reads the ; current value of biasField if no keyword value is supplied. ; ; pause -- Pauses evolution of probability density. ; ; resume -- Resumes evolution of probability density if animation running. ; ; reset -- Resets probability density parameters so no coins have been tossed. ; ; startAnimate -- Commences the animation process. ; ; setDuration -- Allows user to change the interval between animation ; frames. Can be called with the DURATION keyword set ; to a value or it reads the current value of durField ; if no keyword value is supplied. ; ; quit -- Destroy widget hierarchy and destroy object. ; ; ; PRIVATE OBJECT PROCEDURE METHODS: ; ; toss_the_coin -- Calculates the new distribution after simulating the toss ; of the coin. ; ; resize -- Allows dynamic resizing of the GUI interface. ; ; animateCoinToss -- Executes object methods after animation has been started. ; ; draw -- Displays current probability density in draw widget. ; ; updateTimer -- Increments the widget timer (for animation). ; ; displayInfo -- Displays information about the widget under the cursor. ; ; EXAMPLE ; ; IDL> o = obj_new('cointoss') ; instantiate the object class ; ; ; The user can manipulate the widget controls or call the methods directly ; ; from the command line as shown below: ; ; IDL> o -> startanimate ; start animating the evolution ; IDL> o -> setbias,bias = 0.9 ; watch the distribution move to the right! ; IDL> o -> pause ; stop evolution temporarily ; IDL> o -> reset ; start over again ; IDL> o -> step ; step through evolution ; IDL> o -> resume ; resume animation of evolution ; IDL> o -> setbias,bias = 0.25 ; watch the distribution move to the left! ; IDL> o -> quit ; kill it ; ; DISCLAIMER ; ; This software is provided as is without any warranty whatsoever. ; Permission to use, copy, modify, and distribute modified or ; unmodified copies is granted, provided this disclaimer ; is included unchanged. ; ; MODIFICATION HISTORY: ; ; Written by Rob Dimeo, September 2, 2002. ; ; RMD-09/04/02 ; Used the suggestion by David Fanning to ; incorporate the object and method into the UVALUE of each widget. ; This results in only a single short event handler that calls a ; general object method (specified in the UVALUE) of the calling ; widget. ; ; RMD-09/06/02 ; Implemented exact expression for mean and standard deviation ; for the distribution for small number of coin flips. Also ; modified procedures (where relevant) so that EVENT is a keyword ; not necessary in any of the methods except the widget resizing. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; function bindist,h,r,n,$ mean = mean,$ st_dev = st_dev ; Calculate the mean and standard deviation of the distribution ; using the Gaussian approximation. if n lt 125 then begin ; IDL can evaluate this for relatively small values ; of n without a problem. We can use the known exact ; expressions for the mean and standard deviation for ; the binomial distribution. dist = (double(h)^r)*(1.0-double(h))^(n-r) dmax = max(dist) dist = dist/dmax area = beta(r+1,n-r+1) mean = 1.0*(r+1.0)/(n+2.0) st_dev = sqrt((1.0*(r+2.0)*(r+1.0)/((n+3.0)*(n+2.0)))-mean^2) endif else begin ; IDL has a rough time for large values of n so ; use Gaussian approximation. mean = 1.0*r/n st_dev = sqrt(mean*(1.0-mean)/n) dist = exp(-0.5*((h-mean)/st_dev)^2) endelse return,dist end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinTossCleanup,tlb widget_control,tlb,get_uvalue = self obj_destroy,self return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::quit,event = event strout = ['Exit the program'] if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin self->displayInfo,strout return endif endif widget_control,self.tlb,/destroy return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::cleanup ptr_free,self.h,self.dist wdelete,self.winPix return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::setproperty,buttonValue = buttonValue if n_elements(buttonValue) ne 0 then $ widget_control,self.goButton,set_value = buttonValue return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::getproperty,buttonValue = buttonValue if arg_present(buttonValue) then $ widget_control,self.goButton,get_value = buttonValue return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::setbias,bias = bias,event = event strout = ['Change the coin bias'] if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin self->displayInfo,strout endif endif if n_elements(bias) eq 0 then begin widget_control,self.biasField,get_value = bias bias = bias[0] bias = (bias gt 1.0) ? 0.5 : bias ; default is 0.5 if user enters invalid bias bias = (bias lt 0.0) ? 0.5 : bias ; default is 0.5 if user enters invalid bias self.bias = bias thisFormat = '(f8.2)' widget_control,self.biasField,$ set_value = strtrim(string(bias,format = thisformat),2) endif else begin bias = (bias gt 1.0) ? 0.5 : bias ; default is 0.5 if user enters invalid bias bias = (bias lt 0.0) ? 0.5 : bias ; default is 0.5 if user enters invalid bias self.bias = bias thisFormat = '(f8.2)' widget_control,self.biasField,$ set_value = strtrim(string(bias,format = thisformat),2) endelse ;endelse return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::setduration,duration = duration,event = event strout = ['Change animation interval'] if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin self->displayInfo,strout endif endif if n_elements(duration) eq 0 then begin widget_control,self.durField,get_value = duration self.duration = float(duration[0]) endif else begin self.duration = duration thisFormat = '(f8.2)' widget_control,self.durField,$ set_value = strtrim(string(duration,format = thisformat),2) endelse return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::toss_the_coin,event = event ; This method simulates tossing a biased coin self.n = self.n + 1L widget_control,self.ntosses,set_value = strtrim(string(self.n),2) r = randomn(s,1,/uniform) if r[0] lt self.bias then self.r = self.r + 1L *self.dist = bindist(*self.h,self.r,self.n,mean = mean,st_dev = st_dev) self.mean = mean self.st_dev = st_dev thisFormat = '(f10.4)' widget_control,self.meanField,set_value = $ strtrim(string(self.mean,format = thisformat),2) widget_control,self.stdevField,set_value = $ strtrim(string(self.st_dev,format = thisformat),2) widget_control,self.nheads,set_value = $ strtrim(string(self.r),2) return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::resize,xsize = xsize,ysize = ysize,event = event ; This method controls how the GUI is resized. Note that we have to consider ; the screen real-estate taken by self.butBase and subtract this from the ; value of xsize and ysize that are passed in here (assumed to be event.x ; and event.y from the BASE widget's event structure) in order to get the ; proper new size for the draw window. if n_elements(event) ne 0 then begin xsize = event.x ysize = event.y endif geom = widget_info(self.butBase,/geometry) widget_control,self.win,draw_xsize = xsize-geom.xsize,draw_ysize = ysize ; Don't forget to resize the pixmap window! wdelete,self.winPix window,/free,/pixmap,xsize = xsize-geom.xsize,ysize = ysize self.winPix = !d.window self->draw return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::draw,event = event xtitle = '!6H' ytitle = '!6Prob(H!3|!6R,N)' title = '!6Coin Bias Distribution' wset,self.winPix plot,*self.h,*self.dist,psym = 0,thick = 2.0,xrange = [0.0,1.0],xstyle = 1,$ yrange = [0.0,1.25],ystyle = 1,xtitle = xtitle,ytitle = ytitle,title = title wset,self.winVis device,copy = [0,0,!d.x_size,!d.y_size,0,0,self.winPix] return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::updateTimer,event = event ; Fire off the timer after a period of self.duration widget_control,self.timerID,timer = self.duration return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::animateEvents,event = event if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin strout = ['Start/pause/resume animation'] self->displayInfo,strout return endif endif self->getproperty,buttonValue = val case val of 'GO': begin self->startanimate end 'PAUSE': begin self->pause end 'RESUME': begin self->resume end else: endcase return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::startAnimate,event = event self->setbias self.loop = 1 self->setproperty,buttonValue = 'PAUSE' self->updateTimer return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::pause,event = event ;if n_params() eq 0 then event = {pseudo_event,x:0.0} if self.loop eq 1 then begin self.loop = 0 self->setproperty,buttonValue = 'RESUME' endif return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::resume,event = event if self.loop eq 0 then begin self->setbias self.loop = 1 self->setproperty,buttonValue = 'PAUSE' self->updateTimer endif return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro cointoss::step,event = event if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin strout = ['Step through evolution'] self->displayInfo,strout return endif endif self->setbias self.loop = 0 self->setproperty,buttonValue = 'RESUME' self->toss_the_coin self->draw return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro cointoss::animateCoinToss,event = event if self.loop eq 1 then begin self->updateTimer self->toss_the_coin self->draw endif return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coinToss::reset,event = event strout = ['Reset evolution'] if n_elements(event) ne 0 then begin if tag_names(event,/structure_name) eq 'WIDGET_TRACKING' then begin self->displayInfo,strout return endif endif npts = 200 hlo = 0.0 & hhi = 1.0 & dh = (hhi-hlo)/(npts-1) h = hlo+dh*dindgen(npts) ones = 1.0+0.0*dindgen(npts) *self.dist = ones self.n = 0L self.r = 0L self.loop = 0 self.mean = 0.5 self.st_dev = 1.0 widget_control,self.goButton,set_value = 'GO' widget_control,self.ntosses,set_value = strtrim(string(self.n),2) widget_control,self.meanField,set_value = $ strtrim(string(self.mean,format = thisformat),2) widget_control,self.stdevField,set_value = $ strtrim(string(self.st_dev,format = thisformat),2) self->draw return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro cointoss::displayInfo,strout widget_control,self.infoField,set_value = strout return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; PRO coinEvents, event ; This is the only event handler necessary for the program. It makes a ; generic method call for an object, the informating begin provided ; by the UVALUE of the calling widgets. ; ; Check for resize events if tag_names(event,/structure_name) eq 'WIDGET_BASE' then begin widget_control,event.top,get_uvalue = self self->resize,event = event return endif ; Handle the methods Widget_Control, event.id, Get_UValue=cmd call_method, cmd.method,cmd.object,event = event end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; function coinToss::init,ntoss = ntoss !except = 0 ; First initialize the data ; Note that the distribution is normalized to a peak of 1.0 (always) npts = 200 hlo = 0.0 & hhi = 1.0 & dh = (hhi-hlo)/(npts-1) h = hlo+dh*dindgen(npts) self.h = ptr_new(h,/no_copy) ones = 1.0+0.0*dindgen(npts) self.dist = ptr_new(ones,/no_copy) self.loop = 0 ; Build the GUI interface smFont = "Times New Roman*16" bsize = 75 tsize = 25 tlb = widget_base(/row,title = 'Coin Toss Bias Distribution Evolution',$ tlb_size_events = 1) self.tlb = tlb xsize = 400 & ysize = 300 butbase = widget_base(tlb,/col) self.butbase = butbase goButton = widget_button(butbase,value = 'GO',font = smFont,xsize = bsize,$ uvalue = {method:'animateevents',object:self,name:'START'},$ /tracking_events) self.goButton = goButton void = widget_button(butbase,value = 'STEP',font = smFont,xsize = bsize,$ uvalue = {object:self,method:'step',name:'STEP'},/tracking_events) void = widget_button(butbase,value = 'RESET',font = smFont,xsize = bsize,$ uvalue = {object:self,method:'reset',name:'RESET'},/tracking_events) void = widget_button(butbase,value = 'QUIT',font = smFont,xsize = bsize,$ uvalue = {object:self,method:'quit',name:'QUIT'},/tracking_events) void = widget_label(butbase,value = 'Animation duration',font = smFont) durField = widget_text(butbase,value = '0.01',font = smFont,$ /editable,xsize = tsize,/tracking_events,$ uvalue = {object:self,method:'setduration',name:'DURATION'}) self.durField = durField void = widget_label(butbase,value = '# Tosses',font = smFont) ntosses = widget_text(butbase,value = '0',font = smFont,xsize = tsize) self.ntosses = ntosses void = widget_label(butbase,value = '# Heads',font = smFont) nheads = widget_text(butbase,value = '0',font = smFont,xsize = tsize) self.nheads = nheads void = widget_label(butbase,value = 'Coin Bias',font = smFont) biasField = widget_text(butbase,/editable,value = '0.5',$ font = smFont,xsize = tsize,/tracking_events,$ uvalue = {object:self,method:'setbias',name:'BIAS'}) self.biasField = biasField void = widget_label(butbase,value = 'Mean ',font = smFont) meanField = widget_text(butbase,value = '0.5',font = smFont,xsize = tsize) self.meanField = meanField void = widget_label(butbase,value = 'Standard Deviation',font = smFont) stdevField = widget_text(butbase,value = '1.0',font = smFont,xsize = tsize) self.stdevField = stdevField void = widget_label(butbase,value = 'Information',font = smFont) infoField = widget_text(butbase,value = '',font = smFont,xsize = tsize,$ ysize = 1) self.infoField = infoField ; Set the window base but don't map it. After realization we'll ; determine how big the control base is and resize the draw window, ; then map the window base. winBase = widget_base(self.tlb,/col,map = 0,$ uvalue = {object:self,method:'animatecointoss',$ name:'animatecointoss'}) self.timerID = winBase win = widget_draw(winBase,xsize = xsize,ysize = ysize) self.win = win widget_control,self.tlb,/realize ; Get the size of the controls base. geom = widget_info(butbase,/geometry) ; If the vertical dimension is larger than the draw widget's vertical ; dimension then resize the draw widget. ysize = ysize > geom.ysize widget_control,win,draw_ysize = ysize widget_control,winBase,map = 1 self->setbias self->setduration widget_control,win,get_value = winVis self.winVis = winVis window,/free,/pixmap,xsize = xsize,ysize = ysize self.winPix = !d.window if n_elements(ntoss) ne 0 then begin if ntoss gt 0 then begin ntoss = ntoss > 1L if ntoss gt 1 then begin self.n = ntoss - 1L toss = randomn(s,self.n,/uniform) dummy = where(toss gt self.bias,r) self.r = r endif self->toss_the_coin endif endif self -> draw widget_control,self.tlb,set_uvalue = self xmanager,'coinToss::init',self.tlb,$ event_handler = 'coinEvents',$ cleanup = 'coinTossCleanup',$ /no_block return,1 end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro cointoss__define ; Definition of the COINTOSS class define = {cointoss, $ tlb:0L, $ butBase:0L, $ winVis:0L, $ winPix:0L, $ infoField:0L, $ ; text field that displays helpful messages durField:0L, $ ; input field for animation duration ntosses:0L, $ ; text field to display number of coin tosses nheads:0L, $ ; text field to display number of heads meanField:0L, $ ; text field for display of mean biasField:0L, $ ; text field for coin bias bias:0.0, $ ; numerical value for coin bias win:0L, $ ; draw window's widget id stdevField:0L, $ ; text field for display of standard deviation timerID:0L,$ ; the top-level base will contain timer information goButton:0L, $ ; go button which will change to pause/resume as ; required by program operation. duration:0.0, $ ; timer interval loop:0, $ ; variable stating if we're evolving the distribution ; 1 -> animating, 0 -> paused mean:0.0, $ ; mean st_dev:0.0, $ ; standard deviation dist:ptr_new(), $ ; probability coin has bias h h:ptr_new(), $ ; independent variable (heads) n:0L, $ ; number of coin tosses r:0L $ ; number of heads in n tosses } return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pro coin_example o = obj_new('cointoss') return end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;