-
Notifications
You must be signed in to change notification settings - Fork 3
For Architects of Ascent
- Straightforward: software should be easy to implement with few common classes.
- Readable: readability in the code is more important than reducing lines of code.
- Small classes: try to maintain less than 500 lines of code per class.
- Fast running: insofar as to not sacrifice dynamic behavior.
- Generic: use templates where possible.
- Modular: modules should be easily reused and extensible.
- Multi-threading: Supports multi-threading at multiple levels of simulation.
- Dynamic: must support dynamic simulation with respect to module additions, deletions, linking, and ordering. And, dynamic behavior must behave properly with numerical integration.
- Hides simulation design assistance: the main focus of the Ascent core is to add improvements that do not require user implementation or that reduce user coding. The aim is not to produce a mountain of tools and options, which add complexity.
- For algorithms, include them in a manner that interfaces best with the Ascent architecture and which are as generic as possible. Specific application algorithms should be in separate Ascent-Community repositories.
Because graph theory cannot be applied without knowing all edges between vertices (i.e. connections between modules). If the ordering is to be fully dynamic then the edges are discovered and not known. Using a dynamic loop that discovers edges makes it possible for the simulation designer to create highly complex systems without needing to know all module connections at all phases. This allows simulations to be run that would otherwise be impractical for the implementer to define.
Given that discrete variables can be handled in an if (sample())
block within update(), it may seem that postcalc() is unnecessary. However, if accumulated values must be handled post integration, then discrete computations within the update() method won't work, because reset() has been called. Another reason for postcalc() is to allow post integration computations that should end the simulation, thus allowing check() to be called at the proper time step and not a step late.
The need for check() comes from the fact that links in postcalc() may alter another module's parameters after another module's postcalc phase has been run, which invalidates proper checking for stopping the simulation. The check() function guarantees that all access is from other modules whose check() functions have already been run.
Classes like History shouldn't have pointers or references to the variables that they are to be associated with, and they shouldn't inherit from Module for automatic storage. First, pointers and references are bad because modules can be destroyed, so the user of these classes should explicitly use push_back to store data. Secondly, automatic storage is dangerous and limiting, because the data might not be stored right after variables have been updated and operations could be performed before push_back was automatically called. Hence, these classes are meant to have variable information pushed back manually within a Ascent module's phases.
The time step can easily be determined from the recorded time history. We don't want to encourage wasted memory costs due to keeping track of the time step when it can be computed. If the time step history is really necessary, then the user can easily setup their own tracking within report dt_hist.push_back(t - t_previous)
.
Simulator numbers (size_t sim) are used to separate integration algorithms. It is essential to know what simulator a module belongs to. Does this need to occur at construction? Couldn't modules be applied to a simulator and be passed back and forth between simulators? This would require a means of transferring state information across integration schemes, and somewhere data would have to be thrown out. There is no formal method of swapping states and a careful and detailed approach would have to be formulated for this to work in a straightforward manner. Furthermore, the goal is to hide the simulator from the user, so that they aren't interacting with a simulator and managing modules within it. This does place a limitation on module handling across simulators, but it simplifies use.
An element can be erased in the standard library containers if erase(it++) is used. However, this only works for removing the current iterator. If using it++->function_call() has the potential to remove other elements from the std:: container, a more dynamic means of erasing is required. This is why Ascent has its own DynamicMap, which delays erasing modules until a separate erase() method is called after looping.
Don't use the auto keyword with Eigen types (see Eigen documentation), unless you really know what you're doing.