Skip to content

SetupToolOverview

Rupert Nash edited this page Jul 9, 2018 · 3 revisions

Summary

The setuptool creates the binary input files for HemeLB (.gmy files) which describe the domain. The file format is described in NewGeometryFiles, but the basics are that it's a two-level format, consisting of Blocks of individual Sites, with some header information at the start. These are created from a description of the surface (in STL format) and the inlets and outlets and voxel size.

Implementation

The GUI and control code is written in Python and the actual generation and IO in C++

GUI

Since there was no python MVC framework that supported having a vtkRenderWindowInteractor available when this project started, I rolled my own simple MVC-sh code. This make heavy use of the Observer pattern.

The GUI can save a "Profile" file (.pr2) which records all the state of the model objects.

Observers

Lives in source:Tools/setuptool/HemeLbSetupTool/Util/Observer.py

The main class is Observable, which your model object should inherit from. It takes a lot of inspiration from Apple's Cocoa framework (see KVO in apple's docs) and includes dependent key paths.

The implementation requires observers to register callables that will be notified of changes to the requested //key path//. A key path is a period . separated list of attributes basically doing dynamic attribute look up and observer removal/adding when the values change.

inlet.AddObserver('Radius', someMethod)

when the Radius attribute of that inlet object is set (either immediately before the change or immediately after or both, depending on the options given to AddObserver - the default is after only) someMethod will be called with one argument a Change instance.

The Observable subclasses can support computed attributes/properties than depend on other attributes by creating dependencies, typically in the __init__ method:

class Circle(Observable):
    @property
    def Area(self):
        return math.pi * self.Radius**2
    def __init__(self, radius):
        self.Radius = radius
        self.AddDependency('Area', 'Radius')

So now observers of Area will get notifications when Radius is set. These support chaining (in the above example, you can make something else depend on Area)

Observers are removable (it is not an error to try to remove a method that's not an observer)

inlet.RemoveObserver('Radius', someMethod)

Observing key paths: You can say something like

tree.AddObserver('LeftChild.RightChild.Value', object.method)

which will fire when the Value attribute of the RightChild of the LeftChild of tree is changed.

Note that the following does note cause the observer to be notified

originalRChild = tree.LeftChild.RightChild
tree.LeftChild.RightChild = NewNode(Value=7)

originalRChild.Value = 18

This is because when the last line executes, the value of tree.LeftChild.RightChild.Value is not set.

The implementation uses the ComplexKeyCallbackWrapper class to manage this. Basically it registers itself as an observer of the local part of the key path and the rest of the key on the current value of the attribute. It then deals with unobserving and reobserving if the local part of the key is updated.

Model

The Model part has no knowledge of the View or Controller parts and just represents the entities describing the simulation domain. The objects should be Observable subclasses.

Describe the Pipeline

Binding

The code in source:Tools/setuptool/HemeLbSetupTool/Bindings/init.py his module contains a simple implementation of the Bindings pattern for MVC applications, using that in Cocoa as its inspiration. See http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaBindings/CocoaBindings.html for details.

Basically your Model object(s) must inherit from Observable and have public attributes/properties (my convention is to start these with an upper case letter) that will be bound to View items. It can also contain methods that we want to trigger from elsewhere.

Your Controller should be an ObjectController or a subclass and when instantiated you should pass it the model object it is in charge of exposing.

Your view should then Bind to keys of the controller which will then keep the model variables in sync with the widgets.

 ________                __________               ______
|Model   |<---\         |Controller|             |Widget|<----|
|--------|     \        |----------|             |------|     |
|FileName|      \-------|delegate  |             |      |     |
|        |       \      |bindings  |----                      |
                  |     |          |    |                     |
                  |                     |                     |
                  |                     ---------------- ...  |
                  |      ____________   |      |      |       |
                  |     |Binding     |<-|      |      |       |
                  |     |------------|                        |
                  |  |--|modelMapper |                        |
                  |  |  |widgetMapper|-----|                  |
                  |  |  |translator  |-|   |                  |
                  |  |  |            | |   |                  |
                  |  |                 |   |                  |
                  |  |                 |   |                  |
                  |  |   __________    |   |                  |
                  |  |  |Translator|<--|   |                  |
                  |  |  |----------|       |                  |
                  |  |  |          |       |                  |
                  |  |                     |                  |
                  |  |                     |                  |
                  |  |                     |                  |
                  |  |   _______________   |   ____________   |
                  |  |->|Observingmapper|  |->|WidgetMapper|  |
                  |     |---------------|     |------------|  |
                  |-----|model          |     |widget      |--|
                        |key ='FileName'|     |            |
                        |               |
  1. Initialisation

    model = Model() controller = ObjectController(model) view = View(controller)

    def View.init(self, controller): self.text = wx.TextCtrl(...) controller.BindValue('FileName', WxWidgetMapper(self.text, 'Value', wx.EVT_TEXT))

  2. Something alters either end.

  • any Mappers are notified (Observing ones through my simple Observer pattern implementation, WxWidget ones through WX events, VTK ones through VTK observation)
  • each tells its Binding
  • the Binding then gets the new value through the Mapper
  • passes it throught the Translator
  • sets the resulting value on the other Mapper

Controller

The Controller is responsible for the behaviour of the model objects in the application. I've set it up so that there are mirror control objects for the non trivial model objects. Each control object is initialised with its model object. Controller objects also have mixins for the types of their keys if they need complex binding behaviour (e.g. HasVtkObjectKeys in Bindings.VtkObject)

View

The View displays the controlled data using wx widgets. Sets up the WX windows and widgets and then binds them to the controllers.

Command line only tool

This uses the profile files created by the GUI tool and generates the .gmy file. It only uses the Model objects.

Generation Python

This labels the cells of the surface geometry with an integer, set to -1 indicating a wall triangle, clips this against each inlet and outlet in turn, at each point filling in the open face with new triangles and labelling them with a integer corresponding to the inlet/outlet and then scales the whole vtkPolyData by the voxel size (i.e. to lattice units).

Generation C++

Key objects: Domain a 3d cuboid of blocks Block a cube of sites (set as 8 sites along each axis) Site a single lattice point. As several important attributes:

  • IsFluidKnown - have we figured out whether this site is inside or outside the surface
  • IsFluid - is this site inside the surface
  • Links - a vector of the site's links to the neighbouring 26 sites (in the order of source:Code/io/formats/geometry.h)

The algorithm is as follows:

  1. Choose a block size, s
  2. Set up a Domain object which is the smallest cuboid of blocks that can fit around the surface, leaving a layer of at least one site outside the surface
  3. For each block (iterating z fastest, then y, then x):
  4. If the block lies on the edge of the domain, set those sites which lie at the edge of the domain to have IsFluidKnown to true and IsFluid to false (to bootstrap the following)
  5. For each site in the block (iterating z fastest, then y, then x):
  6. Note that due to the iteration order, we know the fluidness of the current site (either from the above step or because it was set below)
  7. Iterate over all neighbours that have yet to be reached
  8. Look for intersections with the surface on the line from site to neigh and propagate the state of IsFluidKnown and IsFluid, computing cut distances and normals if an intersection occurs.

This uses CGAL for the geometrical operations as VTK is unreliable. (It only fails 1 time in a million, but when you are doing millions of intersections, the probability of error tends to 1)

Things to fix

  • Accept input in VTP format as well as STL
  • For the core algorithm, iterate over triangles rather than sites and use some sort of octree like data structure to improve scaling.

Clone this wiki locally