Instructions for creating your own version of DsTool ==================================================== Table of Contents: I. Motivation II. Creating your own version of DsTool III. Executing your own version of DsTool IV. Linking external libraries into DsTool V. Adding your own model to DsTool VI. Adding animation to your model VII. Adding your own computational code to DsTool A. Scripting in Tcl/Tk B. Adding model-specific code C. Adding general purpose code D. Incorporating your code into DsTool E. Accessing data already computed by DsTool VIII. Adding your own Tcl/Tk code to DsTool A. Changing default colors and fonts B. Changing external program calls C. Changing default values of other global variables D. Creating Tcl/Tk windows in DsTool I. Motivation ------------- This file describes how to create a custom version of DsTool. You might want to do this for many reasons. For example, you may want to change the default color or font settings for DsTool, or use a different image or postscript viewer than the default one. Maybe you want to add models that you are studying in your own research to DsTool, and you want those models to have accompanying animations. Perhaps you're feeling adventurous and want to extend the capabilities of DsTool by writing your own Tcl/Tk windows or your own computational code. All of these procedures (and more!) are described in this file. II. Creating your own version of DsTool --------------------------------------- 0) This assumes that DsTool has already been installed and compiled on your network and that you have been able to run it! This file, before you possibly copied it into your own directory, is stored in a subdirectory of the DsTool directory called my_dstool. 1) You must first define some environmental variables: setenv DSTOOL setenv MY_DSTOOL setenv ARCH For example: setenv DSTOOL /home/software/group1/dstool_tk setenv MY_DSTOOL $HOME/my_dstool setenv ARCH sun4 These environmental variables should be set for both compilation and runtime so you may want to add them to your .cshrc file or another appropriate place. 2) Now make sure the directory MY_DSTOOL exists and copy the source code in $DSTOOL/my_dstool into $MY_DSTOOL. mkdir $MY_DSTOOL cp -r $DSTOOL/my_dstool/* $MY_DSTOOL 3) Now make your personal version of DsTool. cd $MY_DSTOOL make III. Executing your own version of DsTool ----------------------------------------- 1) You must first define an environmental variable: setenv MY_DSTOOL For example: setenv MY_DSTOOL $HOME/my_dstool 2) Test it by running: dstool_tk There should be a message saying that the following binary is being executed: $MY_DSTOOL/bin/$ARCH/my_dstool Notes on running dstool_tk: dstool_tk is a script distributed with DsTool which should already be installed in someplace accessible to you. This script determines where to look for the dstool_tk binary and the data files which DsTool needs. It uses the environmental variables MY_DSTOOL, DSTOOL, and ARCH. If the MY_DSTOOL environmental variable is set, then it tries to execute the binary $MY_DSTOOL/bin/$ARCH/my_dstool, otherwise it will try to execute $DSTOOL/bin/$ARCH/dstool_tk. A message is printed which gives the expanded pathname of the binary which is being executed when the dstool_tk script is run. IV. Linking external libraries into DsTool ------------------------------------------ When you write the C code to define your model or execute your computational code, you may want to use external libraries (NAG, IMSL, ADOLC, etc). To compile them into DsTool, you will need to modify the file $MY_DSTOOL/Makefile. Specifically, you will need to add the external library to the list of user libraries. This might look something like the following: NAGLIB = /usr/local/lib/libnag.a USER_LIBS = $(NAGLIB) \ /usr/local/lang/SC1.0/libF77.a These lines tell the compiler to link to the appropriate NAG and Fortran 77 libraries. If you compile DsTool on a different architecture, you may need to change these lines to refer to the appropriate architecture-specific libraries. V. Adding your own model to DsTool ---------------------------------- We will describe how to compile a new model into DsTool. Please see the User's manual for a description of how to write the C code for the model. These instructions simply describe how to create the new executable. You may find that you can simply modify a model already in DsTool - look in the directory $DSTOOL/src/models. 1) Write the new model definition in a file which we shall assume is called new_model_def.c and put this file in the directory $MY_DSTOOL. We shall assume that the initialization procedure is called new_model_init(). 2) Edit the file $MY_DSTOOL/user.c so that the new model is incorporated into the list of known models. This involves first declaring the initialization procedure: extern int new_model_init(); then adding it to the list of user dynamical systems. This might now look like: struct DS_DataS USER_DS_Sel[]= { { 0, "Lorenz system", lorenz_init }, { 0, "My new model", new_model_init } }; The first number is an integer which indicates the category under which the model should be listed. Initially, there is just one category, but multiple categories can be set up by modifying the category list in the following manner: char *USER_DS_Category[] = { "User models", /* Category 0 */ "Project with Bob", /* Category 1 */ "My favorite dynamical systems" /* Category 2 */ }; 3) Edit the file $MY_DSTOOL/Makefile to add new_model_def.c to the USER_SRCS line and new_model_def.o to the USER_OBJS line. For example: USER_SRCS = user.c new_model_def.c USER_OBJS = user.o new_model_def.o If your model file uses an external library, modify the file $MY_DSTOOL/Makefile to include the appropriate library, as described in the above section. 4) Recompile your DsTool binary by executing the 'make' command in the directory $MY_DSTOOL. See the above section on creating your own version of DsTool for more details on this and a list of the environmental variables which must be set for it to work. VI. Adding animation to your model ---------------------------------- This will assume that you already know how to use Geomview and a little bit about Geomview's oogl language. You may also find it helpful to look at some of the system examples. There are two types of animations. The first is where objects are moved simply by sending new transformation matrices to Geomview. This is how the pendulum and restricted three body problem are implemented. Another way is to write out a new Geomview object every time replacing an existing one. This is how you make simulated water waves. We will discuss how to animate using transformation objects first. We will suppose that you have just created (and installed) a new dynamical system called "mymodel" and saved it in the file $MY_DSTOOL/mymodel_def.c 1) Create an oogl file for Geomview where the transformation matrices that need to be updated for animation have names ttt0, ttt1, ttt2, .... Save this file in $MY_DSTOOL/oogl/mymodel.oogl 2) Edit the model file $MY_DSTOOL/mymodel_def.c by including the following code before the "#include " statement: #define GV int (*gv_funct)() = mymodel_gv; int (*gv_custom_funct)() = NULL; int gv_n = 3; static char *gv_filename = "mymodel.oogl"; The value of gv_n should be adjusted to reflect the number of transformation matrices to be sent to geomview. 3) Continue editing the file $MY_DSTOOL/mymodel_def.c and create the function mymodel_gv. This function should provide a mapping between the phase and parameter spaces to the transformation matrices. Each transformation matrix is 16 doubles which are arranged in a vector so that f[4*i+j] = T[i][j] (i=0..3, j=0..3). The vectors for each transformation matrix are concatenated together to make one vector of length 16*gv_n, where gv_n is the number of transformation matrices. Here is a simple example where two transformation matrices are used: /* function used for geomview transformation matrices */ int mymodel_gv(double *f, double *x, double *p) { int i; /* zero matrix */ for (i=0; i<32; i++) f[i] = 0.0; /* simply scale first object using p[0] */ f[0] = f[5] = f[10] = 1.0; f[15] = p[0]; /* rotate second object in the x-y plane by theta=x[0] */ f[0] = f[5] = cos(x[0]); f[1] = sin(x[0]); f[4] = -f[1]; f[10] = f[15] = 1.0; return 0; } 4) Recompile your personal version of DsTool and see if it works! If you need more complicated commands sent to Geomview than simply pumping out transformation matrices in order to provide an animation then you need to write a custom animation routine for the model. 1) Create an oogl file for Geomview which will be loaded when the model is selected or through the animation window. Save this file in $MY_DSTOOL/oogl/mymodel.oogl. 2) Edit the model file $MY_DSTOOL/mymodel_def.c by including the following code before the "#include " statement: #define GV int (*gv_funct)() = NULL; int (*gv_custom_funct)() = mymodel_gv_custom; int gv_n = 0; static char *gv_filename = "mymodel.oogl"; In this example we use no transformation matrices, but the two methods can be combined! 3) Continue editing the file $MY_DSTOOL/mymodel_def.c and create the function mymodel_gv_custom. This function should use the supplied phase space and parameter values to write a Geomview object. It should use the procedure geomview_send(char *) to send strings to Geomview. An example function would look something like this: /* function used for geomview animations */ int mymodel_gv_custom(double *x, double *p) { /* wrap commands in progn so that no partial updates are performed */ geomview_send("(progn\n"); /* send stuff now */ geomview_send("(read geometry {define "); ... AND SO ON ... geomview_send(")\n"); return 0; } 4) Recompile your personal version of DsTool and see if it works! VII. Adding your own computational code to DsTool ------------------------------------------------- Custom analysis code may be added to DsTool on a model-by-model basis or on a general purpose basis. The installation of the computational routines in these two cases is similar. In writing your computational code, you should check the utility routines in $DSTOOL/src/math_utilities and $DSTOOL/src/utilities to look for helpful routines. Many matrix utilities (LU-factorization, Singular value decomposition, etc.) are included in these directories. In addition, eigenvalue and eigenvector routines can be found in the directory $DSTOOL/src/eigen. VII.A. Scripting in Tcl/Tk -------------------------- As mentioned in the User's manual (section 3.2.2), DsTool can be run entirely by typing commands at the command prompt. For this reason, when doing simple computations, it is probably easiest to write them as Tcl/Tk scripts. Scripting can also be useful for developing routines, since you can modify scripts and re-execute the same binary, without recompiling every time you modify your code. Of course, due to the overhead of Tcl/Tk, routines scripted in Tcl/Tk will be much slower than their C counterparts. You can run scripts from the terminal by typing "source ", or you can create Tcl/Tk windows which run the scripts when a button is pressed. For information on writing Tcl/Tk scripts, consult a comprehensive Tcl/Tk source. A good on-line reference guide is: http://www.sco.com/Technology/tcl/Tcl.html VII.B. Adding model-specific code --------------------------------- To add model-specific code, two extra C code routines need to be written. The first should be thought of as initialization code which gets called when the model is selected. This routine will install your computational code so that it is usable. The second is cleanup code which gets called when the user selects some other model. This routine will remove your computational code, so that the user does not try to use it with another model. Throughout this and the following sections, we will adhere to the following convention: procedures using the name "mycode" will refer to the computational (model-specific or general purpose) routines, and procedures using the name "mymodel" will refer to routines which perform the operations of adding and removing objects when the specified model is selected. Hence, procedures using the name "mymodel" will appear only in this section. 0) Since your code may consist of several files, it would be nice to have it all together in a subdirectory. Therefore, assume you have created a subdirectory called MYCODE. Assume further that you have created a new model definition called mymodel and it is in the file $MY_DSTOOL/MYCODE/mymodel_def.c. 1) The initialization mymodel_init() in user.c should be as in section V, so do not mention the subdirectory MYCODE there. However, do not add mymodel_def.c to USER_SRCS in the Makefile, and do not add mymodel_def.o to USER_OBJS. Instead, fill out in $MY_DSTOOL/Makefile: SUBDIRS = tcl MYCODE and USER_LIBS = MYCODE/libmycode.a The idea is to compile the files of MYCODE into the library libmycode.a. This will happen by using the command "make all", which first creates the library libmycode.a and then compiles your personal version of DsTool. 2) Edit $MY_DSTOOL/MYCODE/mymodel_def.c to include the following before the "#include " line: pm(PUT, "Model.Install", mymodel_install, PUT, "Model.Clean", mymodel_clean, NULL); Include declarations for these routines right after the "#include " line: void mymodel_install(void); void mymodel_clean(void); 3) Now create a new file, say mymodel.c, in the directory MYCODE, and write the routine mymodel_install(). This routine can do whatever it wants, even calling Tcl/Tk code to modify the interface. This is commonly done to add new menu items to the window menu. The tcl_script(char *) command is used for Tcl/Tk code. Tcl/Tk procedures may be written and put in files in the $MY_DSTOOL/tcl/ directory. Here is a simple example: /* file: mymodel.c */ #include #include #include #include "pm.h" /* these two files are needed to */ #include "utilities.h" /* use the procedure tcl_script */ #include "mymodel.h" /* you create these files */ #include "mycode.h" /* see 4) */ void mymodel_install() { fprintf(stderr, "Executing mymodel_install.\n"); mycode_install(); tcl_script("mymodel(install)"); } The procedure mycode_install() will install the Postmaster entries necessary for the computational routines. It is important that this routine is called before tcl_script("mymodel(install)") to set up the interaction between the Postmaster and the Tcl/Tk variables correctly. We will describe this further in section VII.D. 4) Now write the routine mymodel_clean(), also in $MY_DSTOOL/MYCODE/mymodel.c to deallocate memory assigned in mymodel_install() or revert changes to the interface. For example: void mymodel_clean() { fprintf(stderr, "Executing mymodel_clean.\n"); tcl_script("mymodel(clean)"); } 5) The file "mymodel.h" should be created and should contain something like: /* file: mymodel.h */ #ifndef MYMODEL_H #define MYMODEL_H void mymodel_install(void); void mymodel_clean(void); #endif 6) The file "mycode.h" should be created and should contain something like: /* file: mycode.h */ #ifndef MYCODE_H #define MYCODE_H void mycode_compute(void); void mycode_install(void); #endif 7) In order for all these files to communicate, you should write a Makefile in the directory $MY_DSTOOL/MYCODE/ that links them together and defines the library libmymodel.a. This should look like: # # Makefile for library # TOP = $(DSTOOL) CONFIG = $(TOP)/config CURRENT_DIR = ./MYCODE INCLUDE_DIR = $(TOP)/src/include include $(CONFIG)/Makefile.defs LIB_NAME = libmycode.a SRCS = mymodel_def.c mymodel.c mycode.c OBJS = mymodel_def.o mymodel.o mycode.o INCLUDE_FILES = mymodel.h mycode.h all:: $(LIB_NAME) include $(CONFIG)/Makefile.rules 8) You still need to write the two procedures called by tcl_script, mymodel(install) and mymodel(clean). This is done in the file mymodel.tk, in the directory $MY_DSTOOL/tcl/. As an example, we write the two procedures such that when you select the model "mymodel" in DsTool, a new menu option in "Panels" appears that causes a window to pop up. The Tcl/Tk procedure that creates the window will be discussed below, but the command that will cause the window to pop up is "window(open) mycode". The first part of the file mymodel.tk looks like this: # file: mymodel.tk # procedure that gets called when mymodel is selected proc mymodel(install) {} { new_tcl_pm MyCode cmd(add_to_panels) "Example menu item..." \ "window(open) mycode" } # procedure that gets called when another model is selected # it must undo what the above procedure did proc mymodel(clean) {} { if [winfo exists [build_Wname mymodel]] { window(dismiss) mymodel } remove_tcl_pm MyCode cmd(remove_from_panels) "Example menu item..." } The procedures new_tcl_pm, remove_tcl_pm, cmd(add_to_panels), and cmd(remove_from_panels) are known to DsTool already. The first two procedures respectively create and remove Postmaster objects in Tcl/Tk named MyCode. The latter two commands respectively add and remove a menu item to the command ("cmd") window's popup menu "Panels". One final note: Tcl/Tk is very picky about syntax. If you just cut and paste the routines above into the file mycode.tk (with the spaces before each line), Tcl/Tk will not pick up that they are procedures, and the program will crash. 9) You need to edit the Makefile in $MY_DSTOOL/tcl/ in order to let DsTool know about your mymodel.tk file. In $MY_DSTOOL/tcl/Makefile, fill out: TK_SRCS = mymodel.tk Now type "make" in the directory $MY_DSTOOL/tcl/. 10) The preceding steps have developed the framework for adding computational code in the model-specific case. When your new model is selected, a new option appears in the Panels menu which will cause a window to pop up. When you select a different model, that option disappears. We have yet to implement the computations that we want to perform, and write Tcl/Tk code for the windows. Skip ahead to section VII.D to continue the installation. VII.C. Adding general purpose code ---------------------------------- The procedure for adding general purpose code is much simpler than in the model-specific case. Instead of editing model files so that menu options are added and removed when different models are selected, you only need to add the menu option once. The drawback, of course, is that you either have to make your code more general, or realize that it won't be reliable and might crash the program (or worse!) if you try to use it with another model. To make things even more complicated, remember that DsTool models can either be mappings or vector fields, so if you want your routine to be fully general, you should prepare it for both of these types of models. An alternative is to make your window appear differently for mappings and vector fields. For example, assume you have a computational routine that will work only for vector fields. When your window is opened (by mycode(build) -- see below), you can first check whether the current model is a mapping or a vector field. If it is a mapping, instead of opening your window, you can open a message window saying that your routines will not work for mappings. Neglecting this difficulty, the steps we take are: 0) Since your code may consist of several files, it would be nice to have it all together in a subdirectory. Therefore, assume you have created a subdirectory called MYCODE. 1) Fill out in $MY_DSTOOL/Makefile: SUBDIRS = tcl MYCODE and USER_LIBS = MYCODE/libmycode.a The idea is to compile the files of MYCODE into the library libmycode.a. This will happen by using the command "make all", which first creates the library libmycode.a and then compiles your personal version of DsTool. 2) Edit the file $MY_DSTOOL/user.c to include your installation procedure (which we will describe how to write below). For now, assume it is called mycode_install(). The appropriate lines in user.c should now look something like: /* ---------------------------------------------------------------- * * INCLUDE USER COMPUTATIONAL MODULES HERE * * ---------------------------------------------------------------- */ extern void mycode_install(); typedef void (*PFV)(); PFV user_install_procs[] = { mycode_install, (PFV) NULL }; Be sure not to remove the "(PFV) NULL" entry. DsTool uses this to signal the end of the array. The procedures in user_install_procs will be called when your custom version of DsTool starts up, installing your code once and for all. 3) Edit the file $MY_DSTOOL/tcl/my_app_init.tcl to make the menu option appear. This should look something like: proc my_app_init {} { global COLOR FONT EXT # Add changes to colors and fonts here window(open) cmd # Add new panel items here cmd(add_to_panels) "Example menu item..." \ "window(open) mycode" } This line will cause a new option to appear in the Panels list, which when pressed will execute the command "window(open) mycode", which opens the window "mycode". Note that this line must go after the line "window(open) cmd", since we are changing entries in the Command window. 4) The file "mycode.h" should be created in the directory $MY_DSTOOL/MYCODE, and should contain something like: /* file: mycode.h */ #ifndef MYCODE_H #define MYCODE_H void mycode_compute(void); void mycode_install(void); #endif 5) In order for all these files to communicate, you should write a Makefile in the directory $MY_DSTOOL/MYCODE/ that links them together and defines the library libmycode.a. This should look like: # # Makefile for library # TOP = $(DSTOOL) CONFIG = $(TOP)/config CURRENT_DIR = ./MYCODE INCLUDE_DIR = $(TOP)/src/include include $(CONFIG)/Makefile.defs LIB_NAME = libmycode.a SRCS = mycode.c OBJS = mycode.o INCLUDE_FILES = mycode.h all:: $(LIB_NAME) include $(CONFIG)/Makefile.rules 6) The preceding steps have developed the framework for adding computational code in the general purpose case. When DsTool starts up, your custom version will have an additional option in the Panels menu which will cause a window to pop up. We have yet to implement the computations that we want to perform, and write the Tcl/Tk code for the windows. Move on to the next section to continue the installation. VII.D. Incorporating your code into DsTool ------------------------------------------ If you followed the steps above, you have set up the necessary framework to include your computational code. You still must build the Tk window and write the computational procedures. To do this, you will need to know a little about DsTool's memory storage facility, the Postmaster. The Postmaster is the keeper of most of DsTool's information, and offers an efficient way to pass information from DsTool to Tcl/Tk and vice versa. All of the non-memory entries of the Postmaster are shadowed in Tcl/Tk as parts of global arrays. For example, the Postmaster entry Defaults.Precision is shadowed in Tcl/Tk by the global variable Defaults(Precision). The command "tcl_to_pm " sends the values of the Tcl/Tk global array to the Postmaster, and the command "pm_to_tcl " sends the values of the Postmaster entries of the object to the Tcl/Tk global array . By using these commands together: first tcl_to_pm, then pm_to_tcl, you can update the Postmaster with all of the current information displayed in the Tcl/Tk interface windows, and return the information to confirm the values contained in the Postmaster. 1) You first need to let DsTool know about your new Tcl/Tk routines. Edit the Makefile in $MY_DSTOOL/tcl/ by adding the routine mycode.tk to the TK_SRCS line. In the model-specific case, you will have two entries on that line now, and in the general purpose case, you will have only one. 2) Write the procedure that creates the new window that is supposed to pop up when we select "Example menu item...". Put this procedure in the file mycode.tk. Our example will be a window with a read-write text field and two buttons. The read-write text field will illustrate how to use the Postmaster to send information from Tcl/Tk to DsTool and back again. One of the buttons will be the "Dismiss" button to close the window. You can choose to have the other button (for the computations) next to this button, or right above it. In the first case, the procedure looks like this: # procedure to open MyCode window proc mycode(build) name { global MyCode build_Title $name "Example window" build_LabelEntryColumns $name le0 \ {text "" {Number:}} \ {ientry "" {MyCode(Number)}} bind_LabelEntryColumns $name.le0 1 mycode(update) build_DismissButtonbar $name dbbar \ "window(dismiss) mycode" \ {"My Code" mycode(compute_mycode) } } If you want them above each other, write something like this: # procedure to open MyCode window proc mycode(build) name { global MyCode build_Title $name "Example window" build_DismissButtonbar $name dbbar \ "window(dismiss) mycode" {} build_LabelEntryColumns $name le0 \ {text "" {Number:}} \ {ientry "" {MyCode(Number)}} bind_LabelEntryColumns $name.le0 1 mycode(update) build_Buttonbar $name bb0 \ {"My Code" mycode(compute_mycode) } pack $name.le0 -side top pack $name.bb0 -side bottom } The standard routines build_DismissButtonbar, build_Buttonbar, build_LabelEntryColumns, and bind_LabelEntryColumns are located in the file $DSTOOL/tcl/utils.tk. The first three routines build the Tcl/Tk structures with the specified callbacks. The last command binds execution of the command mycode(update) to the pressing of Return in column 1 (the entry column) of the LabelEntryColumns widget. The "global MyCode" line is important for accessing the global variable MyCode(Number), instead of a new local variable that would be otherwise automatically created by Tcl/Tk. Recall that the global variable MyCode(Number) shadows a Postmaster entry, which will be used by our example computational code. As with the note in section VII.B.8, if you patch the above into a file, make sure you remove the preceding white space, to conform to proper Tcl/Tk syntax. 3) To finish the window, you should write enter, leave, and update procedures for the mycode window. These should all go in the file mycode.tk, and should look like the following: proc mycode(enter) {} { pm_to_tcl MyCode } proc mycode(leave) {} { tcl_to_pm MyCode } proc mycode(update) {} { tcl_to_pm MyCode pm_to_tcl MyCode } These procedures update the Postmaster and/or the Tcl/Tk interface entries when the user leaves, enters, or presses return in the mycode window. 4) In the previous two steps, you created the procedures that build and update the window. In the procedure mycode(build), it says: {"My Code" mycode(compute_mycode) } The build routine interprets this as saying that when you click the "My Code" button, the routine mycode(compute_mycode) should be called. Write this procedure also in the file mycode.tk: proc mycode(compute_mycode) {} { begin_wait "Computing my code..." tcl_to_pm MyCode pm_to_tcl MyCode pm EXEC MyCode.compute_mycode end_wait "Done..." } The begin_wait and end_wait commands create messages on DsTool's Command window to inform the user of the progress of the calculation. The tcl_to_pm and pm_to_tcl commands update the Postmaster and the Tcl/Tk variables as described above. Finally, the line "pm EXEC MyCode.compute_mycode" tells the Postmaster to execute the command MyCode.compute_mycode. 5) The next step is to let the Postmaster know what the objects MyCode and MyCode.compute_mycode are. This should be done in the procedure mycode_install() in the new file $MY_DSTOOL/MYCODE/mycode.c. The file should look like: /* file: mycode.c */ #include #include #include #include #include "mycode.h" void mycode_install() { fprintf(stderr, "Executing mycode_install.\n"); /* create elements for window values in the Postmaster */ pm(CREATE_OBJ, "MyCode", CREATE_ELEM, "MyCode.compute_mycode", FNCT, CREATE_ELEM, "MyCode.Number", INT, NULL); /* initialize the function pointers in the Postmaster */ pm(INIT, "MyCode.compute_mycode", PUT, "MyCode.compute_mycode", mycode_compute, NULL); } The Postmaster will now execute the procedure mycode_compute when "pm EXEC MyCode.compute_mycode" is called, which happens when the button "My Code" is pressed. In DsTool, each Postmaster object has an installation routine similar to the one above, but most are significantly more complex. 6) It remains to write the procedure mycode_compute, which is the actual computational code you want to execute. For our example, we simply put it inside mycode.c. Recall the declaration of the procedure in mycode.h: void mycode_compute(void); Please note that Postmaster functions cannot have any parameters. If information from DsTool is necessary, the procedure should get this information from the Postmaster, via the MyCode or other Postmaster objects. For demonstration, we just print something. Edit $MY_DSTOOL/MYCODE/mycode.c by adding: void mycode_compute() { fprintf(stderr, "Hello World\n"); } 7) Recompile your personal version of DsTool with the command "make all" and see if it works! VII.E. Accessing data already computed by DsTool ------------------------------------------------ Of course you want your computational code to interact with DsTool. For example, you would like to use the function defined in mymodel_def.c, or use the fixed points that you computed in DsTool. To give some intuition what needs to be done, we give an example. The procedure executed when you click on "My Code" is going to print the eigenvectors of the fixed point specified by the read-write text field in the MyCode window (or nothing, if there haven't been enough fixed points computed). 1) Just as you created the object "MyCode" that knows about all the things you implemented so far, there is an object "Model", defined in $DSTOOL/src/init/model_install.c, that knows all parameters in the current model, that is, in mymodel_def.c. Similarly, the object "Fixed", defined in $DSTOOL/src/fixed/fixed_install.c, knows the parameters in the Fixed Points window, etc. Moreover, there is an object "Memory" that has access to the data from previous computations in DsTool. The file mycode.c needs the following additional include files: #include "memory.h" /* memory handling routines */ #include "utilities.h" /* definitions used by tcl */ #include "math_utils.h" /* more definition for tcl */ #include "flow.h" /* used for computing the jacobian */ #include "eigen.h" /* eigenvalue routines -- includes rg */ and the following definition: #define WORK_SIZE 5000 2) Our example function mycode_compute is basically a combination of the routines in $DSTOOL/src/browser/browser.c and $DSTOOL/src/cont/cont_state.c. First we find the right fixed point, then compute its eigenvalues and print them out. Replace the trivial version of mycode_compute above with: void mycode_compute(void) { int i,j,mode,ierror,*iwork,status = 0; int format = *((int *) pm(GET, "Defaults.Precision", NULL)); memory m = (memory) pm(GET, "Memory.Fixed", NULL); int n_varb, n_param, n_funcs, n_flows, number, *p_color, *p_bi; double *fd_step,*p_varb, *p_param, *p_bd,*wr,*wi, **jac2,*dwork,**B; Manifold *manifold; /* check for NULL memory item */ if (m == NULL) return; n_varb = *((int *) pm(GET, "Model.Varb_Dim", NULL))-1; n_param = *((int *) pm(GET, "Model.Param_Dim", NULL)); n_funcs = *((int *) pm(GET, "Model.Funct_Dim", NULL)); n_flows = memory_nflows(m); /* find correct fixed point */ number = *((int *) pm(GET, "MyCode.Number", NULL)); if ((number < 1) || (number > n_flows)) return; status = memory_reset_read(m); if (status) return; status = memory_set_read(m, number, 1, 1, NULL, NULL, NULL, NULL, NULL, NULL); if (status) return; /* read it */ status = memory_read_next_point(m, &p_varb, &p_param, &p_color, &p_bd, &p_bi); /* Set up manifold structure */ manifold = (Manifold *) calloc(1, sizeof(Manifold)); manifold->periodic_varb = ivector(0,n_varb); manifold->period_start = dvector(0,n_varb); manifold->period_end = dvector(0,n_varb); manifold->type = *((int *) pm( GET, "Manifold.Type", NULL )); pm( GET_LIST, "Manifold.Periodic_Varb", 0, n_varb-1, manifold->periodic_varb, NULL); pm( GET_LIST, "Manifold.Period_Start", 0, n_varb-1, manifold->period_start, NULL); pm( GET_LIST, "Manifold.Period_End", 0, n_varb-1, manifold->period_end, NULL); wr = dvector(0,n_varb); wi = dvector(0,n_varb); jac2 = dmatrix(0,n_varb+1,0,n_varb+1); fd_step = dvector(0,n_varb); dwork = dvector(0,WORK_SIZE); for(i=0; iperiodic_varb,0,n_varb); free_dvector(manifold->period_start,0,n_varb); free_dvector(manifold->period_end,0,n_varb); free(manifold); } 3) Recompile your personal version of DsTool with the command "make all" and see if it works! VIII. Adding your own Tcl/Tk code to DsTool ------------------------------------------- You may want to add your own Tcl/Tk code to DsTool for many reasons, ranging from simply wanting to change the default color scheme to wanting to create your own windows to control computations. VIII.A. Changing default colors and fonts ----------------------------------------- The simplest kind of customization is changing default colors or fonts. Since the Tcl/Tk procedure my_app_init (in the file $MY_DSTOOL/tcl/my_app_init.tcl) is called *after* app_init_tk and app_init_tcl, changes to default values of global variables which are initialized on start-up can be made by adding the appropriate lines to my_app_init. In the case of colors or fonts, a command like: set COLOR(views) white will change the default background color for twoD and oneD view windows to white. Note that changes to default colors and fonts must be made *before* the line "window(open) cmd", so that the Command window is built with the new defaults. Fonts should be changed to font names in standard notation, and colors can be changed to either standard color names or hexadecimal notation. The complete color and font lists (with defaults in parentheses) are: Colors: ------- COLOR(bg) -- widget background color (#acf) COLOR(abg) -- active background color (#57f) COLOR(hbg) -- not currently used COLOR(views) -- background color for view windows (black) COLOR(entry) -- entry widget background color (white) COLOR(info) -- info (messages/entries) color (blue) Fonts: ------ FONT(button) -- button font (-Adobe-Helvetica-Medium-R-Normal--*-120-*) FONT(label) -- label font (-Adobe-Helvetica-Medium-R-Normal--*-120-*) FONT(biglabel) -- big label font (-Adobe-Helvetica-Medium-R-Normal--*-140-*) FONT(boldlabel) -- bold label font (-Adobe-Helvetica-Bold-R-Normal--*-120-*) VIII.B. Changing external program calls --------------------------------------- While DsTool is mostly self-contained, it is designed to use external programs for some tasks. Specifically, DsTool calls external programs for viewing images, postscript files, and html documentation, and for creating GIF and TIFF files (actually, converting to those formats from X11 bitmaps). The global Tcl/Tk array EXT contains the calls for all of the external programs that DsTool will use. It contains the following entries (defaults in brackets): EXT(imgview) -- image (GIF, TIFF, PPM, X11 Bitmap) viewer [xv] EXT(psview) -- postscript viewer [gs] EXT(convertgif) -- image conversion tool, PPM -> GIF [convert] EXT(converttiff)-- image conversion tool, PPM -> TIFF [convert] EXT(browser) -- web browser [netscape] and the actual default calls are (from app_init.tk): set EXT(browser) {exec netscape $Html(Url) &} set EXT(convertgif) {exec convert "ppm:$Snap(Directory)/snaptempgif" \ "tiff:$Snap(Directory)/$Snap(Filename)"} set EXT(converttiff) {exec convert "ppm:$Snap(Directory)/snaptemptiff" \ "tiff:$Snap(Directory)/$Snap(Filename)"} set EXT(imgview) {exec xv $fpath &} set EXT(psview) {exec gs $fpath &} To change an external program, you need to change the complete call. A convenient place to do this is the procedure my_app_init (in the file $MY_DSTOOL/tcl/my_app_init.tcl). The new calls can be placed anywhere in that procedure, since the Command window does not use any external programs. For example, the lines set EXT(psview) {exec imagetool $fpath &} set EXT(convertgif) {exec ppmtogif "$Snap(Directory)/snaptempgif" > \ "$Snap(Directory)/$Snap(Filename)"} would change the external postscript viewer to imagetool, and the conversion (PPM->GIF) utility to ppmtogif. The syntax for these calls is important! The call needs to be enclosed in curly braces. This keeps it from being evaluated until it is needed. Eventually, it will be called using the "eval" command. The "exec" command tells Tcl/Tk to execute the command which follows. The remainder of the line should be the proper syntax for the external program, with dollar signs in front of names of files which are to be accessed (e.g. fname for postscript and image files, and Html(Url) for URLs). The quotes around the filenames are important, as they force Tcl/Tk to evaluate the contained variables. Without them, ppmtogif would be looking for the file $Save(Directory)/snaptempgif, and would not be able to find it. The trailing ampersand is important so that DsTool doesn't lose control to the external program. Note that the syntax for the above calls had to be modified to conform to the appropriate calling syntax for the new programs. VIII.C. Changing default values of other global variables --------------------------------------------------------- If you want to change another global variable, you should: 1) be very careful that you know what you're doing and how it will affect the program. For example, you may want to change the Postmaster entry Defaults.Precision, which controls the number of digits which are displayed in the Tk windows, among other things. This is *not* advisable. Due to the frequent interaction between Tcl/Tk and the Postmaster (via the tcl_to_pm and pm_to_tcl commands), you will be introducing (possibly large) roundoff errors into the Postmaster. Depending upon your application, these errors may not be significant, but you should be aware of them. 2) be sure to declare it a global variable via the "global" statement, or if it is part of an array, declare the array as a global variable (as with COLOR, FONT, etc). If you fail to do this, Tcl/Tk will introduce a local variable with the same name, and the global variable will not be changed. 3) be aware that my_app_init is called only once. The importance of this is that if you wish to change default values for variables which are initialized when windows are opened, you may not be able to change them. You should check the appropriate Tcl/Tk script to see how the variable is initialized. If it is initialized from the Postmaster, you can change the default in the Postmaster by issuing the proper Postmaster commands in my_app_init. For example, to have the auto-scaling factor default to 1.0, put the command "pm PUT Defaults.Plot_Scale_Factor 1.0" in my_app_init. When the Defaults window is opened, the auto-scaling factor will be 1.0. However, if the variable is initialized in a different way, you may not be able to change it easily. VIII.D. Creating Tcl/Tk windows in DsTool ----------------------------------------- The code for all of the user interface windows is located in the $DSTOOL/tcl directory. Several utility routines are in utils.tk and utils.tcl. These routines are used to create windows and widgets. It is recommended that you use these routines in order to preserve continuity of the interface features. For more information on Tcl/Tk and the use and design of widgets, a good on-line reference is available at: http://www.sco.com/Technology/tcl/Tcl.html There are two basic steps involved in creating a window: 1) Write the user interface code in Tcl/Tk. Create a file, say my_panel.tk. This file must contain a procedure called my_panel(build) that contains the code for building the user interface components of the window. Each window should have a dismiss button to close it. In addition, if Postmaster entries are involved, you should write procedures my_panel(enter), my_panel(leave), and my_panel(update), which update the Tcl/Tk variables to their Postmaster counterparts, and vice versa. 2) Create a menu entry for the window. The command "cmd(add_to_panels) 'Example' 'window(open) my_panel'" adds an entry to the panel list which has label "Example" and calls "window(open) my_panel" when selected. This line can either be added to my_app_init or called from mymodel(install), as described in sections VII.B and VII.C. The command "window(open) my_panel" calls my_panel(build) to build the window. An example: Selected Point window: The Selected Point window code is in $DSTOOL/tcl/selected.tk, and contains six procedures: selected(build) -- builds the window. selected(update) -- updates the Postmaster and Tcl/Tk variables when the user presses the Return key. selected(enter) -- updates the Postmaster and Tcl/Tk variables when the user enters the window. selected(leave) -- updates the Postmaster and Tcl/Tk variables when the user leaves the window. selected(copy) -- copies the final column to the initial column. selected(save_point) -- saves the initial column point in the Postmaster's memory. For illustrative purposes, we now give a detailed description of some of the procedures which are used in the Selected Point window. The first two are utility routines; the next three are postmaster communication routines, and the final five are for building the objects in the window. array_to_list Model Varb_Names Converts an array object into a list object. The list returned contains the entries Model(Varb_Names,) where runs over all valid entries. message_show "Initial point saved." Displays the message "Initial point saved." in the command window. tcl_to_pm Selected Updates the Tcl/Tk variables in the global array Selected from their corresponding Postmaster entries. pm EXEC Selected.Copy Executes the Postmaster command Selected.Copy, which copies the point in the final point memory (of the Postmaster's Selected Point object) into the initial point memory. pm_to_tcl Selected Memory Updates the entries in the Postmaster objects Selected and Memory from their corresponding Tcl/Tk global variables. build_Title $name "Selected point" Builds the title of the window on the top frame border. The name of the parent widget is $name, and "Selected point" is the string used for the title. build_DismissButtonbar $name dbbar "window(dismiss) selected" \ {"Copy final to initial" selected(copy) } \ {"Save point" selected(save_point)} Builds a dismiss button bar at the bottom of the window. The button bar has a dismiss button, which calls "window(dismiss) selected" when pressed. In addition, the button bar contains a button labelled "Copy final to initial", which calls selected(copy) when pressed, and a button labelled "Save point", which calls selected(save_point) when pressed. The name of the parent window is $name, and $name.dbbar will be the name of the button bar. build_CmdFrame $name cmd Builds a command frame to contain other widgets. The name of the parent frame is $name, and $name.cmd will be the name of the command frame. build_LabelEntryColumnsScroll $cmd le0 250 \ [list label " " [ concat \ [array_to_list Model Varb_Names] \ [array_to_list Model Param_Names] ] \ ] \ [list dentry "Initial" [ concat \ [array_to_list Selected Varb_Ic] \ [array_to_list Selected Param_Ic] ] \ ] \ [list dlabel "Final" [ concat \ [array_to_list Selected Varb_Fc] \ [array_to_list Selected Param_Fc] ] \ ] Builds a scrollable widget consisting of three columns. The first column has an empty header label, and then a list of the variables and parameters from the current system. The second column is labelled "Initial", and consists of the Tcl/Tk variables Selected(Varb_Ic,) and Selected(Param_Ic,). The last column is labelled "Final", and consists of the Tcl/Tk variables Selected(Varb_Fc,) and Selected(Param_Ic,). The name of the parent widget is $cmd, and $cmd.le0 will be the name of the new widget. The number 250 specifies the height of the widget. bind_LabelEntryColumns $cmd.le0 1 selected(update) Binds the execution of a command to a key press in a column of the LabelEntryColumns widget $cmd.le0. In this case, the column is 1 (the "initial" column), the key is Return, and the command which is executed is the Tcl/Tk procedure selected(update).