Re: Simplified expression template tree

From: Todd Veldhuizen (tveldhui@cs.indiana.edu)
Date: Tue Oct 06 1998 - 10:01:43 EST


Hi Gregory; sorry for the slow reply and thanks for the
reminder. I still haven't caught up from my last trip.

I played with your et implementation. Cool tree dumping
mechanism. Your idea is a good one but would result in
more operators than I'd like.

Currently blitz expressions can be:
  array
  indirect array
  unary op
  binary op
  trinary op (where, zip, ...)
  index placeholder
  mapping (for tensor notation)
  scalar

In the future I need to add some other expression types,
such as random number generators (RNGs).

The rational for wrapping with _bz_ArrayExpr<> is that I
only have to provide three signatures for binary operators:

op(Array<>, T)
op(_bz_ArrayExpr<>, T)
op(T, ETBase<>)

This is assuming that I can make index placeholders, mappings
and RNGs look like _bz_ArrayExpr<>'s. Still working on this
part.

In your approach, I need:

op(Array, T)
op(indirectarray, T)
op(unary, T)
op(binary, T)
op(trinary, T)
op(indexplaceholder, T)
op(mapping, T)
     .
     .
op(T, Expr)

i.e. if I have N node types in the expression tree, I need
N+1 operators instead of just 3.

But thanks for the suggestions; working through your code
helped me think about what I need to do to get the new et
implementation working properly.

I'm CCing this to the blitz-dev archive; let me know
if you don't want your code accessible there.

Cheers,
Todd

Gregory Brooks <gregory_brooks@retek.com> wrote:
>
> Hi Todd,
>
> Recently, I have been working on an Array<> class which has some
> similarities to your blitz Array<> class. I have a copy of the latest blitz
> snapshot (19980903) and I believe that I have found a simpler way to form
> the expression template tree. I would like to share this with you.
>
> Currently, in the blitz expression template tree there are basically 4 node
> types: ArrayExpr (general expr nodes), ArrayExprOp (binary op nodes),
> ArrayExprUnaryOp (unary op nodes), and ArrayExprConstant (scalar expr
> nodes). In the expression tree, instances of the ArrayExprOp and
> ArrayExprUnaryOp nodes are always wrapped by an instance of the ArrayExpr
> node. The simplification that I have come up with obviates the need for the
> ArrayExpr node and thus greatly reduces the number of nodes in an expression
> tree.
>
> Consider your new expression template implementation in which both the Array
> class and the ArrayExpr class derive from the ETBase class. The
> simplification involves the following:
> a) change ArrayExprOp, ArrayExprUnaryOp and ArrayExprConstant to derive
> from ETBase
> b) remove ArrayExpr completely
> c) overload Array assignment for an ETBase rather than an ArrayExpr
> d) overload the binary operators whereby the first parameter is an
> ArrayExprOp or an ArrayExprUnaryOp (instead of just ArrayExpr)
> e) use operator tag classes to further simplify the operators (see
> example)
>
> I have implemented these changes in a simple example program (see attached
> file "array.cc" and sample output "array.output"). In this program the
> following classes are roughly equivalent:
> ETBase ==> Expr
> ArrayExprOp ==> BinaryExpr
> ArrayExprUnaryOp ==> UnaryExpr
> ArrayExprConstant ==> ScalarExpr
> asExpr ==> MakeExpr
>
> Do you see the benefits of this implementation? Do you see any
> disadvantages? Your comments would be greatly appreciated.
>
> Please note that in order to keep the example simple, the Array<> is a
> vector, the PromoteTrait<> uses the brute force approach, and some of the
> operators could provide specializations (ie. boolean return types) instead
> of using PromoteTrait<> blindly.
>
> cheers,
> Gregory Brooks
> (Computer Engineering graduate from University of Waterloo)
> gregory_brooks@retek.com
> Retek Information Systems
> Atlanta, GA
>
> <<array.cc>> <<array.output>>
>
> ------ =_NextPart_000_01BDE347.6D2B9D80
> Content-Type: application/octet-stream;
> name="array.cc"
> Content-Transfer-Encoding: quoted-printable
> Content-Disposition: attachment;
> filename="array.cc"
>
>
> #include <stdlib.h>
> #include <iostream.h>
> #include <math.h>
>
> template <class T> class Array;
>
> template <class E> class ScalarExpr;
> template <class A, class B, class Op> class BinaryExpr;
> template <class A, class Op> class UnaryExpr;
>
> template <class AType, class BType, class Op> class BinaryOp;
> template <class AType, class Op> class UnaryOp;
>
> //-----------------------------
> // Level
> // (for printing expressions)
> //-----------------------------
> class Level
> {
> public:
> Level(int level)
> : _level(level)
> {}
> void print(std::ostream& os) const
> { for (int i =3D 0; i < _level; i++) os << "| "; }
> private:
> int _level;
> };
>
> std::ostream& operator<< (std::ostream& os, const Level& level)
> {
> level.print(os);
> return os;
> }
>
> //-----------------------
> // PromoteTrait<T1,T2>
> //-----------------------
>
> template <class T1, class T2>
> struct PromoteTrait
> {
> typedef T1 Type; // default to first type
> };
>
> struct PromoteTrait<bool, char> { typedef char Type; };
> struct PromoteTrait<bool, short> { typedef short Type; };
> struct PromoteTrait<bool, int> { typedef int Type; };
> struct PromoteTrait<bool, long> { typedef long Type; };
> struct PromoteTrait<bool, float> { typedef float Type; };
> struct PromoteTrait<bool, double> { typedef double Type; };
>
> struct PromoteTrait<char, short> { typedef short Type; };
> struct PromoteTrait<char, int> { typedef int Type; };
> struct PromoteTrait<char, long> { typedef long Type; };
> struct PromoteTrait<char, float> { typedef float Type; };
> struct PromoteTrait<char, double> { typedef double Type; };
>
> struct PromoteTrait<short, int> { typedef int Type; };
> struct PromoteTrait<short, long> { typedef long Type; };
> struct PromoteTrait<short, float> { typedef float Type; };
> struct PromoteTrait<short, double> { typedef double Type; };
>
> struct PromoteTrait<int, long> { typedef long Type; };
> struct PromoteTrait<int, float> { typedef float Type; };
> struct PromoteTrait<int, double> { typedef double Type; };
>
> struct PromoteTrait<long, float> { typedef float Type; };
> struct PromoteTrait<long, double> { typedef double Type; };
>
> struct PromoteTrait<float, double> { typedef double Type; };
>
>
> //-----------------------
> // MakeExpr<>
> //-----------------------
>
> template <class E>
> struct MakeExpr
> {
> typedef ScalarExpr<E> Type;
> };
>
> template <class E>
> struct MakeExpr< ScalarExpr<E> >
> {
> typedef ScalarExpr<E> Type;
> };
>
> template <class T>
> struct MakeExpr< Array<T> >
> {
> typedef Array<T> Type;
> };
>
> template <class A, class B, class Op>
> struct MakeExpr< BinaryExpr<A,B,Op> >
> {
> typedef BinaryExpr<A,B,Op> Type;
> };
>
> template <class A, class Op>
> struct MakeExpr< UnaryExpr<A,Op> >
> {
> typedef UnaryExpr<A,Op> Type;
> };
>
>
> //-----------------------
> // Base Expression:
> // Expr<E>
> //-----------------------
>
> template <class E>
> class Expr {};
>
> template <class E>
> std::ostream& operator<< (std::ostream& os, const Expr<E>& e)
> {
> static_cast<const E&>(e).print(os);
> return os;
> }
>
> //-----------------------
> // Scalar Expression:
> // ScalarExpr<E>
> //-----------------------
>
> //
> // default to scalar expression
> //
> template <class S>
> class ScalarExpr : public Expr< ScalarExpr<S> >
> {
> public:
> typedef S Type;
>
> ScalarExpr(const S& s)
> : _s(s)
> {}
>
> Type operator() (int i) const
> { return _s; }
>
> void print(std::ostream& os, int level =3D 0) const
> {
> os << Level(level) << "ScalarExpr:" << endl;
> os << Level(level+1) << _s << endl;
> }
>
> private:
> const S _s;
> };
>
>
> //-----------------------------
> // Binary Expression:
> // BinaryExpr<A,B,Op>
> //-----------------------------
>
> template <class A, class B, class Op>
> class BinaryExpr : public Expr< BinaryExpr<A,B,Op> >
> {
> public:
> typedef MakeExpr<A>::Type AExpr;
> typedef MakeExpr<B>::Type BExpr;
> typedef AExpr::Type AType;
> typedef BExpr::Type BType;
> typedef BinaryOp<AType, BType, Op> Operation;
> typedef Operation::Type Type;
>
> BinaryExpr(const A& a, const B& b)
> : _a(a), _b(b)
> {}
>
> Type operator() (int i) const
> { return Operation::apply(_a(i), _b(i)); }
>
> void print(std::ostream& os, int level =3D 0) const
> {
> os << Level(level) << "BinaryExpr:" << endl;
> _a.print(os, level+1);
> _b.print(os, level+1);
> Operation::print(os, level+1);
> }
>
> private:
> AExpr _a;
> BExpr _b;
> };
>
>
> //-----------------------------
> // Unary Expression:
> // UnaryExpr<A,Op>
> //-----------------------------
>
> template <class A, class Op>
> class UnaryExpr : public Expr< UnaryExpr<A,Op> >
> {
> public:
> typedef MakeExpr<A>::Type AExpr;
> typedef AExpr::Type AType;
> typedef UnaryOp<AType, Op> Operation;
> typedef Operation::Type Type;
>
> UnaryExpr(const A& a)
> : _a(a)
> {}
>
> Type operator() (int i) const
> { return Operation::apply(_a(i)); }
>
> void print(std::ostream& os, int level =3D 0) const
> {
> os << Level(level) << "UnaryExpr:" << endl;
> _a.print(os, level+1);
> Operation::print(os, level+1);
> }
>
> private:
> AExpr _a;
> };
>
>
> //=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
> // Operators
> //=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
>
> //--------------------------
> // BinaryOp<AType,BType,Op>
> //--------------------------
> template <class AType, class BType, class Op>
> class BinaryOp {}; // default: never instantiated
>
>
> #define BINARY_EXPR_OPERATOR(Op,Name) =
> \
> =
> \
> class Name {}; =
> \
> =
> \
> template <class AType, class BType> =
> \
> class BinaryOp<AType, BType, Name> =
> \
> { =
> \
> public: =
> \
> typedef PromoteTrait<AType, BType>::Type Type; =
> \
> =
> \
> static Type apply(const AType& a, const BType& b) =
> \
> { return a Op b; } =
> \
> =
> \
> static void print(std::ostream& os, int level =3D 0) =
> \
> { os << Level(level) << #Name << endl; } =
> \
> }; =
> \
> =
> \
> template <class A, class B> =
> \
> BinaryExpr<Array<A>,B,Name> =
> \
> operator Op (const Array<A>& a, const B& b) =
> \
> { =
> \
> return BinaryExpr<Array<A>,B,Name>(a, b); =
> \
> } =
> \
> =
> \
> template <class A1, class A2, class A3, class B> =
> \
> BinaryExpr<BinaryExpr<A1,A2,A3>,B,Name> =
> \
> operator Op (const BinaryExpr<A1,A2,A3>& a, const B& b) =
> \
> { =
> \
> return BinaryExpr<BinaryExpr<A1,A2,A3>,B,Name>(a, b); =
> \
> } =
> \
> =
> \
> template <class A1, class A2, class B> =
> \
> BinaryExpr<UnaryExpr<A1,A2>,B,Name> =
> \
> operator Op (const UnaryExpr<A1,A2>& a, const B& b) =
> \
> { =
> \
> return BinaryExpr<UnaryExpr<A1,A2>,B,Name>(a, b); =
> \
> } =
> \
> =
> \
> template <class A, class B> =
> \
> BinaryExpr<A,B,Name> =
> \
> operator Op (const A& a, const Expr<B>& b) =
> \
> { =
> \
> return BinaryExpr<A,B,Name>(a, static_cast<const B&>(b)); =
> \
> } =
> \
>
>
> BINARY_EXPR_OPERATOR(+ , BinaryOp_Plus)
> BINARY_EXPR_OPERATOR(- , BinaryOp_Minus)
> BINARY_EXPR_OPERATOR(* , BinaryOp_Multiply)
> BINARY_EXPR_OPERATOR(/ , BinaryOp_Divide)
> BINARY_EXPR_OPERATOR(% , BinaryOp_Modulus)
> BINARY_EXPR_OPERATOR(& , BinaryOp_BitwiseAnd)
> BINARY_EXPR_OPERATOR(| , BinaryOp_BitwiseOr)
> BINARY_EXPR_OPERATOR(^ , BinaryOp_BitwiseXor)
> BINARY_EXPR_OPERATOR(>>, BinaryOp_ShiftRight)
> BINARY_EXPR_OPERATOR(<<, BinaryOp_ShiftLeft)
> BINARY_EXPR_OPERATOR(&&, BinaryOp_LogicalAnd)
> BINARY_EXPR_OPERATOR(||, BinaryOp_LogicalOr)
> BINARY_EXPR_OPERATOR(> , BinaryOp_GreaterThan)
> BINARY_EXPR_OPERATOR(>=3D, BinaryOp_GreaterEqual)
> BINARY_EXPR_OPERATOR(< , BinaryOp_LessThan)
> BINARY_EXPR_OPERATOR(<=3D, BinaryOp_LessEqual)
> BINARY_EXPR_OPERATOR(=3D=3D, BinaryOp_Equal)
> BINARY_EXPR_OPERATOR(!=3D, BinaryOp_NotEqual)
>
>
> //-----------------------
> // UnaryOp<AType,Op>
> //-----------------------
> template <class AType, class Op>
> class UnaryOp {}; // default: never instantiated
>
>
> #define UNARY_EXPR_OPERATOR(Op,Name) =
> \
> =
> \
> class Name {}; =
> \
> =
> \
> template <class A> =
> \
> class UnaryOp<A, Name> =
> \
> { =
> \
> public: =
> \
> typedef A Type; =
> \
> =
> \
> static Type apply(const Type& a) =
> \
> { return Op a; } =
> \
> =
> \
> static void print(std::ostream& os, int level =3D 0) =
> \
> { os << Level(level) << #Name << endl; } =
> \
> }; =
> \
> =
> \
> template <class A> =
> \
> UnaryExpr<A,Name> =
> \
> operator Op (const Expr<A>& a) =
> \
> { =
> \
> return UnaryExpr<A,Name>(static_cast<const A&>(a)); =
> \
> } =
> \
>
>
> UNARY_EXPR_OPERATOR(- , UnaryOp_Minus)
> UNARY_EXPR_OPERATOR(~ , UnaryOp_Compliment)
> UNARY_EXPR_OPERATOR(! , UnaryOp_Not)
>
>
> #define UNARY_EXPR_FUNCTION(Func,Name) =
> \
> =
> \
> class Name {}; =
> \
> =
> \
> template <class A> =
> \
> class UnaryOp<A, Name> =
> \
> { =
> \
> public: =
> \
> typedef A Type; =
> \
> =
> \
> static Type apply(const Type& a) =
> \
> { return Func(a); } =
> \
> =
> \
> static void print(std::ostream& os, int level =3D 0) =
> \
> { os << Level(level) << #Name << endl; } =
> \
> }; =
> \
> =
> \
> template <class A> =
> \
> UnaryExpr<A,Name> =
> \
> Func(const Expr<A>& a) =
> \
> { =
> \
> return UnaryExpr<A,Name>(static_cast<const A&>(a)); =
> \
> } =
> \
>
>
> UNARY_EXPR_FUNCTION(sin, UnaryOp_Sin)
> UNARY_EXPR_FUNCTION(cos, UnaryOp_Cos)
> UNARY_EXPR_FUNCTION(tan, UnaryOp_Tan)
>
>
> //-----------------------
> // Array
> //-----------------------
>
> static int uniqueID =3D 0;
>
> template <class T>
> class Array : public Expr< Array<T> >
> {
> public:
> typedef T Type;
>
> Array(int size)
> : _id(uniqueID++), _size(size), _data(new Type[size])
> {}
>
> ~Array()
> { delete[] _data; }
>
> int size() const
> { return _size; }
>
> const Type& operator() (int i) const
> { return _data[i]; }
>
> Type& operator() (int i)
> { return _data[i]; }
>
> template <class E>
> Array<Type>& operator=3D (const Expr<E>& e)
> {
> const E& expr =3D static_cast<const E&>(e);
> for (int i =3D 0; i < _size; ++i)
> _data[i] =3D expr(i);
> return *this;
> }
>
> void print(std::ostream& os, int level =3D 0) const
> { os << Level(level) << "Array" << _id << endl; }
>
> private:
> int _id;
> int _size;
> Type* _data;
> };
>
> template <class T>
> std::ostream& operator<< (std::ostream& os, const Array<T>& a)
> {
> os << "size " << a.size() << ":" << endl;
> for (int i =3D 0; i < a.size(); ++i)
> os << "data(" << i << ") =3D " << a(i) << endl;
> return os;
> }
>
> //-----------------------
> // Main
> //-----------------------
> int main(int argc, char* argv[])
> {
> Array<double> a(4); // Array0
> Array<double> b(4); // Array1
> Array<double> c(4); // Array2
> Array<double> d(4); // Array3
> Array<double> e(4); // Array4
>
> a(0) =3D 0;
> a(1) =3D 1;
> a(2) =3D 2;
> a(3) =3D 3;
>
> b(0) =3D 0;
> b(1) =3D 2;
> b(2) =3D 4;
> b(3) =3D 6;
>
> cout << "a: " << a << endl;
> cout << "b: " << b << endl;
> cout << "Evaluate: c =3D a + b" << endl;
> c =3D a + b;
> cout << "c: " << c << endl;
> cout << "--" << endl;
>
> cout << "Expression: a + b" << endl;
> cout << a + b << endl;
> cout << "--" << endl;
>
> d(0) =3D 3;
> d(1) =3D 2;
> d(2) =3D 1;
> d(3) =3D 0;
>
> cout << "a: " << a << endl;
> cout << "c: " << c << endl;
> cout << "Evaluate: e =3D (a + 2.0) * c" << endl;
> e =3D (a + 2.0) * c;
> cout << "e: " << e << endl;
> cout << "--" << endl;
>
> cout << "Expression: (a + 2.0) * c" << endl;
> cout << (a + 2.0) * c << endl;
> cout << "--" << endl;
>
> cout << "Expression: sin(-(a + 3) / (1 - b) * c)" << endl;
> cout << sin(-(a + 3) / (1 - b) * c) << endl;
> cout << "--" << endl;
>
> d =3D (-(a + 3) / (1 - b) * c);
> cout << "d: " << d << endl;
> e =3D sin(-(a + 3) / (1 - b) * c);
> cout << "e: " << e << endl;
> cout << "--" << endl;
>
> void funky(int, int, const Array<double>&);
> funky(1,2,a);
> funky(2,1,b);
> }
>
> void funky(int x, int y, const Array<double>& z)
> {
> cout << "Expression: (x + 2.0) * y + z" << endl;
> cout << (x + 2.0) * y + z << endl;
> }
>
> ------ =_NextPart_000_01BDE347.6D2B9D80
> Content-Type: application/octet-stream;
> name="array.output"
> Content-Disposition: attachment;
> filename="array.output"
>
> a: size 4:
> data(0) = 0
> data(1) = 1
> data(2) = 2
> data(3) = 3
>
> b: size 4:
> data(0) = 0
> data(1) = 2
> data(2) = 4
> data(3) = 6
>
> Evaluate: c = a + b
> c: size 4:
> data(0) = 0
> data(1) = 3
> data(2) = 6
> data(3) = 9
>
> --
> Expression: a + b
> BinaryExpr:
> | Array0
> | Array1
> | BinaryOp_Plus
>
> --
> a: size 4:
> data(0) = 0
> data(1) = 1
> data(2) = 2
> data(3) = 3
>
> c: size 4:
> data(0) = 0
> data(1) = 3
> data(2) = 6
> data(3) = 9
>
> Evaluate: e = (a + 2.0) * c
> e: size 4:
> data(0) = 0
> data(1) = 9
> data(2) = 24
> data(3) = 45
>
> --
> Expression: (a + 2.0) * c
> BinaryExpr:
> | BinaryExpr:
> | | Array0
> | | ScalarExpr:
> | | | 2
> | | BinaryOp_Plus
> | Array2
> | BinaryOp_Multiply
>
> --
> Expression: sin(-(a + 3) / (1 - b) * c)
> UnaryExpr:
> | BinaryExpr:
> | | BinaryExpr:
> | | | UnaryExpr:
> | | | | BinaryExpr:
> | | | | | Array0
> | | | | | ScalarExpr:
> | | | | | | 3
> | | | | | BinaryOp_Plus
> | | | | UnaryOp_Minus
> | | | BinaryExpr:
> | | | | ScalarExpr:
> | | | | | 1
> | | | | Array1
> | | | | BinaryOp_Minus
> | | | BinaryOp_Divide
> | | Array2
> | | BinaryOp_Multiply
> | UnaryOp_Sin
>
> --
> d: size 4:
> data(0) = -0
> data(1) = 12
> data(2) = 10
> data(3) = 10.8
>
> e: size 4:
> data(0) = -0
> data(1) = -0.536573
> data(2) = -0.544021
> data(3) = -0.980936
>
> --
> Expression: (x + 2.0) * y + z
> BinaryExpr:
> | ScalarExpr:
> | | 6
> | Array0
> | BinaryOp_Plus
>
> Expression: (x + 2.0) * y + z
> BinaryExpr:
> | ScalarExpr:
> | | 4
> | Array1
> | BinaryOp_Plus
>
>
> ------ =_NextPart_000_01BDE347.6D2B9D80--
>



This archive was generated by hypermail 2b29 : Wed Feb 20 2002 - 04:30:07 EST