123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- 1. Language Implementation Details
- The ode input language is implemented with a bison-generated parser and a
- flex-generated lexical analyzer. The benefits of this approach are great.
- The original version of the basic recognizer was completed in about two
- days. And adding comments to the language required that exactly one line
- of the lexical analyzer source code be changed.
- Differential equations are compiled into linked lists of stack operations,
- one list per dynamic variable. The function evaluator (eval) traverses one
- of these lists, applying the indicated operations to a small operand stack.
- At the completion of the traverse, the new derivative is on top of the
- stack. The routine pops and returns this result. Another routine (field)
- is called by the numerical routines whenever a re-evaluation of the
- gradient is required. It calls eval() for each dynamic variable in turn.
- Changes to the language involve modifications to the bison grammar, the
- flex rules, and/or the semantics stored with the grammar and rules. The
- steps required to add a new builtin function, for example, are (aside from
- writing the function itself) as follows.
- (1) Invent a new terminal symbol name for the reserved word.
- (2) Add the new reserved word to the flex rules.
- (3) Add the new productions to the right-hand side of
- "expr" in the grammar, together with whatever semantic
- action is required (using the included macros if possible).
- (4) Define a new value for exper and add the stack code to eval.
- 2. Numerical Schemes
- The numerical and language procedures were designed in accordance with the
- Jesuit maxim. The major elements manipulated by both are the symbol table,
- and the functions field() and printq(). Addition of a new numerical scheme
- requires that the numerical routine be written, and perhaps that some
- fields inside the symbol table entry be expanded or added. None of these
- changes affects the performance or compromise the integrity of the parser,
- although they may change the space-cost of a dynamic variable. Similarly,
- modifications to the lexical analyzer or the grammar only affect the parse
- tables and related data structures, and have no effect on the numerical
- routines.
- At each step the numerical scheme fills each symbol table entry with
- relevant values. Then a call to field() evaluates every differential
- equation and puts the result in the symbol table element syrime. The
- evaluator gets current values for the dynamics from the field syalue. Both
- syalue and syrime are in some sense temporary variables. All correct
- results eventually find their way into syal[0] and syri[0]; these are never
- touched by the language processor. Thus the real interface between the
- language and numerical procedures is merely syalue and syrime. It is
- important to maintain meaningful values in syalue and syrime, since a
- floating-point exception may disrupt the control flow at any moment. It is
- recommended that the pointer fsp be used in traversing the symbol table, so
- that arithmetic exceptions can be diagnosed as accurately as possible.
- Fourth and fifth order schemes were chosen as the best compromise between
- speed and accuracy. The adaptive stepsize has proved a boon in both areas.
- 3. Details of Error and Fault Recovery
- Command-line errors are diagnosed at initialization time and are generally
- fatal.
- Errors in the input stream are diagnosed during the lexical scan or at
- parse time. They elicit an appropriate message. The error recovery method
- used in the parser is the vanilla Yacc scheme. (see S. Johnson, "Yacc: Yet
- Another Compiler-Compiler", 1978, pp. 16-18).
- Run-time errors are detected by the function evaluator, the numerical
- routines, or the floating-point unit hardware. In each case a message is
- printed describing the error, and ode returns to a known state awaiting
- input. The current step is abandoned, but all variable values are left
- reflecting the state of the machine just prior to the fault. All of the
- numerical schemes scan the symbol table, computing values for each dynamic
- variable, using a common pointer known to the error recovery routines named
- fsp. This pointer provides a way of identifying the offending dynamic
- variable when a trap occurs.
- 4. Space Utilization
- The data space used in numerically solving a system of ODEs consists of
- both fixed and dynamic regions. The fixed space is used efficiently
- regardless of the complexity of the problem being solved. The dynamic
- space contains the symbol table, the expression lists, and several other
- data structures whose sizes vary. The space for these structures comes out
- of a common pool; they play against each other.
- Every identifier in an ODE description requires 254 bytes; this space can
- never be reclaimed. Each operator in an equation costs 14 bytes. For the
- sake of speed, some operators are expanded into a sequence of simpler
- operations, each of which requires 14 bytes. In particular, some common
- powers are reduced to sequences of square roots, squares, cubes, and
- inversions. The extent of these transformations can be seen with the help
- of the `examine' statement. The operator space for an equation is
- reclaimed whenever a new equation is defined for that particular dynamic
- variable. Each lexical token in the most complex statement costs 10 bytes,
- but this space is reclaimed during the scan. Thus, the most complex
- statement circumscribes this requirement. Each element in a `print'
- statement costs 6 bytes. Upon execution of the next `print' statement this
- space is reclaimed.
- In addition to these absolute space limitations, the maximum depth of the
- operand stack used for evaluating derivatives is 30 cells. This limit
- could conceivably be exceeded for a very complex differential equation.
- The solution is to simplify the equation if possible, and where not, to
- recompile ode with a larger operand stack.
|