#!/usr/bin/wish -f ## ## SCRIPT: tkSimulatePopulationGrowthLimit.tk ## ## PURPOSE: This Tk GUI script solves the first-order 'nonlinear' ordinary ## differential equation (ODE) for population growth with a ## limit to growth of the population. ## ## To allow for some fairly complex forms of the differential ## equation, this script uses numerical integration --- the ## Runge-Kutta 4th order (RK4) method. ## ## A relatively simple form of the ODE for limited population ## growth is called 'the logistic equation': ## ## D(u) = (r * (1 - u/k)) * u ## ## where D represents the time-derivative operator d/dt, ## and t represents the time independent variable, ## and u represents the population (typically, taken to ## be the population of all mankind on Earth), ## and r is 'the intrinsic growth rate' (which is ## the net growth rate --- the overall birth rate ## minus the overall death rate), ## and k is 'the saturation level' of the population ## (which, in the case of humans on Earth could ## be given a reasonable value by taking the ## amount of habitable surface area of the Earth ## and multiplying by a limiting population density ## on the Earth, which would surely be less than ## 100 people per square mile, considering there ## would probably be many square miles of land ## that would be needed to grow food --- unless ## mankind eventually got essentially all food from ## the seas. In any case, there would probably need ## to be some room allowed to avoid spread of diseases ## and relieve war-like tensions --- and room for ## some facilities such as sewage and garbage disposal ## and processing of fresh water --- and, perhaps, ## some room for energy-generating facilities.) ## ## Note that the factor (r * (1 - u/k)) is the effective growth rate. ## In this form, the rate goes to zero as the population, u, approaches ## the 'saturation level', k. ## ## Note also that the factor (1 - u/k) may under-estimate --- or ## over-estimate --- the rate at which the growth rate goes to zero as ## the population u increases. ## ## It may be that the factor (1 - u/k) should take some other form ## such as (1 - (u/k}^2) or (1 - sqrt(u/k)) --- to more accurately ## model the change in population growth rate as population increases. ## ## These considerations demonstrate why this script uses numerical ## integration to solve the equation. This numerical solution method will ## allow for solving a wide variety of forms of the growth rate equation. ## ##+##################################### ## SOME NOTES ON THE FACTORS 'r' and 'k': ##+##################################### ## ## In the 'logistic equation', we take r and k to be constants, ## but actually they might be functions of t (time) and/or u, ## the population. ## ## For example, with advances in medicine, the death rate might ## be reduced somewhat so that the 'intrinic growth rate', r, ## might increase somewhat over time. ## ## But r would probably reach a limit as crowding increases ## transmission of diseases and as 'labor-saving' devices lead to ## obesity and hence a higher death rate. ## ## Similarly, the population 'saturation level', k, might be affected ## over time as various inventions make it possible to support ## more people per square kilometer (or square mile). In that case, ## k might increase somewhat over time. ## ## On the other hand, as crowding may lead to more human conflict, ## the value of k may decrease as some types of inventions, such ## as more lethal weaponry, result in population reaching a ## 'saturation level' at a lower overall world poplation level. ## ##+####################################### ## ALGEBRAIC SOLUTIONS OF GROWTH EQUATIONS: ##+####################################### ## ## If there were no limit to growth (i.e. 'k' is infinity) ## and if the growth rate 'r' remains constant, then ## the 'logistic' equation is simply ## ## D(u) = r * u ## ## which has the solution ## ## u(t) = u(0) * exp(r*t) ## ## where exp() represents the exponential function, base e. ## ## In other words, growth is exponential --- without limit. ## ## Furthermore, there is a similar exponential algebraic solution to ## the 'logistic' equation when 'k' is finite: ## ## u(t) = u(0)*k / ( u(0) + (k - u(0))*exp(-r*t) ) ## ## As t goes to infinity, this expression becomes ## ## u(t) = u(0)*k / u(0) = k ## ## So, as one might expect, the population approaches the ## 'saturation level' k eventually. ## ## In real life, influences on population growth over time ## are much more variable --- due to outbreaks of disease, ## outbreaks of war, and changes in the birth rate. ## ## We use numerical integration --- rather than the algebraic equation ## above --- so that we can eventually change this script to ## change the form of 'r' and 'k' --- in general, making ## r and k functions of t and/or y. ## ## In compact notation, we may eventually allow for ## r = r(t,y) and k = k(t,y). ## ##+############# ## GUI FEATURES: ## ## The GUI allows the user to enter various values ## for r (net growth rate) and k (saturation level). ## ## The GUI also allows the user to enter parameters ## for the solver process: ## - an initial population level ## - an end-time for the end of the solution process ## (where we typically think of time measured in years, ## when we are modeling human population growth) ## - a time-step, h, for the solution process, ## where h can be a very small fraction of a year. ## ## Following a solution run, the GUI also has a 'Plot' ## button that allows the user to plot a graph of the ## population curve --- drawn on a Tk canvas. ## ##+###################### ## A POSSIBLE ENHANCEMENT: ##+###################### ## ## Eventually we may allow for doing an animation that ## represents the population growth --- by coloring in ## a map drawn on the canvas --- representing the ## living areas on the Earth. ## ## The animation is shown on a rectangular Tk 'canvas' widget ## by using 'create oval' (at population centers, say) ## and 'delete' commands (to draw larger ovals) on the canvas. ## ##+################################################ ## METHOD - MATH MODELING OF THE 'LOGISTIC' EQUATION: ## ## We write the first-order non-linear population equation as ## ## D(u) = r * (1 - u/k) * u ## ## with initial condition u(0)=A, where ## A is an initial population value (of humans, bacteria, ants, ## algae, coral, lions, elephants, manatee, whales, eagles, whatever). ## ## The common way of expressing systems of 'first order' ## differential equations in compact, general form is ## ## D(u) = f(t,u) ## ## where u and f are N-dimensional vectors. ## ## This is a compact way of expressing a system of scalar ## differential equations: ## ## D(u1) = f1(t,u1,...,uN) ## D(u2) = f2(t,u1,...,uN) ## ...... ## D(uN) = fN(t,u1,...,uN) ## ## In the case of the 'logistic; equation, N=1, and ## we can think of solving for the unknown 1-D function vector ## (u1(t)) where the right-hand-side (RHS) of the ## 'logistic' equation above can be thought of as a special ## case of a more general user-specified 1-D function vector ## (f1(t,u1)) where ## ## f1(t,u1) = r * (1 - u1/k) * u1 = r * (u1 - u1^2/k) ## ## We use the popular Runge-Kutta 4th order method (RK4) to ## perform the numerical integration for a user-specified ## time step, h. ## ## We basically use two procs to perform the integration ## steps: ## - a proc to perform the RK4 integration for N=1--- ## giving the values of u1 for each time step ## ## - a proc to evaluate the RHS function: f1 ## for specified values of t,u1. ## ## (In this 'logistic' equation case, r and k are constant, ## and there is no dependence of f1 on t.) ## ## The latter proc is called several times by the former proc ## for each time step. ## ##+################################################################ ## METHOD - PLOTTING THE SOLUTION ON THE TK CANVAS: ## ## After a solution, we have the solution function ## u1 for a sequence of equally-spaced time values. ## ## We use time, t, as the horizontal axis of the plot. ## The time goes from zero to the end-time, tmax, specifie ## by the user. ## ## As the numerical integration proceeds, we keep track ## of the maximum value, umax, reached by u over the integration ## time period. ## ## The solutioon curve is plotted in a rectangular area: ## 0 to tmax in the x-direction ## 0 to umax in the y-direction ## ## A margin is supplied around this rectangular area, ## and two axes are drawn --- a horizontal time-axis ## and a vertical u-axis (population). ## ## Min and max values are indicated on the ends of the 2 axes. ## (Eventually, intermediate tic-marks and labels may also ## be supplied.) ## ## The GUI provides 2 buttons by which the user can specify ## the 2 colors for: ## - the canvas background ## - the plot components (line segments and text labels). ## ## An 'plot' proc performs the plot of the solution when ## the user clicks on a 'Plot' button of the GUI. ## ## This 'plot' proc uses the 'world-coordinates' --- the ## 0 to tmax values and the 0 to umax values --- to draw the ## plot within an area of about tmax by umax in world coordinates. ## ## Actually, we augment this world-coordinates rectangular area ## by providing a margin of about 0.1*tmax and 0.1*umax ## on the left and right and on the top and bottom, respectively, ## of the plot area. ## ## A proc is provided which maps the plot area limits in ## world coordinates --- say ## UpperLeftCorner: ( -0.1*tmax , 1.1*umax ) ## LowerRightCorner: ( +1.1*tmax , -0.1*umax ) ## to the corners of the plot area in pixel coordinates: ## UpperLeftCorner: (0,0) ## LowerRightCorner: (ImageWidthPx,ImageHeightPx) ## ## We may allow the user to determine the ImageWidthPx and ImageHeightPx ## values (in pixels) by letting the user resize the window, which ## will cause the Tk 'canvas' widget to resize --- and we will query ## the current canvas size to get ImageWidthPx and ImageHeightPx. ## ## The 'plot' proc uses 2 procs --- Xwc2px and Ywc2px --- ## to convert the world coordinates of each point --- such as ## the end-points of the axes and the end-points of line-segments ## connecting successive plot points --- to pixel coordinates. ## ## The pixel-coordinates are used in the 'create line' ## and 'create text' commands to draw the plot on the canvas. ## ##+############## ## THE GUI LAYOUT: ## ## Recall that the Tk GUI should allow the user to specify ## r, k, initial-population, ## time-step-size, end-time. ## ## There is to be a 'Solve' button to perform a solution ## when the user is ready to use these parameters. ## ## There are also to be 2 buttons by which to call up an ## RGB-color-selector GUI by which to specify the 2 colors ## for the plot drawing on the canvas. ## ## A 'ShowList' button can be used to show the list of ## solution values --- t(i), u(t(i)) --- in a popup window. ## ## In addition, on the GUI, there is to be a 'Plot' ## button to perform the plot of u versus t, on the canvas. ## ## One way the user can specify these parameters is indicated by ## the following 'sketch' of the GUI: ## ## FRAMEnames ## VVVVVVVVVV ## ------------------------------------------------------------------------------------------ ## Simulate Population Growth subject to a limiting population ## [window title] ## ------------------------------------------------------------------------------------------ ## ## .fRbuttons {Exit} {Help} {Solve} {Show {Reset {Plot} {Plot {Background ## List} Parms} Color} Color} ## ## .fRrhs [ ........ Population growth expression goes here, in a label widget .......... ] ## (This could be an entry widget, someday, to allow for changes in the math expression.) ## ## .fRparms r (percent 'intrinsic' growth rate, per year, say): 0.1__ k (population saturation level, billions, say): 20.0__ ## ## .fRinit Initial Population (billions, say): 7.0____ ## ## .fRtimes Solve End Time (years,say): 100__ Solve Step Size (years, say): 0.1___ ## ## .fRmsg [ .......... Messages go here, in a label widget .......................... ] ## ## .fRcanvas |------------------------------------------------------------------------| ## | | ## | [This area contains a non-scrollable Tk 'canvas' widget | ## | on which the plot is drawn. | ## | | ## | The canvas widget is centered at the top of this area.] | ## | | ## | | ## | | ## | | ## |------------------------------------------------------------------------| ## ## ------------------------------------------------------------------- ## ## In the above sketch of the GUI: ## ## SQUARE BRACKETS indicate a comment (not to be placed on the GUI). ## BRACES indicate a Tk 'button' widget. ## UNDERSCORES indicate a Tk 'entry' widget. ## A COLON indicates that the text before the colon is on a 'label' widget. ## CAPITAL-O indicates a Tk 'radiobutton' widget. ## CAPITAL-X indicates a Tk 'checkbutton' widget (if any). ## Vertical bars (and horizontal hyphens) outline a 'canvas' widget. ## ## If there are scrollbars: ## Less-than and greater-than signs indicate the left and right ends of a horizontal 'scrollbar'. ## Capital-V and Capital-A letters indicate the bottom and top ends of a vertical 'scrollbar'. ## ##+############## ## GUI components: ## ## From the GUI 'sketch' above, it is seen that the GUI consists of about ## ## - 8 button widgets ## - 7 label widgets ## - 5 entry widgets ## - 1 canvas widget with no scrollbars ## - 0 scale widgets (but may use scale widgets in place of some entry widgets) ## - 0 radiobutton widgets ## - 0 checkbutton widgets ## - 0 listbox widgets ## - 0 text widgets ## ##+######################################################################## ## 'CANONICAL' STRUCTURE OF THIS TK CODE: ## ## 0) Set general window & widget parms (win-name, win-position, ## win-color-scheme, fonts, widget-geometry-parms, win-size-control). ## ## 1a) Define ALL frames (and sub-frames, if any). ## 1b) Pack ALL frames and sub-frames. ## ## 2) Define all widgets in the frames. Pack them. ## ## 3) Define keyboard or mouse/touchpad/touch-sensitive-screen action ## BINDINGS, if needed. ## ## 4) Define PROCS, if needed. ## ## 5) Additional GUI INITIALIZATION (typically with one or two of ## the procs), if needed. ## ## ## Some detail about the code structure of this particular script: ## ## 1a) Define ALL frames: ## ## Top-level : '.fRbuttons' ## '.fRrhs' ## '.fRparms' ## '.fRinit' ## '.fRtime' ## '.fRmsg' ## '.fRcanvas' ## No sub-frames. ## ## 1b) Pack ALL frames. ## ## 2) Define all widgets in the frames (and pack them): ## ## - In '.fRbuttons': ## 6 button widgets ('Exit','Help','Solve','Show','Reset','Plot') ## and ## 2 buttons (for setting 2 colors). ## ## - In '.fRrhs': ## 1 label widget (may add an entry widget someday) ## ## - In '.fRparms': ## 2 pairs of 'label' and 'entry' widgets ## ## - In '.fRinit': ## 1 pair of 'label' and 'entry' widgets ## ## - In '.fRtime': ## 2 paris of 'label' and 'entry' widgets ## ## - In '.fRmsg': ## 1 label widget to display messages to the user, such as ## elapsed execution time, as well as a 'calculation in progress' ## msg or other msgs. ## ## - In '.fRcanvas': 1 'canvas' widget ## ## 3) Define BINDINGS: see the BINDINGS section for bindings, if any ## ## 4) Define procs: ## ## - 'solve' - called by the 'Solve' button ## ## - 'runge-kutta-4' - called by the 'solve' proc ## ## - 'deriv' - called by the 'runge-kutta-4' proc ## ## - 'show_list' - called by the 'ShowList' button ## ## - 'plot' - called by the 'Plot' button ## ## - 'setMappingVars_for_px2wc' - called by proc 'plot' ## ## - 'Xpx2wc' - called by proc 'plot' ## - 'Ypx2wc' - called by proc 'plot' ## ## - 'set_plot_color1' - called by the 'PlotColor' button ## ## - 'set_background_color2' - called by the 'BackgroundColor' button ## ## - 'update_color_button' - sets background & foreground color of ## either of the 2 color buttons ## ## - 'advise_user' - called by the 'animate' proc ## ## - 'reset_parms' - called by the 'ResetParms' button ## ## - 'edit_inputs' - called by the'solve' proc ## ## - 'decimal_check' - called by the 'edit_inputs' proc ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button ## ## 5) Additional GUI initialization: ## Set some inital values of parameters ## such as the 2 colors and use a call to ## proc 'reset_parms' to initialize ## the time-zero population ## and end-time and time step-size. ## ##+######################################################################## ## DEVELOPED WITH: ## Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala'). ## ## $ wish ## % puts "$tcl_version $tk_version" ## showed 8.5 8.5 on Ubuntu 9.10 ## after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts. ##+####################################################################### ## MAINTENANCE HISTORY: ## Created by: Blaise Montandon 2016aug05 ## Changed by: Blaise Montandon 2016 ##+####################################################################### ##+####################################################################### ## Set general window parms (win-title,win-position). ##+####################################################################### wm title . "Simulate Population Growth - subject to a limiting population" wm iconname . "Population" wm geometry . +15+30 ##+###################################################### ## Set the color scheme for the window and set the ## background color for the 'trough' in some widgets. ##+###################################################### tk_setPalette "#e0e0e0" set entryBKGD "#f0f0f0" # set radbuttBKGD "#f0f0f0" # set scaleBKGD "#f0f0f0" # set chkbuttBKGD "#f0f0f0" # set listboxBKGD "#f0f0f0" ##+######################################################## ## Use a VARIABLE-WIDTH FONT for label and button widgets. ## ## Use a FIXED-WIDTH FONT for listboxes (and ## entry fields, if any). ##+######################################################## font create fontTEMP_varwidth \ -family {comic sans ms} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_varwidth \ -family {comic sans ms} \ -size -12 \ -weight bold \ -slant roman ## Some other possible (similar) variable width fonts: ## Arial ## Bitstream Vera Sans ## DejaVu Sans ## Droid Sans ## FreeSans ## Liberation Sans ## Nimbus Sans L ## Trebuchet MS ## Verdana font create fontTEMP_fixedwidth \ -family {liberation mono} \ -size -14 \ -weight bold \ -slant roman font create fontTEMP_SMALL_fixedwidth \ -family {liberation mono} \ -size -12 \ -weight bold \ -slant roman ## Some other possible fixed width fonts (esp. on Linux): ## Andale Mono ## Bitstream Vera Sans Mono ## Courier 10 Pitch ## DejaVu Sans Mono ## Droid Sans Mono ## FreeMono ## Nimbus Mono L ## TlwgMono ##+########################################################### ## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS. ## (e.g. width and height of canvas, and padding for Buttons) ##+########################################################### ## BUTTON geom parameters: set PADXpx_button 0 set PADYpx_button 0 set BDwidthPx_button 2 set RELIEF_button "raised" ## LABEL geom parameters: set PADXpx_label 0 set PADYpx_label 0 set BDwidthPx_label 2 # set RELIEF_label "ridge" # set RELIEF_label "raised" set RELIEF_label "flat" ## ENTRY widget geom settings: set BDwidthPx_entry 2 set ParmEntryWidthChars 8 ## RADIOBUTTON widget geom settings: ## (de-activated, for now) if {0} { set BDwidthPx_radbutt 2 # set RELIEF_radbutt "ridge" set RELIEF_radbutt "raised" } ## SCALE geom parameters: ## (de-activated, for now) if {0} { set BDwidthPx_scale 2 # set initScaleLengthPx 100 set scaleThicknessPx 10 set scaleRepeatDelayMillisecs 800 } ## CANVAS geom parameters: set initCanWidthPx 300 set initCanHeightPx 300 set minCanHeightPx 24 # set BDwidthPx_canvas 2 set BDwidthPx_canvas 0 ##+################################################################### ## Set a MINSIZE of the window. ## ## For width, allow for the minwidth of the '.fRbuttons' frame: ## about 8 buttons (Exit,Help,Solve,Show,Reset,Plot,Color,Color). ## We want to at least be able to see the Exit button. ## ## For height, allow ## 2 chars high for the '.fRbuttons' frame, ## 1 char high for the '.fRrhs' frame, ## 1 char high for the '.fRparms' frame, ## 1 char high for the '.fRinit' frame, ## 1 char high for the '.fRtime' frame, ## 2 chars high for the '.fRmsg' frame, ## 24 pixels high for the '.fRcanvas' frame. ##+################################################################### ## MIN WIDTH: set minWinWidthPx [font measure fontTEMP_varwidth \ "Exit Help Solve ShowList ResetParms Plot Color Color"] ## Add some pixels to account for right-left-side window decoration ## (about 8 pixels), about 8 x 4 pixels/widget for borders/padding for ## 8 widgets. set minWinWidthPx [expr {40 + $minWinWidthPx}] ## MIN HEIGHT --- allow ## 2 chars high for 'fRbuttons' ## 1 char high for 'fRrhs' ## 1 char high for 'fRparms' ## 1 char high for 'fRinit' ## 1 char high for 'fRtime' ## 2 chars high for 'fRmsg' ## 24 pixels high for 'fRcanvas' set CharHeightPx [font metrics fontTEMP_varwidth -linespace] set minWinHeightPx [expr {24 + (8 * $CharHeightPx)}] ## Add about 28 pixels for top-bottom window decoration, ## about 7x4 pixels for each of the 7 stacked frames and their ## widgets (their borders/padding). set minWinHeightPx [expr {$minWinHeightPx + 56}] ## FOR TESTING: # puts "minWinWidthPx = $minWinWidthPx" # puts "minWinHeightPx = $minWinHeightPx" wm minsize . $minWinWidthPx $minWinHeightPx ## We allow the window to be resizable and we pack the canvas with ## '-fill both -expand 1' so that the canvas can be enlarged by enlarging ## the window. ## If you want to make the window un-resizable, ## you can use the following statement. # wm resizable . 0 0 ##+#################################################################### ## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI. ## NOTE: This can aid INTERNATIONALIZATION. This array can ## be set according to a nation/region parameter. ##+#################################################################### ## if { "$VARlocale" == "en"} ## For widgets in 'fRbuttons' frame: set aRtext(buttonEXIT) "Exit" set aRtext(buttonHELP) "Help" set aRtext(buttonSOLVE) "Solve" set aRtext(buttonSHOW) "ShowList" set aRtext(buttonRESET) "ResetParms" set aRtext(buttonPLOT) "Plot" set aRtext(buttonCOLOR1) "Plot Color" set aRtext(buttonCOLOR2) "Background Color" ## For widgets in 'fRrhs' frame: set aRtext(labelRHS) \ "Growth rate expression to be integrated: (r * (1 - u/k)) * u \ See 'Help'." ## For widgets in 'fRparms' frame: set aRtext(labelR) "r (percent 'intrinsic' growth rate):" set aRtext(labelTIMEUNITS) " per " ## For widgets in 'fRinit' frame: set aRtext(labelINIT1) "Initial Population:" set aRtext(labelK) " k (population saturation level):" set aRtext(labelPOPUNITS) " in " ## For widgets in 'fRtime' frame: set aRtext(labelENDTIME) "Solve End Time :" set aRtext(labelSTEPSIZE) " Solve Step Size :" ## For some calls to the 'advise_user' proc: set aRtext(SOLVEmsg) "*** Click 'Solve' when ready to do the solve ***" set aRtext(PLOTmsg) "*** Click 'Plot' when ready to generate a plot ***" ## END OF if { "$VARlocale" == "en"} ##+################################################################ ## DEFINE *ALL* THE FRAMES: ## ## Top-level : '.fRbuttons' '.fRrhs' '.fRparms' ## '.fRinit' '.fRtime' '.fRmsg' '.fRcanvas' ##+################################################################ ## FOR TESTING change 0 to 1: ## (Example1: To see appearance of frames when borders are drawn.) ## (Example2: To see sizes of frames for various '-fill' options.) ## (Example3: To see how frames expand as window is resized.) if {0} { set RELIEF_frame raised set BDwidthPx_frame 2 } else { set RELIEF_frame flat set BDwidthPx_frame 0 } frame .fRbuttons -relief $RELIEF_frame -borderwidth $BDwidthPx_frame # frame .fRrhs -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRrhs -relief raised -borderwidth 2 frame .fRparms -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRinit -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRtime -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRmsg -relief raised -borderwidth 2 # frame .fRcanvas -relief $RELIEF_frame -borderwidth $BDwidthPx_frame frame .fRcanvas -relief raised -borderwidth 2 ##+############################## ## PACK the top-level FRAMES. ##+############################## pack .fRbuttons \ .fRrhs \ .fRparms \ .fRinit \ .fRtime \ .fRmsg \ -side top \ -anchor nw \ -fill x \ -expand 0 pack .fRcanvas \ -side top \ -anchor n \ -fill both \ -expand 1 ##+######################################################### ## OK. Now we are ready to define the widgets in the frames. ##+######################################################### ##+##################################################################### ## In the '.fRbuttons' FRAME --- DEFINE ## - 'Exit','Help','Solve','ShowList','ResetParms', 'Plot' buttons ## and ## - 2 buttons ( to specify colors). ## Then PACK all these widgets. ##+##################################################################### button .fRbuttons.buttEXIT \ -text "$aRtext(buttonEXIT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -command {exit} button .fRbuttons.buttHELP \ -text "$aRtext(buttonHELP)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -command {popup_msgVarWithScroll .topHelp "$HELPtext" +10+10} button .fRbuttons.buttSOLVE \ -text "$aRtext(buttonSOLVE)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -command {solve} button .fRbuttons.buttSHOW \ -text "$aRtext(buttonSHOW)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -state disabled \ -command {show_list} button .fRbuttons.buttRESET \ -text "$aRtext(buttonRESET)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -command {reset_parms} button .fRbuttons.buttPLOT \ -text "$aRtext(buttonPLOT)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -state disabled \ -command {plot} button .fRbuttons.buttCOLOR1 \ -text "$aRtext(buttonCOLOR1)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_plot_color1" button .fRbuttons.buttCOLOR2 \ -text "$aRtext(buttonCOLOR2)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief raised \ -bd $BDwidthPx_button \ -command "set_background_color2" ##+########################################### ## Pack the widgets in the 'fRbuttons' frame. ##+########################################### pack .fRbuttons.buttEXIT \ .fRbuttons.buttHELP \ .fRbuttons.buttSOLVE \ .fRbuttons.buttSHOW \ .fRbuttons.buttRESET \ .fRbuttons.buttPLOT \ .fRbuttons.buttCOLOR1 \ .fRbuttons.buttCOLOR2 \ -side left \ -anchor w \ -fill none \ -expand 0 ##+################################################################## ## In the '.fRrhs' FRAME ---- DEFINE 1 LABEL widget. ## Then PACK all these widgets. ##+################################################################## label .fRrhs.labelRHS \ -text "$aRtext(labelRHS)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bg "#66ff66" \ -bd $BDwidthPx_label pack .fRrhs.labelRHS \ -side left \ -anchor w \ -fill x \ -expand 1 ##+################################################################## ## In the '.fRparms' FRAME ---- DEFINE 2 pairs of ## LABEL-and-ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## ## FOR 'INTRINSIC' POPULATION GROWTH RATE: label .fRparms.labelR \ -text "$aRtext(labelR)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYr "0.1" entry .fRparms.entryR \ -textvariable ENTRYr \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR TIME UNITS: label .fRparms.labelTIMEUNITS \ -text "$aRtext(labelTIMEUNITS)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYtimeunits "years" entry .fRparms.entryTIMEUNITS \ -textvariable ENTRYtimeunits \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ##+##################################### ## PACK the widgets in frame '.fRparms'. ##+##################################### pack .fRparms.labelR \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRparms.entryR \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRparms.labelTIMEUNITS \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRparms.entryTIMEUNITS \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRinit' FRAME ---- DEFINE 3 pairs of ## LABEL and ENTRY widgets. ## Then PACK all these widgets. ##+################################################################## label .fRinit.labelINIT1 \ -text "$aRtext(labelINIT1)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYinit1 "7.0" entry .fRinit.entryINIT1 \ -textvariable ENTRYinit1 \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR POPULATION SATURATION LEVEL: label .fRinit.labelK \ -text "$aRtext(labelK)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYk "20.0" entry .fRinit.entryK \ -textvariable ENTRYk \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR POPULATION UNITS: label .fRinit.labelPOPUNITS \ -text "$aRtext(labelPOPUNITS)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYpopunits "billions" entry .fRinit.entryPOPUNITS \ -textvariable ENTRYpopunits \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ##+##################################### ## PACK the widgets in frame '.fRinit'. ##+##################################### pack .fRinit.labelINIT1 \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRinit.entryINIT1 \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRinit.labelK \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRinit.entryK \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRinit.labelPOPUNITS \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRinit.entryPOPUNITS \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRtime' FRAME ---- DEFINE 2 pairs of ## LABEL and ENTRY widgets. ## Then PACK all these widgets. ##+################################################################### label .fRtime.labelENDTIME \ -text "$aRtext(labelENDTIME)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYendtime "200.0" entry .fRtime.entryENDTIME \ -textvariable ENTRYendtime \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ## FOR INTEGRATION (solver) STEPSIZE: label .fRtime.labelSTEPSIZE \ -text "$aRtext(labelSTEPSIZE)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief $RELIEF_label \ -bd $BDwidthPx_label ## We initialize this widget var (and others) ## in the GUI initialization section at the ## bottom of this script. ## # set ENTRYstepsize "0.1" entry .fRtime.entrySTEPSIZE \ -textvariable ENTRYstepsize \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width $ParmEntryWidthChars \ -relief sunken \ -bd $BDwidthPx_entry ##+##################################### ## PACK the widgets in frame '.fRtime'. ##+##################################### pack .fRtime.labelENDTIME \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRtime.entryENDTIME \ -side left \ -anchor w \ -fill x \ -expand 0 pack .fRtime.labelSTEPSIZE \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRtime.entrySTEPSIZE \ -side left \ -anchor w \ -fill x \ -expand 0 ##+################################################################## ## In the '.fRmsg' FRAME ---- DEFINE-and-PACK 1 LABEL widget. ##+################################################################## label .fRmsg.labelMSG \ -text "" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -relief flat \ -bg "#ff6666" \ -bd $BDwidthPx_button pack .fRmsg.labelMSG \ -side left \ -anchor w \ -fill x \ -expand 1 ##+###################################################### ## In the '.fRcanvas' FRAME - DEFINE the 'canvas' widget ## --- no scrollbars, for now. ## Then PACK the widget(s). ##+###################################################### ## We set highlightthickness & borderwidth of the canvas to ## zero, as suggested on page 558, Chapter 37, 'The Canvas ## Widget', in the 4th edition of the book 'Practical ## Programming in Tcl and Tk'. ##+###################################################### canvas .fRcanvas.can \ -width $initCanWidthPx \ -height $initCanHeightPx \ -relief flat \ -highlightthickness 0 \ -borderwidth 0 # -yscrollcommand ".fRcanvas.scrolly set" \ # -xscrollcommand ".fRcanvas.scrollx set" # scrollbar .fRcanvas.scrolly \ # -orient vertical \ # -command ".fRcanvas.can yview" # scrollbar .fRcanvas.scrollx \ # -orient horizontal \ # -command ".fRcanvas.can xview" ##+####################################################### ## PACK the widgets in frame '.fRcanvas'. ## (Skip the scrollbar pack statements, for now.) ##+####################################################### if {0} { ##+######################################################## ## NOTE: ## NEED TO PACK THE SCROLLBARS BEFORE THE CANVAS WIDGET. ## OTHERWISE THE CANVAS WIDGET TAKES ALL THE FRAME SPACE. ##+####################################################### pack .fRcanvas.scrolly \ -side right \ -anchor e \ -fill y \ -expand 0 pack .fRcanvas.scrollx \ -side bottom \ -anchor s \ -fill x \ -expand 0 ##+############################################################## ## !!!NEED TO USE '-expand 0' FOR THE X AND Y SCROLLBARS, so that ## the canvas is allowed to fill the remaining frame-space nicely ## --- without a gap between the canvas and its scrollbars. ##+############################################################## } ## END OF if {0} pack .fRcanvas.can \ -side top \ -anchor n \ -fill both \ -expand 1 ## We don't let the canvas expand, because we want ## to make it a square with colored background --- ## centered at the bottom of the GUI. # -fill both \ # -expand 1 ##+######################################## ## END OF the DEFINITION OF THE GUI WIDGETS ##+######################################## ##+############################### ## BINDINGS SECTION: ##+############################### ##+################################################################### ## Remind user to click 'Solve' after changing a math expression ## parameter or an initial condition or a time parameter. ##+################################################################### bind .fRparms.entryR \ {advise_user "$aRtext(SOLVEmsg) with new 'r' value."} bind .fRinit.entryK \ {advise_user "$aRtext(SOLVEmsg) with new 'k' value."} bind .fRinit.entryINIT1 \ {advise_user "$aRtext(SOLVEmsg) with new initial population value."} bind .fRtime.entryENDTIME \ {advise_user "$aRtext(SOLVEmsg) with new END-TIME."} bind .fRtime.entrySTEPSIZE \ {advise_user "$aRtext(SOLVEmsg) with new STEP-SIZE."} ##+################################################################ ## Remind user to click 'Plot' button after changing ## a plot-related parameter. ##+################################################################ # bind .fRparms.entryIMGSIZE \ # {advise_user "$aRtext(PLOTmsg) with new image size value."} bind .fRbuttons.buttCOLOR1 \ {advise_user "$aRtext(PLOTmsg) with new plot lines,text color."} bind .fRbuttons.buttCOLOR2 \ {advise_user "$aRtext(PLOTmsg) with new plot background color."} ##+###################################################################### ## PROCS SECTION: ## ## - 'solve' - called by the 'Solve' button. ## ## - 'runge-kutta-4' - called by the 'solve' proc. ## ## - 'deriv' - called by the 'runge-kutta-4' proc. ## ## - 'show_list' - called by the 'ShowList' button ## ## - 'plot' - called by the 'Plot' button. ## ## - 'setMappingVars_for_px2wc' - called by proc 'animate'. ## ## - 'Xpx2wc' - called by proc 'animate'. ## - 'Ypx2wc' - called by proc 'animate'. ## ## - 'set_tic_interval' - called by proc 'plot'. ## ## - 'set_tics_list' - called by proc 'plot'. ## ## - 'set_plot_color1' - called by the 'PlotColor' button. ## ## - 'set_background_color2' - called by the 'BackgroundColor' button. ## ## - 'update_color_button' - sets background & foreground color of ## either of the 2 color buttons. ## ## - 'advise_user' - called by the 'solve' proc and bindings. ## ## - 'reset_parms' - called by the 'ResetParms' button and in the ## 'Additional GUI Initialization' section, ## to initialize the parms. ## ## - 'edit_inputs' - called by 'solve' proc ## ## - 'decimal_check' - called by 'edit_inputs' proc ## ## - 'popup_msgVarWithScroll' - called by the 'Help' button. ## ##+####################################################################### ##+##################################################################### ## PROC: 'solve' ## ## PURPOSE: ## Peforms the integration of the first order ODE: ## ## D(u1) = r * (1 - u1 / k) * u1 ## ## METHOD: ## First do some intialization of variables. Then, in a loop, ## we use the 'runge-kutta-4' proc to get the output u1 for each ## time step. The 'deriv' proc provides the derivative ## (rate of change) evaluations needed for the Runge-Kutta-order-4 ## numerical integration process. ## ## The output is saved in two global arrays aRtime, aRu1 ## which are indexed by integers representing the time-step number. ## ## CALLED BY: By clicking on the 'Solve' button. ##+#################################################################### proc solve {} { ## FOR TESTING: (dummy out this proc) # return global ENTRYr ENTRYk ENTRYinit1 ENTRYtimeunits \ ENTRYendtime ENTRYstepsize EDITcode \ aRtime aRu1 u1max Nsteps ####################################################### ## Clear the canvas of any previous plot elements. ####################################################### .fRcanvas.can delete all update ######################################################## ## Check the entry field inputs. ######################################################## edit_inputs if {$EDITcode > 0} {return} ################################################ ## Set the current time, for determining elapsed ## time for building each 'photo' image. ################################################ set t0 [clock milliseconds] ##################################################################### ## We use local variables 'time' and 'u1' to hold the current ## values of the independent variable and the dependent variable. ## ## We initialize those 2 local variables here. ##################################################################### set time 0.0 set u1 $ENTRYinit1 #################################################################### ## Initialize the 2 arrays in which we store the solution results. #################################################################### set Nsteps 0 set aRtime($Nsteps) 0.0 set aRu1($Nsteps) $u1 set u1max 0.0 #################################################################### ## Start the solution loop. ## Set some variables to be used to determine ## when the time steps get to ENDtime. #################################################################### set ENDtime [expr {double($ENTRYendtime)}] set h [expr {double($ENTRYstepsize)}] set timeTolerance [expr {0.0001 * $h}] while {1} { ################################################# ## Use runge-kutta-4 to get the next value of u1. ################################################# ## Set the u1 variable for input to runge-kutta-4 ## by using the input from the previous step. ################################################# set u1 $aRu1($Nsteps) set LISTu1out [runge-kutta-4 $time $h $u1] #################################################### ## Save the u1 output into the next u1 array element. #################################################### incr Nsteps set aRu1($Nsteps) [lindex $LISTu1out 0] ###################################### ## Reset u1max if this u1 was greater. ###################################### if {$aRu1($Nsteps) > $u1max} {set u1max $aRu1($Nsteps)} ####################################### ## Set the value of time for this step ## and store it in the time array. ## This time value is also the time input ## for runge-kutta-4 in the next step. ####################################### set time [expr {$time + $h}] set aRtime($Nsteps) $time ########################################################### ## If the time of this time-step is within ## 'timeTolerance' of ENDtime, break out of this while loop. ########################################################### if {[expr {abs($time - $ENDtime)}] < $timeTolerance } {break} } ## END OF solution loop ######################################################## ## Show the user the elapsed-time for this solution run. ######################################################## set solvetime [expr {[clock milliseconds] - $t0}] set simtime [format "%.2f" $time] advise_user "\ ** SOLVE DONE: $solvetime millisecs elapsed ; SIMULATED TIME: $simtime ${ENTRYtimeunits}s ; Steps: $Nsteps **" ######################################################## ## Activate the initially disabled 'ShowList' button ## and the 'Plot' button. ######################################################## .fRbuttons.buttSHOW configure -state normal .fRbuttons.buttPLOT configure -state normal } ## END OF PROC 'solve' ##+##################################################################### ## PROC: 'show_list' ## ## PURPOSE: ## Creates a columnar list of results of the form: ## t u1 ## where ## t is the time, from 0.0 to ENTRYendtime ## u1 the population ## ## Puts the list in a text variable, VARlist, and ## shows the list in a popup window by using the ## 'popup_msgVarWithScroll' proc. ## ## METHOD: Uses the 2 arrays created by the 'solve' proc. ## ## CALLED BY: By clicking on the 'ShowList' button. ##+#################################################################### proc show_list {} { ## FOR TESTING: (dummy out this proc) # return global ENTRYr ENTRYk ENTRYinit1 \ ENTRYtimeunits ENTRYpopunits \ ENTRYendtime ENTRYstepsize \ aRtime aRu1 Nsteps ################################## ## Create the heading for the list. ################################## set tunitsFMTED "[string trim $ENTRYtimeunits]s" set punitsFMTED [string trim $ENTRYpopunits] set VARlist \ "Simulation of Population Growth - subject to a limiting population r (percent 'intrinsic' growth rate) = $ENTRYr per $ENTRYtimeunits k (population saturation level) = $ENTRYk in $ENTRYpopunits Initial population: $ENTRYinit1 in $ENTRYpopunits Start time: 0.0 $tunitsFMTED End time : $ENTRYendtime $tunitsFMTED Solver time step: $ENTRYstepsize $tunitsFMTED Number of time steps: $Nsteps Time Population Time Step Number ($tunitsFMTED) ($punitsFMTED) ---------- -------------------- ------------------- " ######################################################## ## In a loop over the integer-index of the 2 'aR' arrays, ## put the 2 numbers in each row of the list. ######################################################## set idx 0 while {$idx <= $Nsteps} { set timeFMTED [format "%10.3f" $aRtime($idx)] set u1FMTED [format "%20.3f" $aRu1($idx)] set idxFMTED [format "%20.0d" $idx] append VARlist "$timeFMTED $u1FMTED $idxFMTED \n" incr idx } ## END OF while loop ####################################### ## Add usage info to bottom of the list. ####################################### append VARlist " --- With a mouse, you can highlight the lines above and paste them into a text editor window and save that text into a file. Then you can use that text file in a plot utility, such as 'gnuplot', to plot the population against time. " ############################## ## Show the list. ############################## popup_msgVarWithScroll .topList "$VARlist" +20+30 } ## END OF PROC 'show_list' ##+############################################################## ## PROC: 'runge-kutta-4' ## ## PURPOSE: Let u(t) be a vector function of independent variable ## t (a scalar) --- in this case a 1-dimensional vector ## function composed of 1 scalar functions u1(t). ## ## Let f(t,u) be a vector function of t and vector u --- ## in this case a 1-dimensional vector function ## composed of 1 functions f1(t,u1). ## ## For a given value of t and h (a step-size of t), ## this proc is TO RETURN a floating-point value ## corresponding to using a Runge-Kutta 4th order ## method to evaluate u1(t+h). ## ##+###### ## METHOD: ## ## For a first-order differential equation of the form ## ## D(u) = f(t,u) where D is the d/dt derivative operator ## ## and where t is the independent variable and u is a vector ## function of t, ## ## the Runge-Kutta 4th order method (RK4) is often written in the form ## ## u(t+h) = u(t) + (Ka + 2 * Kb + 2 * Kc + Kd) / 6 ## where ## Ka = h * f (t , u(t) ) ## Kb = h * f (t + (h/2), u(t) + (Ka / 2) ) ## Kc = h * f (t + (h/2), u(t) + (Kb / 2) ) ## Kd = h * f (t + h, u(t) + Kc ) ## where ## f and u and the K's are vectors (2-dimensional in this ## oscillating single pendulum case). ## ##+########################################### ## An alternate way of writing these equations: ## (essentially factoring out the factor 'h' from the K eqns) ## ## u(t+h) = u(t) + (h/6) * (Da + 2*Db + 2*Dc + Dd) ## where ## Da = f (t , u(t) ) ## Db = f (t + (h/2), u(t) + (h/2)*Da ) ## Dc = f (t + (h/2), u(t) + (h/2)*Db ) ## Dd = f (t + h, u(t) + h*Dc ) ## ##+############### ## Scalar eqns instead of vector eqns: ## ## Let us rewrite these vector equations as ## scalar equations involving scalar functions --- u1,f1, ## Da1,Db1,Dc1,Dd1. ## ## Let us rewrite them in an order in which they would need ## to be computed. ## ## Da1 = f1 (t , u1(t) ) ## ## Db1 = f1 (t + (h/2), u1(t) + (h/2)*Da1 ) ## ## Dc1 = f1 (t + (h/2), u1(t) + (h/2)*Db1 ) ## ## Dd1 = f1 (t + h, u1(t) + h*Dc1) ## ## u1(t+h) = u1(t) + (h/6) * (Da1 + 2*Db1 + 2*Dc1 + Dd1) ## ## This proc returns u1(t+h) for given t and h. ## ## The value of f1 for given u1 --- and t --- ## are given by calls to proc 'deriv' --- before each pair ## of D equations. ## ## CALLED BY: the 'solve' proc. ##+############################################################## proc runge-kutta-4 {t h u1} { ## FOR TESTING: (dummy out this proc) # return ###################################################### ## Use the 'deriv' proc to get deriv values f1 ## for the given t,u1. ###################################################### set LISTf1 [deriv $t $u1] set Da1 [lindex $LISTf1 0] set h2 [expr { $h / 2.0 }] set t_h2 [expr { $t + $h2 }] set u1_hD [expr {$u1 + ($h2 * $Da1)}] set LISTf1 [deriv $t_h2 $u1_hD] set Db1 [lindex $LISTf1 0] set u1_hD [expr {$u1 + ($h2 * $Db1)}] set LISTf1 [deriv $t_h2 $u1_hD] set Dc1 [lindex $LISTf1 0] set t_h [expr { $t + $h}] set u1_hD [expr {$u1 + ($h * $Dc1)}] set LISTf1 [deriv $t_h $u1_hD] set Dd1 [lindex $LISTf1 0] set h6 [expr { $h / 6.0 }] set u1out [expr {$u1 + ( $h6 * ($Da1 + (2.0 * ($Db1 + $Dc1)) + $Dd1) )}] return "$u1out" } ## END OF PROC 'runge-kutta-4' ##+############################################################## ## PROC: 'deriv' ## ## PURPOSE: For a given independent variable, t, and for a value ## of a vector u (in this case, a 1-dimensional vector ## with value u1), this proc is ## TO RETURN a floating-point value for a ## vector function f(t,u) (in this case, for a ## 1-dimensional vector f). ## ## In other words, for 1 scalar function ## f1(t,u1) ## this proc returns a floating point value ## determined by evaluating f1 for the ## given floating point values t and u1. ## ## METHOD: In this application, ## f1(t,u1) = r * (1 - u1/k) * u1 = r * (u1 - u1^2/k) ## ## CALLED BY: the 'runge-kutta-4' proc. ##+############################################################## proc deriv {t u1} { ## FOR TESTING: (dummy out this proc) # return global ENTRYr ENTRYk ## If 'r' is a 'raw' fraction, use this form of the derivative. # set f1 [expr { $ENTRYr * (1.0 - ($u1 / $ENTRYk)) * $u1 }] ## If 'r' is entered as a percent, instead of a raw fraction, ## we need to divide r by 100. set f1 [expr { ($ENTRYr / 100.0) * (1.0 - ($u1 / $ENTRYk)) * $u1 }] ## NOTE: ## t is not used in these functions, but we are ## prepared to allow adding a time-dependent function ## such as A * sin(k * t) return "$f1" } ## END OF PROC 'deriv' ##+##################################################################### ## PROC: 'plot' ## ## PURPOSE: ## Draws a plot of the population solution --- using ## 'create line' statements to draw line-segments ## between the successive plot points. ## ## Also draws horizontal and vertical axes --- and text and ## numeric labels. ## ## CALLED BY: a click on the 'Plot' button. ##+##################################################################### proc plot {} { ## FOR TESTING: (dummy out this proc) # return global aRtime aRu1 u1max Nsteps ENTRYr ENTRYk \ ENTRYtimeunits ENTRYpopunits \ ENTRYstepsize ENTRYendtime EDITcode \ COLOR1r COLOR1g COLOR1b COLOR1hex \ COLOR2r COLOR2g COLOR2b COLOR2hex ############################################################ ## If the Nsteps variable does not exist (no solver run yet), ## do not perform a plot. ############################################################ if {[info exists Nsteps] == 0} {return} ######################################################## ## Check the entry field inputs. ######################################################## edit_inputs if {$EDITcode > 0} {return} ################################################################# ## Clear the message area --- replace with a plot in-progress msg. ## NOT NEEDED? Plot goes almost immediately(?). ################################################################# # advise_user "* Plot in progress. *" ############################################################## ## Reconfigure the canvas into a rectangular image area that ## we are going to use --- from user-specified image size ## in variables ImageWidthPx and ImageHeightPx. ## NOT USED. Maybe someday. ############################################################## # .fRcanvas.can configure -width $ImageWidthPx # .fRcanvas.can configure -height $ImageHeightPx ############################################################## ## Query the current canvas size to get the image size ## for the plot. ############################################################## set ImageWidthPx [winfo width .fRcanvas.can] set ImageHeightPx [winfo height .fRcanvas.can] ############################################################ ## Set the background color of the canvas area from the ## current background color variable. ############################################################ .fRcanvas.can config -bg $COLOR2hex ############################################################# ## We use a 'wm geometry' command to get the window (and the ## canvas widget) to resize appropriately --- even after the ## user has manually resized the top window. ## ## Reference: wiki.tcl.tk/10720 and wiki.tcl.tk/44 ## and page 237 of Ousterhout's book 'Tcl and the Tk Toolkit': ## "If you would like to restore a window to its natural ## size, you can invoke 'wm geometry' with an empty ## geometry string." ## ## NOT NEEDED? Maybe someday. ############################################################# # wm geometry . {} ################################################################ ## Set the variables for converting pixels to world-coords. ## This is in case the user changed the image dimensions. ################################################################ ## Recall input vars for proc 'setMappingVars_for_px2wc' are: ## xORy,ULwcX,ULwcY,ULpxX,ULpxY,LRwcX,LRwcY,LRpxX,LRpxY ## ## In order to allow some margin: ## ## Map the plot area limits in world coordinates --- say ## UpperLeftCorner: ( -0.1*tmax , 1.1*umax ) ## LowerRightCorner: ( +1.1*tmax , -0.1*umax ) ## to the corners of the plot area in pixel coordinates: ## UpperLeftCorner: (0,0) ## LowerRightCorner: (ImageWidthPx,ImageHeightPx) ## ## See code in 'setMappingVars_for_px2wc' for details. ######################################################################## set MARGINfactor 0.1 set XwcUL [expr {-$MARGINfactor * $aRtime($Nsteps)}] set YwcUL [expr {(1.0 + $MARGINfactor) * $u1max}] set XwcLR [expr {(1.0 + $MARGINfactor) * $aRtime($Nsteps)}] set YwcLR [expr {-$MARGINfactor * $u1max}] setMappingVars_for_px2wc xy $XwcUL $YwcUL 0 0 $XwcLR $YwcLR $ImageWidthPx $ImageHeightPx ####################################################### ## Clear the canvas of any previous plot elements. ####################################################### .fRcanvas.can delete all ################################################################### ## Draw the x-axis --- the time axis. ################################################################### set lineWidthPx 2 set x1 0.0 set y1 0.0 set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] set x2 $aRtime($Nsteps) set y2 0.0 set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] .fRcanvas.can create line $x1Px $y1Px $x2Px $y2Px \ -tags TAGplot -fill "$COLOR1hex" -width $lineWidthPx ########################################################## ## Put a 0.0 label at the origin of the plot. ########################################################## .fRcanvas.can create text $x1Px $y1Px -fill "$COLOR1hex" \ -tags TAGtext -text "0.0" -anchor ne -font fontTEMP_fixedwidth ############################################################## ## Put a max-time numeric label at the right end of the x-axis. ############################################################## set tmaxFMTED [format "%6.2f" $x2] .fRcanvas.can create text $x2Px $y2Px -fill "$COLOR1hex" \ -tags TAGtext -text "$tmaxFMTED" -anchor nw -font fontTEMP_fixedwidth ############################################################## ## Put a 'Time' label near the middle of the x-axis. ############################################################## set xmid [expr {$x2 / 2.0}] set xmidPx [Xwc2px $xmid] .fRcanvas.can create text $xmidPx $y2Px -fill "$COLOR1hex" \ -tags TAGtext -text "Time (${ENTRYtimeunits}s)" \ -anchor n -font fontTEMP_fixedwidth ############################################################# ## Put tic-marks on the x-axis. ############################################################# set ticInterval [set_tic_interval 0.0 $aRtime($Nsteps)] set LISTtics [set_tics_list 0.0 $aRtime($Nsteps) $ticInterval] set y 0.0 set yPx [Ywc2px $y] foreach tic $LISTtics { set xPx [Xwc2px $tic] ## Draw each x-tic about 3 pixels above the x-axis. .fRcanvas.can create line $xPx $yPx $xPx [expr {$yPx + 3}] \ -tags TAGplot -fill "$COLOR1hex" -width $lineWidthPx } ## END of x-tics loop ############################################################# ## Put a label on the index=1 tic-mark on the x-axis. ## yPx was set above. ############################################################# set x [lindex $LISTtics 1] set xPx [Xwc2px $x] set xFMTED [format "%6.2f" $x] .fRcanvas.can create text $xPx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "$xFMTED" -anchor n -font fontTEMP_fixedwidth ################################################################### ## Draw the y-axis --- the population axis. ## $x1Px $y1Px were set above, as the origin. ################################################################### set x2 0.0 set y2 $u1max set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] .fRcanvas.can create line $x1Px $y1Px $x2Px $y2Px \ -tags TAGplot -fill "$COLOR1hex" -width $lineWidthPx ################################################# ## Put the u1max number at the top of the y-axis. ################################################# set u1maxFMTED [format "%6.2f" $u1max] .fRcanvas.can create text $x2Px $y2Px -fill "$COLOR1hex" \ -tags TAGplot -text "$u1maxFMTED -->" -anchor sw -font fontTEMP_fixedwidth ################################################# ## Put the u1-init number near the y-axis ## at the appropriate height. ################################################# set y0 $aRu1(0) set y0Px [Ywc2px $y0] set uinitFMTED [format "%6.2f" $y0] .fRcanvas.can create text $x2Px $y0Px -fill "$COLOR1hex" \ -tags TAGplot -text "$uinitFMTED" -anchor e -font fontTEMP_fixedwidth ############################################### ## Put a title at the top of the plot. ############################################### .fRcanvas.can create text $xmidPx $y2Px -fill "$COLOR1hex" \ -tags TAGtext -text "Population Growth ($ENTRYpopunits)" \ -anchor s -font fontTEMP_fixedwidth ############################################################# ## Put tic-marks on the y-axis. ############################################################# set ticInterval [set_tic_interval 0.0 $u1max] set LISTtics [set_tics_list 0.0 $u1max $ticInterval] set x 0.0 set xPx [Xwc2px $x] foreach tic $LISTtics { set yPx [Ywc2px $tic] ## Draw each y-tic about 3 pixels to the right of the y-axis. .fRcanvas.can create line $xPx $yPx [expr {$xPx + 3}] $yPx \ -tags TAGplot -fill "$COLOR1hex" -width $lineWidthPx } ## END of y-tics loop ############################################################# ## Put a label on the index=1 tic-mark on the y-axis. ## xPx was set above. ############################################################# set y [lindex $LISTtics 1] set yPx [Ywc2px $y] set yFMTED [format "%6.2f" $y] .fRcanvas.can create text $xPx $yPx -fill "$COLOR1hex" \ -tags TAGtext -text "$yFMTED" -anchor e -font fontTEMP_fixedwidth ############################################################### ## LOOP over the index of the array aRu1 (population) ## to perform the plot of the population curve. ############################################################### ## ## At the bottom of each pass through the loop, the results-array ## index is incremented. If that index exceeds Nsteps, ## we 'break' out of this plot loop. ############################################################### set idx 0 set x1 $aRtime($idx) set y1 $aRu1($idx) set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] while {1} { ########################################################### ## If we exceed the number of elements in the array aRu1 ## (that is, the last of the time steps), break out of this ## animation loop. ########################################################### incr idx if {$idx > $Nsteps} {break} ################################################################# ## Set the coordinates of the next plot-point ## in world-coords: (x2,y2). And convert those world-coordinates ## to pixel coordinates, for use in the 'create line' command below. ################################################################# set x2 $aRtime($idx) set y2 $aRu1($idx) set x2Px [Xwc2px $x2] set y2Px [Ywc2px $y2] ########################################################################## ## Draw the next line-segment of the plot curve --- with 'create line'. ########################################################################## .fRcanvas.can create line $x1Px $y1Px $x2Px $y2Px \ -tags TAGplot -fill "$COLOR1hex" ########################################################################## ## Set x1,y1 for the next line-segment. ########################################################################## set x1 $x2 set y1 $y2 set x1Px [Xwc2px $x1] set y1Px [Ywc2px $y1] } ## END OF the plot loop (over the index of array aRu1) ############################################################ ## Try to make the entire plot visible. ############################################################## .fRcanvas.can configure -width $ImageWidthPx .fRcanvas.can configure -height $ImageHeightPx ############################################################ ## Clear the message area --- replace with a plot-done msg. ############################################################ advise_user "\ ** PLOT DONE: Number Of Points Plotted: [expr {$Nsteps + 1}] **" } ## END OF PROC 'plot' ##+######################################################################## ## PROC: 'setMappingVars_for_px2wc' ##+######################################################################## ## PURPOSE: Sets up 'constants' to be used in converting between x,y ## 'world coordinates' and 'pixel coordinates' on a Tk canvas. ## ## Puts the constants in global variables: ## PXperWC BASEwcX BASEwcY BASEpxX BASEpxY ## ## These variables are for use by 'Xwc2px' and 'Ywc2px' procs ## and 'Xpx2wc' and 'Ypx2wc' procs. ## ## The 'BASE' variables are coordinates of the upper-left point ## of the 'plotting rectangle' --- in world coordinates and ## in pixel coordinates. ## ## METHOD: This proc takes the coordinates of an UpperLeft (UL) ## point and a LowerRight (LR) point --- in both ## 'world coordinates' and 'pixel coordinates' and ## sets some global variables to the used by the ## other drawing procs --- mainly the ratio: ## ## the number-of-pixels-per-world-coordinate-unit, ## in global variable 'PXperWC' ## ## (This will generally include a fractional amount, ## i.e. it is not necessarily an integer.) ## ## INPUTS: ## ## At least eight numbers are input to this proc, as indicated by: ## ## ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY ## ## Generally, the 'wc' inputs may be floating point numbers, and the ## 'px' inputs will generally be (non-negative) integers. ## ## Example: (for a plot area with x between -1.2 and +1.2 ## and with y between -0.2 and +1.2) ## setMappingVars_for_px2wc xy -1.2 1.2 0 0 1.2 -0.2 $canvasWidthPx $canvasHeightPx ## ## The first argument can be either 'x' or 'y' or 'xy'. This determines whether ## global variable 'PXperWC' is detemined by just the X-numbers, just the Y-numbers, ## or both. In this script, we use 'xy' (both). ## ## An 'adjustYpx' global variable can be used to adjust if the pixels ## on a user's monitor are not square. ## ## OUTPUTS: global variables PXperWC BASEwcX BASEwcY BASEpxX BASEpxY ## ## CALLED BY: by the animate' proc. ##+######################################################################## proc setMappingVars_for_px2wc {xORy ULwcX ULwcY ULpxX ULpxY LRwcX LRwcY LRpxX LRpxY} { global PXperWCx PXperWCy BASEwcX BASEwcY BASEpxX BASEpxY adjustYpx ## FOR TESTING: (to dummy out this proc) # return ############################################################ ## Calculate PXperWCx and PXperWCy ## (pixels-per-world-coordinate-unit) --- the ratio ## of pixels-per-world-coordinate in the x and y directions, ## for the given UL and LR values. ############################################################ set PXperWCx [expr {abs(($LRpxX - $ULpxX) / ($LRwcX - $ULwcX))}] set PXperWCy [expr {abs(($LRpxY - $ULpxY) / ($LRwcY - $ULwcY))}] ## FOR TESTING: if {0} { puts "proc 'animate':" puts "LRwcY: $LRwcY ULwcY: $ULwcY LRwcX: $LRwcX ULwcX: $ULwcX" puts "PXperWCx: $PXperWCx" puts "LRpxY: $LRpxY ULpxY: $ULpxY LRpxX: $LRpxX ULpxX: $ULpxX" puts "PXperWCy: $PXperWCy" } ############################################################# ## Reset PXperWCx and PXperWCy according to whether input ## variable 'xORy' is 'x' or 'y' or 'min' or 'xy'. ## ## For 'x', we set PXperWCy equal to PCperWcx. ## ## For 'y', we set PXperWCx equal to PCperWcy. ## ## For 'min', we set PXperWCx and PXperWCy to the smaller ## of PXperWCx and PXperWCy. ## ## For 'xy', we will leave PXperWCx and PXperWCy unchanged. ############################################################# if {$xORy == "x"} { set PXperWCy $PXperWCx } elseif {$xORy == "y"} { set PXperWCx $PXperWCy } elseif {$xORy == "min"} { if {$PXperWCx > $PXperWCy} { set PXperWCx $PXperWCy } else { set PXperWCy $PXperWCx } } ## END OF if {$xORy == "x"} ############################################################ ## In case the pixels are not square, provide a factor ## that can be used to adjust in the Y direction. ############################################################ set adjustYpx 1.0 ############################################################ ## Set BASEwcX, BASEwcY, BASEpxX and BASEpxY. ############################################################ set BASEwcX $ULwcX set BASEwcY $ULwcY set BASEpxX $ULpxX set BASEpxY $ULpxY ## FOR TESTING: if {0} { puts "proc 'setMappingVars_for_px2wc':" puts "PXperWCx: $PXperWCx PXperWCy: $PXperWCy" puts "BASEwcX: $BASEwcX BASEwcY: $BASEwcY" puts "BASEpxX: $BASEpxX BASEpxY: $BASEpxY" } } ## END OF PROC 'setMappingVars_for_px2wc' ##+######################################################################## ## PROC: 'Xwc2px' ##+######################################################################## ## PURPOSE: Converts an x world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Xwc2px {x} { global PXperWCx BASEwcX BASEpxX set px [expr {($x - $BASEwcX) * $PXperWCx + $BASEpxX}] return $px } ## END OF PROC 'Xwc2px' ##+######################################################################## ## PROC: 'Xpx2wc' ##+######################################################################## ## PURPOSE: Converts an x-pixel unit to an x-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Xpx2wc {px} { global PXperWCx BASEwcX BASEpxX set x [expr {( ($px - $BASEpxX) / $PXperWCx ) + $BASEwcX }] return $x } ## END OF PROC 'Xpx2wc' ##+######################################################################## ## PROC: 'Ywc2px' ##+######################################################################## ## PURPOSE: Converts an y world-coordinate to pixel units. ## ## CALLED BY: the 'draw' procs ##+######################################################################## proc Ywc2px {y} { global PXperWCy BASEwcY BASEpxY adjustYpx set px [expr {($BASEwcY - $y) * $PXperWCy * $adjustYpx + $BASEpxY}] return $px } ## END OF PROC 'Ywc2px' ##+######################################################################## ## PROC: 'Ypx2wc' ##+######################################################################## ## PURPOSE: Converts a y-pixel unit to a y-world-coordinate value. ## ## CALLED BY: the 'StartAnimation' proc ##+######################################################################## proc Ypx2wc {px} { global PXperWCy BASEwcY BASEpxY adjustYpx set y [expr { $BASEwcY - ( ($px - $BASEpxY) / ( $PXperWCy * $adjustYpx ) ) }] return $y } ## END OF PROC 'Ypx2wc' ##+##################################################################### ## PROC: 'set_tic_interval' ## ## PURPOSE: Given 2 values --- min,max --- representing the ## min value and max value along an axis (horizontal or ## vertical), calculate a suitable tic interval value ## --- to give about 4 to 10 tic marks along the axis. ## ## SOME EXAMPLES: suitable ## min max tic-interval ## ------- ------- ------------ ## 0.0 115.0 20.0 (or 25.0) ## 0.0 995.0 100.0 (or 200.0) ## 0.0 0.17 0.02 (or 0.025) ## -250 +500 100.0 (or 200.0) ## -0.06 +0.08 0.02 (or 0.025) ## ## We can feed values like these for xmin,max into this proc ## to see if it gives a 'suitable' tic-interval value. ## ## CALLED BY: proc 'plot'. ##+##################################################################### proc set_tic_interval {min max} { set delta [expr {$max - $min}] set Nminus1 [expr {int(log10($delta) - 1)}] set ticInterval [expr {2.0 * pow(10,$Nminus1)}] ## FOR TESTING: if {0} { puts "" puts "proc 'set_tic_interval' OUTPUT:" puts "ticInterval: $ticInterval" } return $ticInterval } ## END OF PROC 'set_tic_interval' ##+#################################################################### ## PROC: 'set_tics_list' ## ## PURPOSE: Given 3 values --- min,max,ticInterval --- representing ## the min value and max value along an axis (horizontal or ## vertical), and a tic-mark interval: ## generate a list of tic marks covering an interval ## newmin,newmax where newmin <= min and newmax >= max. ## ## CALLED BY: proc 'plot'. ##+#################################################################### proc set_tics_list {min max ticInterval} { ## FOR TESTING: if {0} { puts "" puts "proc 'set_tics_list' INPUT:" puts "min: $min" puts "max: $max" puts "ticInterval: $ticInterval" } set N [expr {int( [expr {$min / $ticInterval}] )}] set newmin [expr {$N * $ticInterval}] if {$newmin > $min} {set newmin [expr {$newmin - $ticInterval}]} lappend LISTtics $newmin set newmax $newmin while {$newmax < $max} { set newmax [expr {$newmax + $ticInterval}] lappend LISTtics $newmax } ## END of while loop return $LISTtics } ## END OF PROC 'set_tics_list' ##+##################################################################### ## PROC: 'set_plot_color1' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a 'plot color'' --- for ## line-segment and text elements. ## ## Arguments: global variables ## ## CALLED BY: .fRbuttons.buttCOLOR1 button ##+##################################################################### proc set_plot_color1 {} { global COLOR1r COLOR1g COLOR1b COLOR1hex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLOR1r: $COLOR1r" # puts "COLOR1g: $COLOR1g" # puts "COLOR1b: $COLOR1b" set TEMPrgb [ exec $ColorSelectorScript $COLOR1r $COLOR1g $COLOR1b] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR1hex "#$hexRGB" set COLOR1r $r255 set COLOR1g $g255 set COLOR1b $b255 ## Set background-and-foreground colors of the indicated color button. update_color_button color1 advise_user "$aRtext(PLOTmsg) to use a new color" } ## END OF proc 'set_plot_color1' ##+##################################################################### ## PROC: 'set_background_color2' ##+##################################################################### ## PURPOSE: ## ## This procedure is invoked to get an RGB triplet ## via 3 RGB slider bars on the FE Color Selector GUI. ## ## Uses that RGB value to set a canvas 'background' color. ## ## Arguments: global variables ## ## CALLED BY: .fRbuttons.buttCOLOR2 button ##+##################################################################### proc set_background_color2 {} { global COLOR2r COLOR2g COLOR2b COLOR2hex ColorSelectorScript aRtext ## FOR TESTING: # puts "COLOR2r: $COLOR2r" # puts "COLOR2g: $COLOR2g" # puts "COLOR2b: $COLOR2b" set TEMPrgb [ exec $ColorSelectorScript $COLOR2r $COLOR2g $COLOR2b] ## FOR TESTING: # puts "TEMPrgb: $TEMPrgb" if { "$TEMPrgb" == "" } { return } scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB set COLOR2hex "#$hexRGB" set COLOR2r $r255 set COLOR2g $g255 set COLOR2b $b255 ## Set background-and-foreground colors of the indicated color button. update_color_button color2 advise_user "$aRtext(PLOTmsg) to use new color." } ## END OF proc 'set_background_color2' ##+##################################################################### ## PROC: 'update_color_button' ##+##################################################################### ## PURPOSE: ## This procedure is invoked to set the background color of the ## color button, indicated by the 'colorID' string, ## to its currently set 'colorID' color --- and sets ## foreground color, for text on the button, to a suitable black or ## white color, so that the label text is readable. ## ## Arguments: global color vars ## ## CALLED BY: in two 'set_*_color' procs ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc update_color_button {colorID} { global COLOR1r COLOR1g COLOR1b COLOR1hex global COLOR2r COLOR2g COLOR2b COLOR2hex # set colorBREAK 300 set colorBREAK 350 if {"$colorID" == "color1"} { .fRbuttons.buttCOLOR1 configure -bg $COLOR1hex set sumCOLOR1 [expr {$COLOR1r + $COLOR1g + $COLOR1b}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLOR1 configure -fg "#000000" } else { .fRbuttons.buttCOLOR1 configure -fg "#ffffff" } } elseif {"$colorID" == "color2"} { .fRbuttons.buttCOLOR2 configure -bg $COLOR2hex set sumCOLOR1 [expr {$COLOR2r + $COLOR2g + $COLOR2b}] if {$sumCOLOR1 > $colorBREAK} { .fRbuttons.buttCOLOR2 configure -fg "#000000" } else { .fRbuttons.buttCOLOR2 configure -fg "#ffffff" } } else { ## Seems to be an invalid colorID. return } } ## END OF PROC 'update_color_button' ##+##################################################################### ## PROC: 'advise_user' ##+##################################################################### ## PURPOSE: Puts a message to the user on the GUI. ## ## CALLED BY: in three 'set_*_color*' procs, ## in some 'bind' statements in the BIND section above, ## and in the additional-GUI-initialization section at ## the bottom of this script. ##+##################################################################### proc advise_user {text} { .fRmsg.labelMSG configure -text "$text" ## Make sure the text is displayed on the GUI. update ## Alternatively, we could put the message in the title-bar ## of the GUI window. (But it is easy for the user to ## fail to see the message there. Besides, we have more ## options in displaying the message by putting it on a ## Tk widget in the GUI.) ## # wm title . "$text" } ## END OF PROC 'advise_user' ##+######################################################################## ## PROC: 'reset_parms' ##+######################################################################## ## PURPOSE: To reset the 'entry' widgets on the GUI ## --- math expression parameters, initial value parameters, ## and two time parameters (endtime and stepsize). ## ## CALLED BY: 'ResetParms' button and in the 'Additional GUI Initialization' ## section at the bottom of this script. ##+######################################################################## proc reset_parms {} { global ENTRYr ENTRYtimeunits ENTRYk ENTRYinit1 ENTRYpopunits \ ENTRYendtime ENTRYstepsize ## 'Intrinsic' growth rate, percent. # set ENTRYr 1.5 set ENTRYr 5.0 set ENTRYtimeunits "year" set ENTRYpopunits "billions" ## Population saturation limit, in billions, say. # set ENTRYk 20.0 set ENTRYk 15.0 ## Initial population, in billions, say. set ENTRYinit1 7.5 ## End-time of numerical integration, in years, say. set ENTRYendtime 200.0 ## Step-size of numerical integration, in years, say. set ENTRYstepsize 0.1 } ## END OF PROC 'reset_parms' ##+#################################################################### ## PROC: 'edit_inputs' ##+##################################################################### ## PURPOSE: Checks entry widgets entries and pops up an error message ## if the data is invalid. ## ## CALLED BY: the 'solve' and 'animate' procs ##+##################################################################### proc edit_inputs {} { ## Decimal/floating-point variables. global ENTRYr ENTRYk ENTRYinit1 \ ENTRYendtime ENTRYstepsize ## Integer variables. NOT USED, for now. # global ENTRYimgsize ## Error indicator. global EDITcode ## We could do without the following EDITcode variable, by using ## a code with the 'return' statement herein. But using this ## code variable is a little more self-documenting. global EDITcode set EDITcode 0 ####################################################### ## Remove trailing and leading blanks (if any) from the ## user entries in the 'entry' widgets. ####################################################### set ENTRYr [string trim $ENTRYr] set ENTRYk [string trim $ENTRYk] set ENTRYinit1 [string trim $ENTRYinit1] set ENTRYendtime [string trim $ENTRYendtime] set ENTRYstepsize [string trim $ENTRYstepsize] # set ENTRYimgsize [string trim $ENTRYimgsize] ######################################################################### ## Check that these entry fields are NOT blank. ######################################################################### set MSGblank "is blank. Must NOT be blank." if {"$ENTRYr" == ""} { popup_msgVarWithScroll .topErr "The gravitation constant g $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYk" == ""} { popup_msgVarWithScroll .topErr "The pendulum length L $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYinit1" == ""} { popup_msgVarWithScroll .topErr "The initial angular displacement $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYendtime" == ""} { popup_msgVarWithScroll .topErr "The end-time for the solve $MSGblank" +10+10 set EDITcode 1 return } if {"$ENTRYstepsize" == ""} { popup_msgVarWithScroll .topErr "The solve step-size $MSGblank" +10+10 set EDITcode 1 return } # if {"$ENTRYimgsize" == ""} { # popup_msgVarWithScroll .topErr "The size of the image square (pixels) $MSGblank" +10+10 # set EDITcode 1 # return # } ########################################################## ## Check that ENTRYimgsize is an integer. NOT USED, for now. ########################################################## # set MSGnotInteger " is NOT INTEGER." # if {![string is integer -strict "$ENTRYimgsize"]} { # popup_msgVarWithScroll .topErr "The size of the image square (pixels) $MSGnotInteger" +10+10 # set EDITcode 1 # return # } ######################################################################### ## Check that ENTRYr ENTRYk ENTRYinit1 ENTRYendtime ## and ENTRYstepsize are decimal numbers (positive or negative) --- ## such as 1.234 or -3 or -3.0 or -.4 or .5 or 7 ######################################################################### ## Implemented using the 'decimal_check' proc below. ######################################################################### set NUMERICmsg "should be a decimal number. Examples: 1.234 or 0.56 or -.789" if {![decimal_check "$ENTRYr"]} { popup_msgVarWithScroll .topErr "The gravitational constant g $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYk"]} { popup_msgVarWithScroll .topErr "The pendulum length L $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYinit1"]} { popup_msgVarWithScroll .topErr "The initial angular displacement $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYendtime"]} { popup_msgVarWithScroll .topErr "The solve end-time (seconds) $NUMERICmsg" +10+10 set EDITcode 1 return } if {![decimal_check "$ENTRYstepsize"]} { popup_msgVarWithScroll .topErr "The solve step-size (seconds) $NUMERICmsg" +10+10 set EDITcode 1 return } ####################################################################### ## Check that ENTRYr ENTRYk ENTRYendtime ENTRYstepsize ## are not negative. ####################################################################### set POSITIVEmsg "should be a POSITIVE number. Examples: 1.234 or 0.56" if {$ENTRYr < 0.0} { popup_msgVarWithScroll .topErr "The gravitational constant g $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYk < 0.0} { popup_msgVarWithScroll .topErr "The pendulum length L $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYendtime < 0.0} { popup_msgVarWithScroll .topErr "The solve end-time (seconds) $POSITIVEmsg" +10+10 set EDITcode 1 return } if {$ENTRYstepsize < 0.0} { popup_msgVarWithScroll .topErr "The solve step-size (seconds) $POSITIVEmsg" +10+10 set EDITcode 1 return } } ## END of proc 'edit_inputs' ##+######################################################################## ## PROC: 'decimal_check' ##+######################################################################## ## PURPOSE: Returns 1 or 0 if the input string looks like a decimal number ## --- positive or negative. Example numbers that are OK: ## 1.234 12.34 0.234 .234 6 ## -1.234 -12.34 -0.234 -.234 -6 ##+######################################################################## ## References (lots of one-liners): ## http://stackoverflow.com/questions/2072222/regular-expression-for-positive-and-a-negative-decimal-value-in-java ## http://stackoverflow.com/questions/308122/simple-regular-expression-for-a-decimal-with-a-precision-of-2 ## http://stackoverflow.com/questions/4246077/matching-numbers-with-regular-expressions-only-digits-and-commas/4247184#4247184 ## ## More specific to Tcl-Tk (including multi-liners): ## http://wiki.tcl.tk/989 'Regular Expression Examples' ## http://wiki.tcl.tk/768 'Entry Validation' - See "Integer forbidding leading zero:" ## http://wiki.tcl.tk/10166 'string is' ## http://wiki.tcl.tk/40710 'significant digits rounding' - uses regexp to split a number - ## Splits using: if {[regexp {^([+,-]?)([0-9]+)(\.?[0-9]*)?([eE][+-]?[0-9]+)?$} $num -> s i d e]} ## Removes leading zero with: regexp {^(0*)([1-9][0-9]*)$} $i -> NULL DIG ## http://wiki.tcl.tk/530 'Unit converter' has a regexp to parse numbers: ## set RE {(?ix) # Ignore case, extended syntax ## ([-+]?) # Optional leading sign ## ([0-9]*) # Integer part ## \.? # Optional decimal point ## ([0-9]*) # Fractional part ## (e?[0-9]*) # Optional exponent ## } ## ##+######################################################################## ## I do not mind incurring a little (minute amount of) processing ## with a multiple-line implementation. Probably easier to fix if ## a string gets through --- such as ".0.3" (two decimal points). ## ## CALLED BY: proc 'edit_inputs' ##+######################################################################## proc decimal_check {string} { set PosDecimalOK [regexp {^([0-9]*)\.?([0-9]*)$} "$string"] set NegDecimalOK [regexp {^\-([0-9]*)\.?([0-9]*)$} "$string"] set PosNakedDecimalOK [regexp {^\.?([0-9]*)$} "$string"] set NegNakedDecimalOK [regexp {^\-\.?([0-9]*)$} "$string"] set IntegerOK [string is integer $string] set retCODE [expr { \ $PosDecimalOK || $NegDecimalOK || \ $PosNakedDecimalOK || $NegNakedDecimalOK || \ $IntegerOK }] ## FOR TESTING: if {0} { puts "" puts "decimal_check:" puts "string: $string" puts "PosDecimalOK: $PosDecimalOK" puts "NegDecimalOK: $NegDecimalOK" puts "PosNakedDecimalOK: $PosNakedDecimalOK" puts "NegNakedDecimalOK: $NegNakedDecimalOK" puts "IntegerOK: $IntegerOK" puts "retCODE: $retCODE" } return $retCODE } ## END of proc 'decimal_check' ##+######################################################################## ## PROC: 'popup_msgVarWithScroll' ##+######################################################################## ## PURPOSE: Report help or error conditions to the user. ## ## We do not use focus,grab,tkwait in this proc, ## because we use it to show help when the GUI is idle, ## and we may want the user to be able to keep the Help ## window open while doing some other things with the GUI ## such as putting a filename in the filename entry field ## or clicking on a radiobutton. ## ## For a similar proc with focus-grab-tkwait added, ## see the proc 'popup_msgVarWithScroll_wait' in a ## 3DterrainGeneratorExaminer Tk script. ## ## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk', ## 4th edition, by Welch, Jones, Hobbs. ## ## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg) ## and a variable holding text (many lines, if needed). ## ## CALLED BY: 'help' button ##+######################################################################## ## To have more control over the formatting of the message (esp. ## words per line), we use this 'toplevel-text' method, ## rather than the 'tk_dialog' method -- like on page 574 of the book ## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications ## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor". ##+######################################################################## proc popup_msgVarWithScroll { toplevName VARtext ULloc} { ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global. ## global env # bell # bell ################################################# ## Set VARwidth & VARheight from $VARtext. ################################################# ## To get VARheight, ## split at '\n' (newlines) and count 'lines'. ################################################# set VARlist [ split $VARtext "\n" ] ## For testing: # puts "VARlist: $VARlist" set VARheight [ llength $VARlist ] ## For testing: # puts "VARheight: $VARheight" ################################################# ## To get VARwidth, ## loop through the 'lines' getting length ## of each; save max. ################################################# set VARwidth 0 ############################################# ## LOOK AT EACH LINE IN THE LIST. ############################################# foreach line $VARlist { ############################################# ## Get the length of the line. ############################################# set LINEwidth [ string length $line ] if { $LINEwidth > $VARwidth } { set VARwidth $LINEwidth } } ## END OF foreach line $VARlist ## For testing: # puts "VARwidth: $VARwidth" ############################################################### ## NOTE: VARwidth works for a fixed-width font used for the ## text widget ... BUT the programmer may need to be ## careful that the contents of VARtext are all ## countable characters by the 'string length' command. ############################################################### ##################################### ## SETUP 'TOP LEVEL' HELP WINDOW. ##################################### catch {destroy $toplevName} toplevel $toplevName # wm geometry $toplevName 600x400+100+50 # wm geometry $toplevName +100+50 wm geometry $toplevName $ULloc wm title $toplevName "Note" # wm title $toplevName "Note to $env(USER)" wm iconname $toplevName "Note" ##################################### ## In the frame '$toplevName' - ## DEFINE THE TEXT WIDGET and ## its two scrollbars --- and ## DEFINE an OK BUTTON widget. ##################################### if {$VARheight > 10} { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 \ -yscrollcommand "$toplevName.scrolly set" \ -xscrollcommand "$toplevName.scrollx set" ## -font fontTEMP_varwidth scrollbar $toplevName.scrolly \ -orient vertical \ -command "$toplevName.text yview" scrollbar $toplevName.scrollx \ -orient horizontal \ -command "$toplevName.text xview" } else { text $toplevName.text \ -wrap none \ -font fontTEMP_fixedwidth \ -width $VARwidth \ -height $VARheight \ -bg "#f0f0f0" \ -relief raised \ -bd 2 ## -font fontTEMP_varwidth } button $toplevName.butt \ -text "OK" \ -font fontTEMP_varwidth \ -command "destroy $toplevName" ############################################### ## PACK *ALL* the widgets in frame '$toplevName'. ############################################### ## Pack the bottom button BEFORE the ## bottom x-scrollbar widget, pack $toplevName.butt \ -side bottom \ -anchor center \ -fill none \ -expand 0 if {$VARheight > 10} { ## Pack the scrollbars BEFORE the text widget, ## so that the text does not monopolize the space. pack $toplevName.scrolly \ -side right \ -anchor center \ -fill y \ -expand 0 ## DO NOT USE '-expand 1' HERE on the Y-scrollbar. ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA. pack $toplevName.scrollx \ -side bottom \ -anchor center \ -fill x \ -expand 0 ## DO NOT USE '-expand 1' HERE on the X-scrollbar. ## THAT KEEPS THE TEXT AREA FROM EXPANDING. pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } else { pack $toplevName.text \ -side top \ -anchor center \ -fill both \ -expand 1 } ################################################ ## Set some 'event' bindings to allow for ## easy scrolling through huge listings. ## is a press of the Page-Down key. ## is a press of the Page-Up key. ## is a press of the Home key ## to go to the top of the listing. ## is a press of the End key ## to go to the bottom of the listing. ## is a press of the Up-arrow key. ## is a press of the Down-arrow key. ################################################ bind $toplevName "$toplevName.text yview scroll +1 page" bind $toplevName "$toplevName.text yview scroll -1 page" bind $toplevName "$toplevName.text see 1.0" bind $toplevName "$toplevName.text see end" bind $toplevName "$toplevName.text yview scroll -1 unit" bind $toplevName "$toplevName.text yview scroll +1 unit" ##################################### ## LOAD MSG INTO TEXT WIDGET. ##################################### ## $toplevName.text delete 1.0 end $toplevName.text insert end $VARtext $toplevName.text configure -state disabled } ## END OF PROC 'popup_msgVarWithScroll' ##+######################################################## ## Set the 'HELPtext' var. ##+######################################################## set HELPtext \ " ** HELP for this 'Simulate Population Growth - subject to a population limit' ** * software application * (which includes an option to plot the population curve on a 'canvas') This Tk GUI script solves a first-order 'nonlinear' ordinary differential equation (ODE) for population growth with a limit to growth of the population. To allow for some fairly complex forms of the differential equation, this script uses numerical integration --- the Runge-Kutta 4th order (RK4) method. A relatively simple form of the ODE for limited population growth is called 'the logistic equation': D(u) = (r * (1 - u/k)) * u where D represents the time-derivative operator d/dt, and t represents the time independent variable, and u represents the population (typically, taken to be the population of all mankind on Earth), and r is 'the intrinsic growth rate' (which is the net growth rate --- the overall birth rate minus the overall death rate --- when the population is nowhere near being limited by resources), and k is 'the saturation level' of the population (which, in the case of humans on Earth, could be given a reasonable value by taking the amount of habitable surface area of the Earth and multiplying by a limiting population density on the Earth, which may be on the order of 100 people per square kilometer, on average. There would probably be many square miles of land that would be needed to grow food --- unless mankind eventually got essentially all food from the seas. In any case, there would probably need to be some room allowed to avoid spread of diseases and relieve war-like tensions, like demilitarized zones --- and room for some areas for sewage and garbage disposal/storage/recycling and for storage and processing of non-salty, disease-free water --- and, perhaps, some room for energy-generating facilities --- and room for transporation avenues and parking facilities for various kinds of vehicles.) (A separate Tk GUI utility could be provided to calculate a value for 'k', taking into account factors like those above --- including the radius of the planet and percent of the planet covered by oceans, deserts, mountains, volcano areas, flood-prone areas, etc.) Note that the factor (r * (1 - u/k)) is the effective growth rate. In this form, the rate goes to zero as the population, u, approaches the 'saturation level', k. Presumably the rate would go to zero as the birth rate eventually matches the death rate --- because of a combination of factors, such as food and water limits, oxygen generation limits, disease transmission, deaths due to wars and crime, etc. Note also that the factor (1 - u/k) may under-estimate --- or over-estimate --- the rate at which the growth rate goes to zero as the population u increases. It may be that the factor (1 - u/k) should take some other form such as (1 - (u/k)^2) or (1 - sqrt(u/k)) --- to more accurately model the change in population growth rate as population increases. Because in real life there may be sudden changes in population due to epidemics and wars and natural disasters, these 'smooth' forms of the slow-down in the growth rate will not yield precise predictions of actual population level versus time. These considerations demonstrate why this script uses numerical integration to solve the equation. This numerical solution method will allow for solving a wide variety of forms of the growth rate equation. This GUI may be altered some day to allow for more general forms of the right-hand-side of the differential equation. ************************************** SOME NOTES ON THE FACTORS 'r' and 'k': ************************************** In the 'logistic equation', we take r and k to be constants, but actually they might be functions of t (time) and/or u, the population. For example, with advances in medicine, the death rate might be reduced somewhat so that the 'intrinic growth rate', r, might increase somewhat over time. But r would probably reach a limit --- for example, as world-wide travel and disease-resistance to medical treatment leads to a higher death rate and/or as 'labor-saving' devices lead to obesity and hence a higher death rate. Similarly, the population 'saturation level', k, might be affected over time as various inventions make it possible to support more people per square kilometer (or square mile). In that case, k might increase somewhat over time. On the other hand, as crowding may lead to more human conflict, the value of k may decrease as some types of inventions, such as more lethal weaponry, result in population reaching a 'saturation level' at a lower overall world population level. ***************************************** 'ALGEBRAIC' SOLUTIONS OF GROWTH EQUATIONS: ***************************************** If there were no limit to growth (i.e. 'k' is infinity) and if the growth rate 'r' remains constant, then the 'logistic' equation is simply D(u) = r * u which has the solution u(t) = u(0) * exp(r*t) where exp() represents the exponential function, base e. In other words, growth is exponential --- without limit. Furthermore, there is a similar exponential algebraic solution to the 'logistic equation' when 'k' is finite: u(t) = u(0)*k / ( u(0) + (k - u(0))*exp(-r*t) ) As t goes to infinity, this expression becomes u(t) = u(0)*k / u(0) = k So, as one might expect, the population approaches the 'saturation level' k eventually. In real life, influences on population growth over time are much more variable --- due to outbreaks of disease, outbreaks of war, natural disasters, and changes in the birth rate. We use numerical integration --- rather than the algebraic equation above --- so that we can eventually change this script to change the form of 'r' and 'k'. And, more generally, we could make the RHS (right-hand-side) of the differential equation some more general function, f(u), of population --- or an even more general function, f(u,t), of population and time --- instead of the 'logistic' RHS : f(u) = (r * (1 - u/k)) * u ************ GUI FEATURES: ************ The current GUI allows the user to enter various values for r (net growth rate) and k (saturation level). The GUI also allows the user to enter parameters for the solver process: - an initial population level - an end-time for the end of the solution process (where we typically think of time measured in years, when we are modeling human population growth) - a time-step, h, for the solution process, where h can be a very small fraction of a year. Following a solution run, the user can use a 'Plot' button on the GUI that allows the user to plot a graph of the population curve --- drawn on a Tk canvas. The GUI also has a 'ShowList' button by which the user can show a list of the time and population values generated by the solution process. ********************** TYPICAL USE OF THE GUI: ********************** There are typically 3 steps in using this GUI: 1) THE SOLVE STEP: The Tk GUI allows the user to specify r, k, initial-population, time-step-size, end-time. Set these values as desired and click on the 'Solve' button to perform a solution (a numerical integration of the first-order ordinary differential equation). 2) THE SHOW-LIST STEP: The 'ShowList' button can be used to show the list of current solution values --- t(i), u(t(i)) --- in a popup window. 3) THE PLOT STEP: There are 2 'Color' buttons by which to call up an RGB-color-selector GUI by which to specify the 2 colors for the plot drawing on the canvas. Then the 'Plot' button can be used to perform a plot of u versus t, on the canvas. ************************* EXPERIMENTING VIA THE GUI: ************************* The user can experiment with various values of the 'intrinsic' (maximum) growth rate, 'r', and various values of the assumed 'population saturation level', 'k'. Generally the time-units of the population growth rate is taken to be in years --- when dealing with animals on the Earth. But if you are using this utility to simulate population growth of faster growing species, you may wish to use growth rates based on weeks or days or hours --- instead of years. --- If you are simulating populations of species that are going extinct, it may be suitable to set the intial value in thousands or hundreds --- instead of billions. However, in that case, the saturation limit will never be approached, so the value of 'k' may be far greater than the initial value --- and the growth rate is negative, so the saturation level will never be approached. In fact, in the case of a species going extinct, its negative growth rate may be related to the human population --- and its growth rate may get more and more negative as human population increases. In this case, we need to use a *PAIR* of first-order differential equations, where the differential equation for the population of the species going extinct is strongly influenced by the human population --- and the differential equation for the human population is also strongly influenced by the human population, but the human population is essentially not influenced at all by the population of the species going extinct. That 'species going extinct' case could be handled in a different Tk GUI script. ************************************************* METHOD of MATH MODELING OF THE 'LOGISTIC' EQUATION: ************************************************* We write the first-order non-linear population equation as D(u) = r * (1 - u/k) * u with initial condition u(0)=A, where A is an initial population value (of humans, bacteria, viruses, parasites, ants, roaches, termites, algae, scum, whatever). The common way of expressing systems of 'first order' differential equations in compact, general form is D(u) = f(t,u) where u and f are N-dimensional vectors. This is a compact way of expressing a system of scalar differential equations: D(u1) = f1(t,u1,...,uN) D(u2) = f2(t,u1,...,uN) ...... D(uN) = fN(t,u1,...,uN) In the case of the 'logistic equation', N=1, and we can think of solving for the unknown 1-D function vector (u1(t)) where the right-hand-side (RHS) of the 'logistic' equation above can be thought of as a special case of a more general user-specified 1-D function vector (f1(t,u1)) where f1(t,u1) = r * (1 - u1/k) * u1 = r * (u1 - u1^2/k) We use the popular Runge-Kutta 4th order method (RK4) to perform the numerical integration for a user-specified time step, h. We basically use two procs to perform the integration steps: - a proc to perform the RK4 integration for N=1--- giving the values of u1 for each time step - a proc to evaluate the RHS function: f1 for specified values of t,u1. (In this 'logistic' equation case, r and k are constant, and there is no dependence of f1 on t.) The latter proc is called several times by the former proc for each time step. *************************************************** METHOD of PLOTTING THE POPULATION CURVE on the TK CANVAS: *************************************************** After a solution, we have the solution function u1 for a sequence of equally-spaced time values. We use time, t, as the horizontal axis of the plot. The time goes from zero to the end-time, tmax, specified by the user. As the numerical integration proceeds, we keep track of the maximum value, umax, reached by u1 over the integration time period. The solution curve is plotted in a rectangular area: 0 to tmax in the x-direction 0 to umax in the y-direction A margin is supplied around this rectangular area, and two axes are drawn --- a horizontal time-axis and a vertical u-axis (population). Min and max values are indicated on the ends of the 2 axes. Intermediate tic-marks and tic-labels may also be supplied. The GUI provides 2 buttons by which the user can specify the 2 colors for: - the canvas background - the plot components (line segments and text labels). A 'plot' proc performs the plot of the solution when the user clicks on a 'Plot' button of the GUI. This 'plot' proc uses the 'world-coordinates' --- the 0 to tmax values and the 0 to umax values --- to draw the plot within an area of about tmax by umax in world coordinates. Actually, we augment this world-coordinates rectangular area by providing a margin of about 0.1*tmax and 0.1*umax on the left and right and on the top and bottom, respectively, of the plot area. A proc is provided which maps the plot area limits in world coordinates --- say UpperLeftCorner: ( -0.1*tmax , 1.1*umax ) LowerRightCorner: ( +1.1*tmax , -0.1*umax ) to the corners of the plot area in pixel coordinates: UpperLeftCorner: (0,0) LowerRightCorner: (ImageWidthPx,ImageHeightPx) We may allow the user to determine the ImageWidthPx and ImageHeightPx values (in pixels) by letting the user resize the window, which will cause the Tk 'canvas' widget to resize --- and we will query the current canvas size to get ImageWidthPx and ImageHeightPx. The 'plot' proc uses 2 procs --- Xwc2px and Ywc2px --- to convert the world coordinates of each point --- such as the end-points of the axes and the end-points of line-segments connecting successive plot points --- to pixel coordinates. The pixel-coordinates are used in 'create line' and 'create text' commands to draw the plot on the canvas. " ##+##################################################### ##+##################################################### ## ADDITIONAL GUI INITIALIZATION, if needed (or wanted). ##+##################################################### ##+##################################################### ##+##################################################### ## Set the full-name of the RGB color-selector Tk script ## that is used in several procs above. ##+##################################################### ## FOR TESTING: # puts "argv0: $argv0" set DIRthisScript "[file dirname $argv0]" ## For ease of testing in a Linux/Unix terminal and located at the ## directory containing this Tk script. Set the full directory name. if {"$DIRthisScript" == "."} { set DIRthisScript "[pwd]" } set DIRupOne "[file dirname "$DIRthisScript"]" set DIRupTwo "[file dirname "$DIRupOne"]" set ColorSelectorScript "$DIRupTwo/SELECTORtools/tkRGBselector/sho_colorvals_via_sliders3rgb.tk" ## Alternatively: Put the RGB color-selector Tk script in the ## same directory as this Tk script and uncomment the following. # set ColorSelectorScript "$DIRthisScript/sho_colorvals_via_sliders3rgb.tk" ##+############################################# ## Initialize the plot-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative pendulum-color.) ##+############################################# if {1} { ## Yellow: set COLOR1r 255 set COLOR1g 255 set COLOR1b 0 } else { ## White: set COLOR1r 255 set COLOR1g 255 set COLOR1b 255 } set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b] update_color_button "color1" ##+############################################# ## Initialize the background-color. ## ## (Change 'if {1}' to 'if {0}' to try an ## alternative min-color.) ##+############################################# if {1} { ## Black: set COLOR2r 0 set COLOR2g 0 set COLOR2b 0 } else { ## Red: set COLOR2r 255 set COLOR2g 0 set COLOR2b 0 } set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b] update_color_button "color2" #+####################################################### ## Initialize the entry widgets on the GUI. ##+###################################################### reset_parms ##+##################################################### ## Advise the user how to start. ##+##################################################### advise_user "$aRtext(SOLVEmsg)"