The library is available for download under the LGPL copyleft terms (see COPYING) from the Download page Printable versions of this document (pdf and ps) are available on the above download page.
The following compilers have been found to work fine with expresso. Newer versions of the same compiler is very likely to work just as well.
> cd /usr/local/include /usr/local/include> gunzip < expresso.tgz | tar xf -
#include "expresso.h" using namespace esso; main() { Array<3,float> x(10,20,30); // You have now a fortran compatible 3-dimensional array of floats // with index range (0:9,0:19,0:29) x<<=0; // Efficient elementwise copy from scalar x(2,5,14)=1; // Fortran-like element access x<<=25*Sqrt(x)-x; // Efficient arithmetic expressions }
> gcc -I/usr/local/include/expresso -O3 testc.cc
Array<3,float> a(10,20,30);
real a(0:9,0:19,0:29);
Array<3,Fix<float> > a(10,20,30);
The most useful constructors for Arrays follow (other exist), see esso::Array class documentation):
Ix<3> ix(10,20,30); // Range as defined in 'ix' Array<3,float> a(ix); // An array with start index is 1 instead of 0 Array<3,float> b(ix,Ix<3>(1)); // Copy constructor, this creates a reference to the same elements Array<3,float> c(b);
Ix
is a small vector of integers whose length is known at compile time, see esso::Ix. Array<3,Fix<float> > x(10,10,10); for(int k=x.L(2); k<x.U(2); k++) for(int j=x.L(1); j<x.U(1); j++) for(int i=x.L(0); i<x.U(0); i++) x(i,j,k)=x(i,j,k)*x(i,j,k)-1;
Note the usage of member functions L and U. These functions return lower and upper index bounds for the Array.
Just as in fortran, the innermost loop should be over the first index for best performance, this is because the elements are by default stored in memory in the same way as fortran (unless you have permuted the ranks in your code). Note that the storage order of ranks is thus by default reversed compared to built-in C and C++ arrays.
Ix<3,T> i;
Ix<3> i(10,15,20); // All elements are initialized Ix<3> j(10); // 10 is used to initialize all elements Ix<3> k; // Uninitialized Ix<3> l(i); // Elements of Ix object i are copied j[1]=25; // Element access j=25; // Assign all elements with 25 j=i; // Copy elements from Ix object cout << j[0] <<' '<< j[1] <<' '<< j[2] <<endl;
Index objects can (of course) be used to access elements in an array:
Ix<2> i(2,2); Array<2,float> x(i); // Allocate array using length given by i i = Ix<2>(1,0); a[i] = 3.15; // Access an element using an Ix object as index cout << a;
It is recommended that the ()-operator are used for indexing an Array rather than Ix objects when performance is important, because most of todays compilers are better at optimizing such code. Ix objects are still very commonly used though, for example as arguments to logical operators and to declarations of Arrays.
Another possibility is to use Ix objects as element types for Array objects. The following code snippet shows some of the flexibility by such technique:
Array<2,Ix<3,float> > a(10,20); a(2,4)[1] = 0; // Access a single float element a(2,4) = 1; // Access a Ix object and assign with a scalar a <<= Ix<3,float>(1,2,3); // Assign all Ix elements with an Ix object a <<= 3.14; // Assign all float elements with a scalar
How is this done?
When an array is created memory is allocated automatically to fit it's declared index range (unless declared with the default constructor, in which case no memory is allocated). When another array is assigned from the first, a counter in the memory block object is increased, so that at all times the memory block knows how many Array objects are referring to it. When an Array object stops referring to a certain memory block the counter is decreased one step. When the counter reaches zero, the memory block is deallocated automatically.
As an example, it is safe and reasonably efficient to return a locally declared Array object from a function, because the memory will continue to exist until the last Array object referring to it is destroyed or assigned some other memory block.
Array<3,float> f(float q) { Array<3,float> array(10,10,10); array<<=q; return array; // Safe and fast! } main() { Array<3,float> x; f(2.8); // The returned memory is safely deleted x=f(3.14); // x now points to the memory allocated in the // call to f() }
Array<3,float> a(10,10,10); Array<3,float> b(a); // b refers to the same memory as a Array<2,float> c; c = Rmdim(a,2,5); // c refers to the slice resulting // from fixing dimension 2 to index value 5 void f(Array<3,float>); f(a); // The local array object in f // refers to the same memory as a
In many senses the Array objects work as a pointer with automatic memory allocation mechanism and an index range.
Array<3,float> a(10,10,10),b(15,10,5); a<<= 3.14; // Copy 3.14 to all elements b<<=a; // Copy elements from Array a to b
The operators +=, -=, *= and /= work the same way as <<= but also performs an arithmetic operation.
To create a reference rather than copying elements, use operator =
Array<3,float> a(10,10,10),b(15,10,5); a<<= 3.14; // Copy 3.14 to all elements b=a; // Do not copy elements, create reference
b
is identical to that of a
after the assignment. Array<3,float> a(10,10,10); a.Shift(0,10); // The index range now is (10:19,0:9,0:9) a.Permute(0,2); // Ranks 0 and 2 are permuted a.Reverse(1); // The order of elements in dimension 1 is reversed
These member functions also exist in a function flavors:
Array<3,float> a(10,10,10),b,c,d; b = Shift(a,0,10); c = Permute(a,0,2); d = Reverse(a,1);
See esso namespace documentation for listing of more logical functions.
Array<2,float> a,b,c,d; a<<=a-b*c+3.14;
In addition to the four arithmetic operations there are Max, Min, Abs, Sqr, Sqrt, Pow and some other functions defined to be used in arithmetic expressions, see esso namespace documentation for a complete listing.
Of course, the result of logical functions can also be used in arithmetic expressions. (However, arithmetic expressions can in most cases not be used as input to logical functions).
When compiled, the assignment with an arithmetic expression right hand side results in a single loop, i.e. no temporary array objects are created. This means that a good compiler may produce optimal code for such array expression assignments.
Experience with some of todays compilers indicate that best performance is attained by forming expressions that do not contain to many operands. As a rule of thumb, use of ten or more array operands is not recommended. Also, using arithmetic assignment operators (i.e. +=, -= ...) wherever possible is recommended. (i.e. rather than a<<=a+1, use a+=1).
Array<4,Fix<float[30][20]> > a(10,5); // Index range is (0:29,0:19,0:9,0:4) a(0,1,10,20) = 3.14; // The fixed ranks are indexed just as usual.
Note that an alternative is to use Ix objects as elements:
Array<2,Ix<20,Ix<30,float> > > a(10,5); // Index range is (0:29,0:19,0:9,0:4) a(0,1)[10][20] = 3.14;
Beware that the runtime checks activated by the EXPRESSO_DEBUG macro will decrease efficiency drastically. It is only meant for use while developing or debugging.
Also beware that compiling only some files with EXPRESSO_DEBUG may sometimes produce strange results because inlined functions may be defined differently in different object files. It is recommended not to link together files compiled with different EXPRESSO_DEBUG macro settings.
The recommended way of dealing with this is to just look at the compiler output to determine which line in your code caused the error, then look in your code at that line to see what the problem could be.
The good part is that (in contrast to fortran and other less type-safe languages) if your code compiles there is a very good chance that it actually does what you expected.