When you start working on your very own models, improve your modeling experience by knowing about the following tips and tricks.
- Enable versioning, to have older versions available.
- Enable automatic opening of last model, to get your model loaded with SeSAm.
- Enable "Debug -> Assert -> Global Assert", to get a chance to notice Errors that may occur during your work with the model.
- Once you model more than small tests, you really should increase the memory usage. (Check the "About SeSAm" window for current memory consumption.) You need to add the -Xmx virtual machine parameter to the beginning of the SeSAm startup script. For example to allow the use of 1024 MB of RAM:
- # Uncomment the following line to add additional VM parameters
Under *nix/linux operating systems:
- Increase the double click time default in X server, to make the double click be more gentle on your fingers. (see FAQ)
- Enable error.log output to console (in the SeSAm shellscript: set log file to a non-writeable location like "/sesam-error.log.unwritable") and keep the console from were you run SeSAm always visible, so you'll notice any error/log output.
Save your model often and keep previous versions. You can use the versioning feature of SeSAm to automatically increment the model version when saving (Settings ->General ->Enable versioning). You can open and re-save the model in a second window, before closing your first SeSAm window, to make sure your model is saved in a clean state after you worked worth a day. Make this a simple habit. Failures may come from copy/pasting, changing functions, or leaving functions with unset null values. Please report any bug if you can catch it.)
Always use double-clicks to open functions
Using right-click -> Change Value to open an "Edit Function" window can lead to an unresposive OK button. (Bug #208 present as of version 2.5.1)
Finding appropriate/available primitives
Click on the small face on the right, below every functions list. The dialog that opens then gives information on the functions and allows to search primitives in a convenient way. If the function you are looking for is not available, check if none of the primitives higher in the tree has been created with a wrong type. (i.e. a specific SimObject, IsEmpty needs List<Number> not Number, ...)
Avoiding runtime "null" errors
To avoid "null" errors, make sure all variables are properly initialized before they are accessed in your simulation. Catch empty lists with (if IsEmpty(...), 0, else Get...) or empty hashtable entries with (if ContainsKey...) before accessing them.
Local variables trick
Use DoWith to create a list of local variables within a function/action. (Add list child items by right clicking on createlist.) Those variables can be accessed with Get/SetNth on the list within the function. (Using DoWith with a single (non-list) variable and Get/SetVariable does not seem to work.)
A clipboard for editing of actions and functions
You can create a dedicated User Function containing a Block and SwallowReturnValue child items and use it as a Clipboard or "ToDo/ScratchPad" for code trees. However if you want to copy/paste within a function, it is best to temporarily create a "Block" within that function and do all copying within the function. That way all the bindings of the local variable names will stay valid.
Be careful with refactoring features
The refactoring functionality seems to have problems when the modified object is referenced (called) somewhere in the model. Whenever a refactoring feature produced errors (closely watch the console/error.log!) or produced an unloadable corrupted xml model file: Create a bug report with a demo model if you found a new bug, and importantly, revert to your pre refactoring backup copy! Then resort to manually creating copies of the user types/variables/functions you want to change, rename the originals to "OLD: ...", adapt the new ones as you want, then successively switch all references to "OLD: ..." over to new ones and finaly search and delete all "OLD:"s (do saving/opening tests in between.) (Bug #210, 214, 219)
Refactoring example: extracting a method and moving it into a user feature
First extract, then move extraced user function to the user feature, then close the function editor window, reopen the function editor with the reference and manually reselect the now extracted function for use within the function, close function editor, save. (The model gets corrupted if you don't do a manual re-selection!)
GetVariable not only copies the value but gets the object
Changes will be made to the variable (of the entity).
ForTimes loop range
ForTimes(from:1 to: 3) runs for 1 and 2 only. Use 0-3 and it will run 0,1,2 (zero is the first position in lists/strings).
Fractions like 0.2 have only a rounded binary representation in the double type. To avoid rounding errors up to the second digit in calculations you can do the calculations in 1/100dth (i.e. like cents). When index variables (i.e. ForTimes) are of type double (the default in sesam) never use fractional increments in order to avoid (binary<->decimal) rounding errors that can lead to failing (Equal) evaluations. Using the double type instead of the integer type however has the benefit of a larger range. It is therefor ok that IndexOf returns a double and not an integer. (->larger range)
Lists vs. Hash-tables
In general use Lists if the order is important, and use Hash-tables for fast lookup.
- A simple list stores no keys.
- A simple hash-table stores no definable order.
- Option: To combine both use a hash-table and an ordered list of keys.
Iterators are different from lists
Iterators get eaten! With an iterator, after a single GetFirst or GetNth access, the Nth+1 element will be the first item of the iterator (if any element remains in the iterator). An iterator is empty after it has iterated through completely once.
In cases where only a subset of elements needs to be accessed, the computational load can be reduced by using iterators as argument type (instead of lists) and getting (and if necessary saving) only the amount of iterator elements needed (i.g. with GetFirst from within a While loop). See also WikiTutorial/OptimizedIteratorFunctions.
One simulation run error can trigger other errors
Doing only a "reset" of the simulation run after an error occurred may trigger other errors in the following run. Especially if a stack overflow occurred, completely restart SeSAm.
GetCurrentSimObject does not work in resources (passive entities)
It is not possible to use GetCurrentSimObject in variable init or "next value" functions of resources. Workaround: add an autoupdate variable to an active entity (like the world) that will update the passive entities.
"was not of type List" Error
This error may be thrown when setting a List with SetVariable(AsList(<Iterator>)) at once and the Iterator is empty? (no agents in tick 1) -> Use ForElements (Push...) instead.
- Is it at all possible to set a complete List variable at once with SetVariable?
- Same error when Analysis tries to read an empty list? -> Workaround: Use if IsEmpty(...), 0, else Get... in analysis items.
(Why this first only gave an error if logging was active (else only the note appeared in the console behind the world variable), but later the error was always thrown.)
Initialization takes some time
During the first couple of ticks the initialization has to propagate. (i.e.: Agents created by initialization actions of agents (or by the world) in tick one won't do any reasoning before the analysis of tick one is calculated and tick two has started.)
Calculations of entities (resource variables, agents, analysis and the world)
Be aware that the world does calculations during or at the beginning of a tick, agents in random order during a tick, and the analysis items are calculated at the end of a tick. So do not mix for example world calculated numbers with analysis calculated numbers without considering this. Keep these lags in mind when looking at live object attributes in the simulation run. (A step stops at the end of a time tick, right after analysis calculation, but time has passed since world stats calculation.)
Data calculated by entities can only be valid during their respective runtime calculation. They may not be valid any more when read by other entities (agents, the world, analysis items).
Therefore analysis functions for example may need to recalculate required agent variables (states) before computing correct statistics.
Make them universal
To create universal analysis calculations that will work with different simulations/agents, always refer to Feature variables and let the Analysis item GetAllObjectsWithFeature instead of GetAllObjectsOfType.
Program and compute the analysis only once
To need just one calculation per analysis and tick, add a user feature with statistics_variables (of type list) and update functions like block(clear(statistics_listvar), calculate(statistics_listvar)) to the world. Then in the first analysis item use progn(update(statistics_listvar), return(statistics_listvar). I.e. bar chart items for example can return() the value by reading the list variable with "If IsEmpty then 0 else PopAndReturnFirst". With this any following item just returns the subsequent value or zero. If the world calculates the statistics_listvar the calculation can be used by multiple analysis items.
Analysis calculation in world?
Be aware that any calculations carried out by the world takes place during or at the beginning of a tick, while the analysis items are calculated at the end of a tick. So do not mix world calculated analysises with regular analysis calculations without considering this.
Fixed graph range
You can set the autorange feature to a fixed range by right-clicking the graph properties during the simulation run. A workaround for a permanently fixed range: Add an additional large enough bar or time series item to the analysis (possibly a constant or max value).
Create a Test Simulation for each user feature
Create test simulations with dedicated testing agents that make defined use of a feature and Assert() expected conditions and changes. They will reassure you that a feature is (still) working as expected while you make changes to your model.
Make your test simulations independent from user feature defaults
If your tests depend on some user feature defaults, adjusting the defaults for your experimentation simulations would make your tests fail. Your test simulations should therefore use own defaults (fixtures). Your user features should contain a function that merely holds (in a Block with Print and SwallowReturnValue statements) all the "own default value" fixtures for that feature's software test simulation (as a saved backup). (The "own default values" of a simulation get lost if you change the world in the situation, for example.)