ChomboVis already exposes a data API. It falls far short of our full goals for this sort of thing, but everyone should be aware of its existence. Go here and scan down to "class ReaderAPI".
The current ReaderAPI is limited in that it can deal with only one HDF5 dataset at a time. Where we want to go is a ChomboVis that can deal with HDF5 datasets, and subsets of them, as first-class objects. All that remains of class ReaderAPI is those methods that deal with data-specific settings current in the graphics subsystem. All other data operations go into standalone classes called VisualizableDataset, BoxLayoutData and BoxLayout, which are defined in the chombovis module.
I present class definitions without intertextual comments. But a "notes" section comes right afterward. Some readers will not be interested in knowing more than is already apparent from the function prototypes. All the rest will appreciate the lack of clutter, when they come back to this document on second and subsequent reads. Only non-obvious methods are commented.
Wherever it says real, understand that to mean Python's native floating-point type.
Intvect means list in Python.
The classes are presented in a more or less top-down order. This is so the reader sees the most important things first. Therefore, if you see an unfamiliar class mentioned (as a return type or function argument perhaps), it's probably defined further down.
Brian proposed a new visualization feature, a facility to render objects smaller than entire hdf5 datasets. I don't take that up here, as this document is about data, as distinct from rendering. In any case, nothing I propose here rules out that sort of new visualization feature.
I do not take up coarse-fine interpolation.
A user who is not interested in visualization need have nothing to do with class ReaderAPI; all non-visualization functionality can be found on the other classes (discussed below).
class ReaderAPI { public: // Operations involving entire dataset. void setVTKData( VisualizableDataset ); VisualizableDataset & getVTKData(); // Information about the state of the graphics subsystem. string getCurrentComponent() const; pair<real> getCurrentVisibleRange() const; int getVisibleLevelMin() const; int getVisibleLevelMax() const; bool is2DMode() const; bool isResliceMode() const; // Manipulation of the graphics subsystem. void setCurrentComponent( string name ); void setVisibleLevelMin( int level ); void setVisibleLevelMax( int level ); void showGUI() const; };
Note that even if we move toward a situation where every pipeline (isocontours, slices) can select its component independently (the way volume and streamlines already do), we still will not eliminate this method because there will always be a "default" component to go into those pipelines where another component was not selected explicitly.
import chombovis c = chombovis.this() # Obtains active instance of class ChomboVis. hdf5data = c.reader.getVTKData() # c.reader is the instance of class ReaderAPI. c.reader.setVisibleLevelMin( 0 ) c.reader.setVisibleLevelMax( hdf5data.getNumLevels() - 1 ) # See VisualizableDataset.
At this writing, a class like the proposed VisualizableDataset exists in ChomboVis' C++ layer (see pyChomboVis/utils/ChomboHDF5.h|cpp). The proposal here is to give this class a presence at the Python layer.
class VisualizableDataset { public: VisualizableDataset( string infile_name ); VisualizableDataset( vector<vector<BoxLayoutData> > ); VisualizableDataset( vector<BoxLayoutData> ); VisualizableDataset( BoxLayoutData ); // Accessors for global data. int getDimensionality() const; string getComponentName( int component_num ) const; vectorgetComponentNames() const; int getComponentNum( string component_name ) const; int getNumLevels() const; int getNumComponents() const; // Accessors for by-level data. Box getProbDomain( int level ) const; Realvect getDx( int level ) const; Realvect getOrigin() const; Intvect getOutputGhost( int level ) const; int getNumBoxes( int level ) const; BoxLayout getBoxes( int level ) const; BoxLayoutData const & getData( int level, string component ) const; // I/O void write( string outfile_name ); // Data creation. VisualizableDataset make2DSlice( char axis, real axis_position, int min_level, int max_level ) const; void defineNewComponent( string new_component_name, real (*function)(vector<real>), vector<string> arg_component_names ); void defineNewComponent( string new_component_name, real (*function)(vector<real>), vector<int> arg_component_numbers ); void importNewComponent( string new_component_name, VisualizableDataset other_dataset, string component_name_in_other_dataset ); };
The argument is not copied, unless and until this instance of VisualizableDataset is left holding the only reference to it, or we modify it.
The argument is not copied, unless and until this instance of VisualizableDataset is left holding the only reference to it, or we modify it.
The argument is not copied, unless and until this instance of VisualizableDataset is left holding the only reference to it, or we modify it.
The box numbering is not preserved; the new, two-dimensional VisualizableDataset's boxes are numbered in order 0, 1, 2, ....
The new component's status becomes equal in every way to that of the existing components: the graphics subsystem can display the new component; write() dumps it to file along with all the other components; getData() can return the new data; etc.
The box numbering is preserved.
Example:
import chombovis c = chombovis.this() # Obtains the active ChomboVis instance. hdf5_data = c.reader.getVTKData() # c.reader is the instance of class ReaderAPI. def norm( x, y ): return pow( x*x + y*y, 0.5 ) hdf5_data.defineNewComponent( 'momentum_norm', norm, ('momentum_x', 'momentum_y') )
The two VisualizableDatasets' BoxLayoutData's must be identical -- same number of levels, same number of boxes, of the same size, at the same locations.
No data is actually copied unless and until the other VisualizableDataset is deleted. Until then, the calling instance of VisualizableDataset just creates a pointer to the data in the other VisualizableDataset.
The box numbering is preserved.
Example: Here is how we would augment one VisualizableDataset by all the components of another.
import chombovis c = chombovis.this() data1 = VisualizableDataset( 'greg01.hdf5' ) data2 = VisualizableDataset( 'greg02.hdf5' ) for i in range(0, data2.getNumComponents()): data1.importNewComponent( 'new'+str(i), data2, data2.getComponentName(i) )
class BoxLayout { public: BoxLayout(); BoxLayout( vector<Box> ); int getNumBoxes() const; Box getBox( int ) const; Intvect getDx() const; }; BoxLayout union( vector<BoxLayout> ); BoxLayout intersection( vector<BoxLayout> ); BoxLayout difference( pair<BoxLayout> );
The return values are new BoxLayouts.
class BoxLayoutData { public: BoxLayoutData( BoxLayout ); int copyTo(); int exchange(); Intvect getGhost( int level ) const; void setGhost( Intvect ); int getCentering() const; void setCentering( int ); void crop( BoxLayout ); void clamp( BoxLayout ); void clamp( real lo, real hi ); void unclamp() void setDatum( int box_num, Intvect coordinates, real value ); real getDatum( int box_num, Intvect coordinates ) const; real const * getFArray( int box_num ) const; BoxLayout const & getBoxes() const; int getDimensionality() const; real getMin() const; real getMax() const; real getQuantile( real ) const; real getSum() const; real getSumSqr() const; real getNumCells() const; [In-place versions of operators below, e.g. +=, -=, etc.] }; BoxLayoutData crop( BoxLayoutData, BoxLayout ); BoxLayoutData operator+( BoxLayoutData, real ); BoxLayoutData operator+( BoxLayoutData, BoxLayoutData ); BoxLayoutData operator-( BoxLayoutData, real ); BoxLayoutData operator-( real, BoxLayoutData ); BoxLayoutData operator-( BoxLayoutData, BoxLayoutData ); BoxLayoutData operator*( BoxLayoutData, real ); BoxLayoutData operator*( BoxLayoutData, BoxLayoutData ); BoxLayoutData operator/( BoxLayoutData, real ); BoxLayoutData operator/( real, BoxLayoutData ); BoxLayoutData operator/( BoxLayoutData, BoxLayoutData ); BoxLayoutData exp( BoxLayoutData ); BoxLayoutData log( BoxLayoutData ); BoxLayoutData sin( BoxLayoutData ); BoxLayoutData cos( BoxLayoutData ); BoxLayoutData tan( BoxLayoutData ); BoxLayoutData asin( BoxLayoutData ); BoxLayoutData acos( BoxLayoutData ); BoxLayoutData atan( BoxLayoutData ); BoxLayoutData abs( BoxLayoutData ); BoxLayoutData pow( BoxLayoutData, real ); BoxLayoutData pow( real, BoxLayoutData );
All the existing data gets copied; this can be an expensive operation.
The box numbering is preserved.
The box numbering is preserved as long as no boxes end up entirely outside the cropped zone. Otherwise, the box numbering is not preserved.
The clamp(real lo, real hi) version makes all cells whose values fall outside the indicated range "invisible" to the reduction methods getMin(), getMax(), etc.
The box numbering is preserved.
The in-place versions modify all the data. The global versions create new BoxLayoutData instances.
If you need an operation not listed here, use VisualizableDataset::defineNewComponent() followed by VisualizableDataset::getData(). In fact, defineNewComponent() is advisable for efficiency reasons, whenever you need to do something that involves the composition of more than a few operations, and your BoxLayoutData object is close to an entire VisualizableDataset level in size. This is because the pointwise operators and functions construct a new BoxLayoutData on each invocation, whereas VisualizableDataset::defineNewComponent() does not create even one BoxLayoutData (until you call getData(), and that creates a single BoxLayoutData).
import chombovis data = chombovis.VisualizableDataset( 'vortex.3d.hdf5' ) n_levels = data.getNumLevels() for i in range( 0, data.getNumComponents() ): comp_name = data.getComponentName( i ) lo, hi = 10E5000, -10E5000 for l in range( 0, n_levels ): boxdata = data.getData( l, comp_name ) lo = min( lo, boxdata.getMin() ) hi = max( hi, boxdata.getMax() ) print "range of", comp_name, "=", [lo,hi]