Developer's Manual
From ASCEND
I thought we should make a start on documenting the architecture of the ASCEND system. This will help us to be clear about the boundaries of the functionality of the different modules within ASCEND, which will help us to write better, self-contained, resuable modules, and also be of use to new developers trying to familiarise themselves with the system.
In addition to these notes, there is a lot of descriptive material embedded in the source code, accessible via doxygen at http://ascend.cheme.cmu.edu/dox/
See also the user's manual (1 MB .pdf).
Contents |
[edit] Overview
Ascend includes two main parts, the 'compiler' and the 'solver'. These are organised in directories in the base/generic/ subdirectory of the source distribution. There are also 'utilities', 'general' and 'packages' subdirectories.
The utilities directory is intended to contain utility routines that only really of use to ASCEND but which are linked specifically neither to the compiler nor the solver. The general directory contains general purpose routines which would be of use in any similar software project, with no specific dependencies on the other ASCEND code.
The packages directory contains a few so called 'internal packages'. These provide functionality that is outside the compiler and solver, but which is important for basic standard-case use of ASCEND. As example is the code which performs the 'ClearAll' function to reset the values the 'fixed' flag.
Finally, there is the ASCEND GUI. There is currently a Tcl/Tk GUI as well as a PyGTK GUI. These can be used independently; there is not interdependency.
[edit] Compiler
The compiler includes a parser which constructs a types hierarchy which is stored in a library. When a model (or instance) of a specific type is instantiated, it creates an Instance tree, the top node of which is a 'simulation' instance, and underneath which is a simulation root corresponding in structure to the type that was instantiated.
Something about anonymous merge here.
Relations are stored in the compiler in the following data structures:
[edit] Relationships
The following explicit relationships are tracked by the Compiler
- Part-whole (model members), including relations in models.
- structural isomorphism (are-alike clique).
- structural identity (are-the-same clique, parameter passing, aliasing).
- continuous variable referenced by equation or boundary.
- discrete variable referenced by logical equations and WHENs.
- discrete constants determining compilation alternatives (SWITCH).
- satisfaction of a nonlinear equation/inequality to a tolerance.
- units and dimensionality of variables, equations.
- a variety of stuff about sets of discrete values.
- variable values.
- interpretation of names in methods in one or more object scopes.
The following implicit relations are tracked:
- bounds, scaling, ode-classification on variables, scaling on relations.
- semi-continuous variable treatment for mixed-IP (solver_semi).
- relation inclusion in a model to be solved (.included, WHEN).
- model inclusion (WHEN).
- time dependent/event state-transition would be if we had the solver to manage it.
- objective functions.
- output file logging.
Note that the implicit (soft) nature of the relationships is precisely what we intended. It serves to separate model definition (which is simply listing a set of equations we assert to be true) from the model solution process. As soon as one gets overly aggressive, the compiler turns into a solver and this is a bad idea.
[edit] Analyser
The analyze.c code lies in the base/generic/system directory and is reponsible for 'visiting' the instance hierarchy maintained in memory by the Compiler and turning it into a 'flat' system that can be passed on the the Solvers.
First, the GUI calls the routine system_build which allocates space for slv_system_t object and then calls analyze_make_problem.
[edit] analyze_make_problem
This is where the main work begins.
Space is allocated for a temporary object of type problem_t. First the instance hierarchy is visited by the CountStuffInTree method, which accumulates counts of the various types of variables.
[edit] analyze_make_master_lists
Next there is a called to analyze_make_master_lists, which allocates memory for lists of each type of variable (according the counts made), then calls PushInterfacePtrs, which seems to somehow fish out the correct links to items in the instance hierarchy and create the interim items in the lists inside the problem_t object. This is where variables are flagged as FIXED and FREE, etc, and where derivatives are identified as such?
(a bit vague here) After that CollectRelsAndWhens. This seems to do the job of organising the lists of WHEN statements, including the possibility of nested and conditional parts of the moment.
Next all variables in the found relations are marked incident. Variables found in objective relations are also marked incident, as are real variables in CONDITIONAL relations, and boolean variables in logical relations and CONDITIONAL logical relations. WHENs are numbered.
Variables that did not get marked 'incident' in the above process are moved off to separate 'unattached' lists. This completes the call to analyze_make_master_lists.
[edit] analyze_make_solvers_lists
Next, analyze_make_solvers_lists is called. (This seems to be a bit muddy: stuff seems to be done here to both the master list as well as the solver's list.)
[edit] system_generate_diffvars
Next system_generate_diffvars is called, which if any derivatives were found, creates a set of derivative chains. This gives the relationship between variables declared in the model, to identify which vars are supposed to be derivatives of which other vars.
[edit] analyze_configure_system
The routine analyze_configure_system does the job of passing off the lists from the problem_t and giving them to the slv_system_t. NULLs are assigned in the problem_t in order to make it clear that a list has been 'disowned' by the temporary problem_t structure.
PopInterfacePtrs?
[edit] analyze_free_lists
Finally analyze_free_lists cleans up any not-disowned stuff still in the problem_t structure. The result is that the slv_system_t contains everything you need to solve your problem, but in such a way that you don't need to 'talk' directly to the ASCEND instance hierarch. This way your solver only needs to speak the reduced vocabulary of the slv_system_t and can safely ignore the implementation details of the Compiler.
[edit] Solver
The analysis module is kept in the solver directory and translates the ASCEND instance tree into a flat set of lists of relations and variables (there are various types of variables; the main one is the real-valued solver_var.
The analysis module hands off the slv_system_t to the slv server and allows it to be queried by one of the slv clients that actually does the solving work.
The slv server also implements an general-purpose way of getting and setting solver parameters for the various solvers.
The solver performs blocking of equations. This means dividing the overall system into sequences of subsystems that can be solved without knowledge of any other as-yet-unsolved variables. The allows the Newton iteration to work on smaller matrices, for great speed and improved convergence.
[edit] Integrator
The integrator sits aside the solver and coordinates a time-stepping process that may be imeplemented using whatever additional logic you want. We currently have the LSODE integrator as well as a pre-alpha implementation with the IDA solver.
The Integrator API permits you to implement your own blocking/solving algorithms, or to re-use the main solvers, as desired. The LSODE implementation currently uses the ASCEND solver to perform block solution. The IDA implementation isn't quite as clever.
[edit] Utility and General Routines
There are various list and string and collection 'classes' in these directories that support the solver and compiler functionality, as well as some of the GUI functions.
[edit] External Libraries
ExternalLibraries are supported. You can use both 'black box' external relations (for computation) or 'external methods' for doing special things to the model hierarchy (model setup and reporting).
The architecture is there for dlopening of whatever module you need at runtime. And example of how to build and link an external package is given in the models/johnpye/extfn directory.
[edit] GUI interfaces
GUIs talk to the solver and the compiler depending on what they're doing. All GUI function should be designed such that the internals of the data structures used by the solver and or compiler are not depended on: a proper API.
The Tcl/Tk interface is implemented via directly-coded access to the Tcl/Tk interpreter using the C-language bindings provided by Tcl. The Tk graphical interface is defined via a series of Tcl scripts. This implements full access to the ASCEND C functionality, but expanding the interface is rather laborious due to leg-work of transferring string and numerical values to and from Tcl/Tk.
The Python (PyGTK) interface is implemented as a thin C++ layer on top of the core C code. This C++ layer is then wrapped with SWIG to provide an object-oriented interface to the ASCEND library, types, simulation, instance tree, variables and relations, and so on. There is only mimimal code required to interface the C++ layer with Python, since SWIG does most of this work (and also permits ASCEND to be linked to other languages if desired).
[edit] PyGTK interface
The PyGTK interface (see PygtkScreenshots) implements a fully object-oriented interface on top of the C language API provided by the base code.
An important distinction in the PyGTK code is that the 'library', which holds details of the currently open models and external packages, etc, is considered to be subordinate to the model which is being worked on. In the Tcl/Tk GUI, the library is an entity that stands aside regardless of the currently instantiated model.
Another distinction is that the 'solver' in the PyGTK interface is kept as a somewhat distributed concept. Unlike the Tcl/Tk interface, there is not a dedicated 'solver' window. You can edit the parameters for the solver via the menus, and you can examine its blocking and solving via the 'diagnose' window. It was thought that by tying the solver more tightly with the model browser it would lead to a more intuitive interface.

