← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 1894: 1. Add some introspection interfaces to Indexables and dispatchers, as needed for Programmer's ma...

 

------------------------------------------------------------
revno: 1894
committer: Václav Šmilauer <eudoxos@xxxxxxxx>
branch nick: trunk
timestamp: Sun 2009-12-13 20:27:57 +0100
message:
  1. Add some introspection interfaces to Indexables and dispatchers, as needed for Programmer's manual (https://yade-dem.org/sphinx/prog.html#multiple-dispatch), remove dead code, some cleanups.
modified:
  core/Dispatcher.hpp
  core/Functor.hpp
  core/Omega.cpp
  core/Omega.hpp
  lib/multimethods/DynLibDispatcher.hpp
  lib/multimethods/Indexable.hpp
  pkg/common/Engine/Dispatcher/InteractionGeometryFunctor.hpp
  pkg/dem/meta/Tetra.hpp
  py/utils.py
  py/yadeWrapper/yadeWrapper.cpp
  scripts/default-test.py


--
lp:yade
https://code.launchpad.net/~yade-dev/yade/trunk

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-dev/yade/trunk/+edit-subscription.
=== modified file 'core/Dispatcher.hpp'
--- core/Dispatcher.hpp	2009-12-11 11:38:18 +0000
+++ core/Dispatcher.hpp	2009-12-13 19:27:57 +0000
@@ -10,7 +10,9 @@
 
 #include "Engine.hpp"
 #include "Functor.hpp"
+#include<yade/core/Omega.hpp>
 #include<yade/lib-multimethods/DynLibDispatcher.hpp>
+#include<boost/lexical_cast.hpp>
 
 class Dispatcher : public Engine
 {
@@ -47,6 +49,7 @@
 		virtual int getDimension() { throw; };
 		virtual string getBaseClassType(unsigned int ) { throw; };
 
+
 	protected:
 		virtual void postProcessAttributes(bool deserializing);
 	REGISTER_ATTRIBUTES(Engine,(functorNames)(functorArguments));
@@ -54,6 +57,29 @@
 };
 REGISTER_SERIALIZABLE(Dispatcher);
 
+/*! Function returning class name (as string) for given index and topIndexable (top-level indexable, such as Shape, Material and so on)
+This function exists solely for debugging, is quite slow: it has to traverse all classes and ask for inheritance information.
+It should be used primarily to convert indices to names in Dispatcher::dictDispatchMatrix?D; since it relies on Omega for RTTI,
+this code could not be in Dispatcher itself.
+*/
+template<class topIndexable>
+std::string Dispatcher_indexToClassName(int idx){
+	scoped_ptr<topIndexable> top(new topIndexable);
+	std::string topName=top->getClassName();
+	typedef std::pair<string,DynlibDescriptor> classItemType;
+	FOREACH(classItemType clss, Omega::instance().getDynlibsDescriptor()){
+		if(Omega::instance().isInheritingFrom_recursive(clss.first,topName) || clss.first==topName){
+			// create instance, to ask for index
+			shared_ptr<topIndexable> inst=dynamic_pointer_cast<topIndexable>(ClassFactory::instance().createShared(clss.first));
+			assert(inst);
+			if(inst->getClassIndex()<0 && inst->getClassName()!=top->getClassName()){
+				throw logic_error("Class "+inst->getClassName()+" didn't use REGISTER_CLASS_INDEX("+inst->getClassName()+","+top->getClassName()+") and/or forgot to call createIndex() in the ctor. [[ Please fix that! ]]");
+			}
+			if(inst->getClassIndex()==idx) return clss.first;
+		}
+	}
+	throw runtime_error("No class with index "+boost::lexical_cast<string>(idx)+" found (top-level indexable is "+topName+")");
+}
 
 
 template
@@ -75,7 +101,21 @@
 {
 
 	public :
-		void dump(){ DynLibDispatcher<TYPELIST_1(baseClass),FunctorType,FunctorReturnType,FunctorArguments,autoSymmetry>::dumpDispatchMatrix1D(std::cout); }
+		typedef baseClass argType1;
+		typedef FunctorType functorType;
+		typedef DynLibDispatcher<TYPELIST_1(baseClass),FunctorType,FunctorReturnType,FunctorArguments,autoSymmetry> dispatcherBase;
+
+		shared_ptr<FunctorType> getFunctor(shared_ptr<baseClass>& arg){ return getExecutor(arg); }
+		python::dict dump(bool convertIndicesToNames){
+			python::dict ret;
+			FOREACH(const DynLibDispatcher_Item1D& item, dispatcherBase::dataDispatchMatrix1D()){
+				if(convertIndicesToNames){
+					string arg1=Dispatcher_indexToClassName<argType1>(item.ix1);
+					ret[python::make_tuple(arg1)]=item.functorName;
+				} else ret[python::make_tuple(item.ix1)]=item.functorName;
+			}
+			return ret;
+		}
 		virtual void add(FunctorType* eu){ add(shared_ptr<FunctorType>(eu)); }
 		virtual void add(shared_ptr<FunctorType> eu){
 			storeFunctorName(eu->get1DFunctorType1(),eu->getClassName(),eu);
@@ -145,7 +185,21 @@
 				>
 {
 	public :
-		void dump(){ DynLibDispatcher<TYPELIST_2(baseClass1,baseClass2),FunctorType,FunctorReturnType,FunctorArguments,autoSymmetry>::dumpDispatchMatrix2D(std::cout); }
+		typedef baseClass1 argType1;
+		typedef baseClass2 argType2;
+		typedef FunctorType functorType;
+		typedef DynLibDispatcher<TYPELIST_2(baseClass1,baseClass2),FunctorType,FunctorReturnType,FunctorArguments,autoSymmetry> dispatcherBase;
+		shared_ptr<FunctorType> getFunctor(shared_ptr<baseClass1>& arg1, shared_ptr<baseClass2>& arg2){ return getExecutor(arg1,arg2); }
+		python::dict dump(bool convertIndicesToNames){
+			python::dict ret;
+			FOREACH(const DynLibDispatcher_Item2D& item, dispatcherBase::dataDispatchMatrix2D()){
+				if(convertIndicesToNames){
+					string arg1=Dispatcher_indexToClassName<argType1>(item.ix1), arg2=Dispatcher_indexToClassName<argType2>(item.ix2);
+					ret[python::make_tuple(arg1,arg2)]=item.functorName;
+				} else ret[python::make_tuple(item.ix1,item.ix2)]=item.functorName;
+			}
+			return ret;
+		}
 		/* add functor by pointer: this is convenience for calls like foo->add(new SomeFunctor); */
 		virtual void add(FunctorType* eu){ add(shared_ptr<FunctorType>(eu)); }
 		/* add functor by shared pointer */

=== modified file 'core/Functor.hpp'
--- core/Functor.hpp	2009-12-09 16:43:25 +0000
+++ core/Functor.hpp	2009-12-13 19:27:57 +0000
@@ -58,8 +58,8 @@
 {
 	public:
 		#define FUNCTOR2D(type1,type2) public: std::string get2DFunctorType1(void){return string(#type1);}; std::string get2DFunctorType2(void){return string(#type2);};
-		virtual std::string get2DFunctorType1(void){throw runtime_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");}
-		virtual std::string get2DFunctorType2(void){throw runtime_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");}
+		virtual std::string get2DFunctorType1(void){throw logic_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");}
+		virtual std::string get2DFunctorType2(void){throw logic_error("Class "+this->getClassName()+" did not use FUNCTOR2D to declare its argument types?");}
 		virtual vector<string> getFunctorTypes(){vector<string> ret; ret.push_back(get2DFunctorType1()); ret.push_back(get2DFunctorType2()); return ret;};
 	REGISTER_CLASS_AND_BASE(Functor2D,Functor FunctorWrapper);
 	/* do not REGISTER_ATTRIBUTES here, since we are template; derived classes should call REGISTER_ATTRIBUTES(Functor,(their)(own)(attributes)), bypassing Functor2D */

=== modified file 'core/Omega.cpp'
--- core/Omega.cpp	2009-12-04 23:07:34 +0000
+++ core/Omega.cpp	2009-12-13 19:27:57 +0000
@@ -181,6 +181,14 @@
 	return (dynlibs[className].baseClasses.find(baseClassName)!=dynlibs[className].baseClasses.end());
 }
 
+bool Omega::isInheritingFrom_recursive(const string& className, const string& baseClassName){
+	if (dynlibs[className].baseClasses.find(baseClassName)!=dynlibs[className].baseClasses.end()) return true;
+	FOREACH(const string& parent,dynlibs[className].baseClasses){
+		if(isInheritingFrom_recursive(parent,baseClassName)) return true;
+	}
+	return false;
+}
+
 void Omega::loadPlugins(vector<string> pluginFiles){
 	FOREACH(const string& plugin, pluginFiles){
 		LOG_DEBUG("Loading plugin "<<plugin);

=== modified file 'core/Omega.hpp'
--- core/Omega.hpp	2009-12-04 23:07:34 +0000
+++ core/Omega.hpp	2009-12-13 19:27:57 +0000
@@ -129,6 +129,7 @@
 		void		scanPlugins(vector<string> baseDirs);
 		void		loadPlugins(vector<string> pluginFiles);
 		bool		isInheritingFrom(const string& className, const string& baseClassName );
+		bool		isInheritingFrom_recursive(const string& className, const string& baseClassName );
 
 		void		setTimeStep(const Real);
 		Real		getTimeStep();

=== modified file 'lib/multimethods/DynLibDispatcher.hpp'
--- lib/multimethods/DynLibDispatcher.hpp	2009-08-21 11:44:00 +0000
+++ lib/multimethods/DynLibDispatcher.hpp	2009-12-13 19:27:57 +0000
@@ -21,6 +21,7 @@
 #include<yade/lib-loki/NullType.hpp>
 
 
+
 #include<vector>
 #include<list>
 #include<string>
@@ -31,6 +32,8 @@
 using namespace boost;
 
 
+struct DynLibDispatcher_Item2D{ int ix1, ix2; std::string functorName; DynLibDispatcher_Item2D(int a, int b, std::string c):ix1(a),ix2(b),functorName(c){}; };
+struct DynLibDispatcher_Item1D{ int ix1     ; std::string functorName; DynLibDispatcher_Item1D(int a,        std::string c):ix1(a),       functorName(c){}; };
 ///
 /// base classes involved in multiple dispatch must be derived from Indexable
 ///
@@ -58,7 +61,6 @@
 >
 class DynLibDispatcher
 {
-
 		// this template recursively defines a type for callBacks matrix, with required number of dimensions
 	private : template<class T > struct Matrix
 		  {
@@ -161,20 +163,6 @@
 	private : typedef typename Impl::Parm14 Parm14;
 	private : typedef typename Impl::Parm15 Parm15;
 	
-	// Serialization stuff.. - FIXME - maybe this should be done in separate class...
-//		bool deserializing;
-// //	protected:
-// // 		vector<vector<string> >	functorNames;
-// // 		list<shared_ptr<Executor> > functorArguments;
-// // 		typedef typename list<shared_ptr<Executor> >::iterator executorListIterator;
-			
-// 			virtual void registerAttributes()
-// 			{
-// 				REGISTER_ATTRIBUTE_(functorNames);
-// 				if(functors.size() != 0)
-// 					REGISTER_ATTRIBUTE_(functors);
-// 			}
-			
  	public  : DynLibDispatcher()
 		  {
 			// FIXME - static_assert( typeid(BaseClass1) == typeid(Parm1) ); // 1D
@@ -204,6 +192,13 @@
 			return callBacks[index1][index2];
 		  }
 
+		  shared_ptr<Executor> getExecutor(shared_ptr<BaseClass1>& arg1, shared_ptr<BaseClass2>& arg2){
+				if(arg1->getClassIndex()<0 || arg2->getClassIndex()<0) throw runtime_error("No functor for types "+arg1->getClassName()+" (index "+lexical_cast<string>(arg1->getClassIndex())+") + "+arg2->getClassName()+" (index "+lexical_cast<string>(arg2->getClassIndex())+"), since some of the indices is invalid (negative).");
+			  	int ix1,ix2;
+				if(locateMultivirtualFunctor2D(ix1,ix2,arg1,arg2)) return callBacks[ix1][ix2];
+				return shared_ptr<Executor>();
+		  }
+
  	public  : shared_ptr<Executor> getExecutor(const string& baseClassName)
 		  {
 
@@ -218,6 +213,13 @@
 			return callBacks[index];
 		  }
 
+		  shared_ptr<Executor> getExecutor(shared_ptr<BaseClass1>& arg1){
+			  	int ix1;
+				if(arg1->getClassIndex()<0) throw runtime_error("No functor for type "+arg1->getClassName()+" (index "+lexical_cast<string>(arg1->getClassIndex())+"), since the index is invalid (negative).");
+				if(locateMultivirtualFunctor2D(ix1,arg1)) return callBacks[ix1];
+				return shared_ptr<Executor>();
+		  }
+
  	public  : shared_ptr<Executor> makeExecutor(string libName)
 		  {
 			shared_ptr<Executor> executor;
@@ -241,53 +243,6 @@
 			return executor;
 		  }
 		
-// // 		void storeFunctorArguments(shared_ptr<Executor>& ex)
-// // 		{
-// // 			if(! ex) return;
-// // 			bool dupe = false;
-// // 			executorListIterator it    = functorArguments.begin();
-// // 			executorListIterator itEnd = functorArguments.end();
-// // 			for( ; it != itEnd ; ++it )
-// // 				if( (*it)->getClassName() == ex->getClassName() )
-// // 					dupe = true;
-// // 			
-// // 			if(! dupe) functorArguments.push_back(ex);
-// // 		}
-// // 		
-// // 		shared_ptr<Executor> findFunctorArguments(string libName)
-// // 		{
-// // 			executorListIterator it    = functorArguments.begin();
-// // 			executorListIterator itEnd = functorArguments.end();
-// // 			for( ; it != itEnd ; ++it )
-// // 				if( (*it)->getClassName() == libName )
-// // 					return *it;
-// // 			
-// // 			return shared_ptr<Executor>();
-// // 		}
-
-// add multivirtual function to 1D
-// // 		void postProcessDispatcher1D(bool d)
-// // 		{
-// // 			if(d)
-// // 			{
-// // 				deserializing = true;
-// // 				for(unsigned int i=0;i<functorNames.size();i++)
-// // 					add(functorNames[i][0],functorNames[i][1],findFunctorArguments(functorNames[i][1]));
-// // 				deserializing = false;
-// // 			}
-// // 		}
-		
-// // 		void storeFunctorName(	  const string& baseClassName
-// // 					, const string& libName
-// // 					, shared_ptr<Executor>& ex)
-// // 		{
-// // 			vector<string> v;
-// // 			v.push_back(baseClassName);
-// // 			v.push_back(libName);
-// // 			functorNames.push_back(v);
-// // 			storeFunctorArguments(ex);
-// // 		}
-		
  	public  : void add1DEntry( string baseClassName, string libName, shared_ptr<Executor> ex = shared_ptr<Executor>())
 		  {
 
@@ -349,31 +304,6 @@
 		  }
 
 		
-// add multivirtual function to 2D
-// // 		void postProcessDispatcher2D(bool d)
-// // 		{
-// // 			if(d)
-// // 			{
-// // 				deserializing = true;
-// // 				for(unsigned int i=0;i<functorNames.size();i++)
-// // 					add(functorNames[i][0],functorNames[i][1],functorNames[i][2],findFunctorArguments(functorNames[i][2]));
-// // 				deserializing = false;
-// // 			}
-// // 		}
-		
-// // 		void storeFunctorName(	  const string& baseClassName1
-// // 					, const string& baseClassName2
-// // 					, const string& libName
-// // 					, shared_ptr<Executor>& ex) // 2D
-// // 		{
-// // 			vector<string> v;
-// // 			v.push_back(baseClassName1);
-// // 			v.push_back(baseClassName2);
-// // 			v.push_back(libName);
-// // 			functorNames.push_back(v);
-// // 			storeFunctorArguments(ex);
-// // 		}
-		
 	public  : void add2DEntry( string baseClassName1, string baseClassName2, string libName, shared_ptr<Executor> ex = shared_ptr<Executor>())
 		  {
 			shared_ptr<BaseClass1> baseClass1 = YADE_PTR_CAST<BaseClass1>(ClassFactory::instance().createShared(baseClassName1));
@@ -455,22 +385,30 @@
 			swap=(bool)(callBacksInfo[ix1][ix2]);
 			return callBacks[ix1][ix2];
 		}
-	
+		/*! Return representation of the dispatch matrix as vector of int,int,string (i.e. index1,index2,functor name) */
+		vector<DynLibDispatcher_Item2D> dataDispatchMatrix2D(){
+			vector<DynLibDispatcher_Item2D> ret; for(size_t i=0; i<callBacks.size(); i++){ for(size_t j=0; j<callBacks.size(); j++){ if(callBacks[i][j]) ret.push_back(DynLibDispatcher_Item2D(i,j,callBacks[i][j]->getClassName())); } }
+			return ret;
+		}
+		/*! Return representation of the dispatch matrix as vector of int,string (i.e. index,functor name) */
+		vector<DynLibDispatcher_Item1D> dataDispatchMatrix1D(){
+			vector<DynLibDispatcher_Item1D> ret; for(size_t i=0; i<callBacks.size(); i++){ if(callBacks[i]) ret.push_back(DynLibDispatcher_Item1D(i,callBacks[i]->getClassName())); }
+			return ret;
+		}
+		/*! Dump 2d dispatch matrix to given stream. */
 		std::ostream& dumpDispatchMatrix2D(std::ostream& out, const string& prefix=""){
-			for(size_t i=0; i<callBacks.size(); i++){
-				for(size_t j=0; j<callBacks.size(); j++){
-					if(callBacks[i][j]) out<<prefix<<i<<"+"<<j<<" -> "<<callBacks[i][j]->getClassName()<<std::endl;
-				}
-			}
+			for(size_t i=0; i<callBacks.size(); i++){	for(size_t j=0; j<callBacks.size(); j++){
+				if(callBacks[i][j]) out<<prefix<<i<<"+"<<j<<" -> "<<callBacks[i][j]->getClassName()<<std::endl;
+			}}
 			return out;
 		}
+		/*! Dump 1d dispatch matrix to given stream. */
 		std::ostream& dumpDispatchMatrix1D(std::ostream& out, const string& prefix=""){
 			for(size_t i=0; i<callBacks.size(); i++){
-					if(callBacks[i]) out<<prefix<<i<<" -> "<<callBacks[i]->getClassName()<<std::endl;
+				if(callBacks[i]) out<<prefix<<i<<" -> "<<callBacks[i]->getClassName()<<std::endl;
 			}
 			return out;
 		}
-
 		bool locateMultivirtualFunctor2D(int& index1, int& index2, shared_ptr<BaseClass1>& base1,shared_ptr<BaseClass2>& base2)
 		  {
 			//#define _DISP_TRACE(msg) cerr<<"@DT@"<<__LINE__<<" "<<msg<<endl;
@@ -479,107 +417,50 @@
 			assert(index1>=0); assert(index2>=0); 
 			assert((unsigned int)(index1)<callBacks.size()); assert((unsigned int)(index2)<callBacks[index1].size());
 			_DISP_TRACE("arg1: "<<base1->getClassName()<<"="<<index1<<"; arg2: "<<base2->getClassName()<<"="<<index2)
-			#define _FIX_2D_DISPATCHES
-			#ifdef _FIX_2D_DISPATCHES
-				/* This is python pseudocode for the algorithm:
-
-					def ff(x,sum): print x,sum-x,sum
-					for dist in range(0,5):
-						for ix1 in range(0,dist+1): ff(ix1,dist)
-
-					Increase depth sum from 0 up and look for possible matches, of which sum of distances beween the argument and the declared functor arg type equals depth.
-					
-					Two matches are considered euqally good (ambiguous) if they have the same depth. That raises exception.
-
-					If both indices are negative (reached the top of hierarchy for that indexable type) and nothing has been found for given depth, raise exception (undefined dispatch).
-
-					FIXME: by the original design, callBacks don't distinguish between dispatch that was already looked for,
-					but is undefined and dispatch that was never looked for before. This means that there can be lot of useless lookups;
-					e.g. if MetaInteractingGeometry2AABB is not in BoundingVoumeMetaEngine, it is looked up at every step.
-
-				*/
-				if(callBacks[index1][index2]){ _DISP_TRACE("Direct hit at ["<<index1<<"]["<<index2<<"] → "<<callBacks[index1][index2]->getClassName()); return true; }
-				int foundIx1,foundIx2; int maxDp1=-1, maxDp2=-1;
-				// if(base1->getBaseClassIndex(0)<0) maxDp1=0; if(base2->getBaseClassIndex(0)<0) maxDp2=0;
-				for(int dist=1; ; dist++){
-					bool distTooBig=true;
-					foundIx1=foundIx2=-1; // found no dispatch at this depth yet
-					for(int dp1=0; dp1<=dist; dp1++){
-						int dp2=dist-dp1;
-						if((maxDp1>=0 && dp1>maxDp1) || (maxDp2>=0 && dp2>maxDp2)) continue;
-						_DISP_TRACE(" Trying indices with depths "<<dp1<<" and "<<dp2<<", dist="<<dist);
-						int ix1=dp1>0?base1->getBaseClassIndex(dp1):index1, ix2=dp2>0?base2->getBaseClassIndex(dp2):index2;
-						if(ix1<0) maxDp1=dp1; if(ix2<0) maxDp2=dp2;
-						if(ix1<0 || ix2<0) continue; // hierarchy height exceeded in either dimension
-						distTooBig=false;
-						if(callBacks[ix1][ix2]){
-							if(foundIx1!=-1 && callBacks[foundIx1][foundIx2]!=callBacks[ix1][ix2]){ // we found a callback, but there already was one at this distance and it was different from the current one
-								cerr<<__FILE__<<":"<<__LINE__<<": ambiguous 2d dispatch ("<<"arg1="<<base1->getClassName()<<", arg2="<<base2->getClassName()<<", distance="<<dist<<"), dispatch matrix:"<<endl;
-								dumpDispatchMatrix2D(cerr,"AMBIGUOUS: "); throw runtime_error("Ambiguous dispatch.");
-							}
-							foundIx1=ix1; foundIx2=ix2;
-							callBacks[index1][index2]=callBacks[ix1][ix2]; callBacksInfo[index1][index2]=callBacksInfo[ix1][ix2];
-							_DISP_TRACE("Found callback ["<<ix1<<"]["<<ix2<<"] → "<<callBacks[ix1][ix2]->getClassName());
-						}
-					}
-					if(foundIx1!=-1) return true;
-					if(distTooBig){ _DISP_TRACE("Undefined dispatch, dist="<<dist); return false; /* undefined dispatch */ }
-				}
-			#else
-				#if 0 
-					if((unsigned)index1>=callBacks.size()) cerr<<__FILE__<<":"<<__LINE__<<" FATAL: Index out of range for class "<<base1->getClassName()<<" (index=="<<index1<<", callBacks.size()=="<<callBacks.size()<<endl;
-					if((unsigned)index2>=callBacks[index2].size()) cerr<<__FILE__<<":"<<__LINE__<<" FATAL: Index out of range for class "<<base2->getClassName()<<" (index=="<<index2<<", callBacks[index1].size()=="<<callBacks[index1].size()<<endl;
-				#endif
-					
-				if(callBacks[index1][index2]){
-					_DISP_TRACE("Direct hit at ["<<index1<<"]["<<index2<<"] → "<<callBacks[index1][index2]->getClassName());
-					return true;
-				}
-
-				int depth1=1, depth2=1;
-				int index1_tmp=base1->getBaseClassIndex(depth1), index2_tmp = base2->getBaseClassIndex(depth2);
-				_DISP_TRACE("base classes: "<<base1->getBaseClassName()<<"="<<index1_tmp<<", "<<base2->getBaseClassName()<<"="<<index2_tmp);
-				if(index1_tmp == -1) {
-					while(1){
-						if(index2_tmp == -1){
-							_DISP_TRACE("Returning FALSE");
-							return false;
-						}
-						if(callBacks[index1][index2_tmp]){ // FIXME - this is not working, when index1 or index2 is out-of-boundary. I have to resize callBacks and callBacksInfo tables.  - this should be a separate function to resize stuff
-							callBacksInfo[index1][index2] = callBacksInfo[index1][index2_tmp];
-							callBacks    [index1][index2] = callBacks    [index1][index2_tmp];
-	//						index2 = index2_tmp;
-							_DISP_TRACE("Found callback ["<<index1<<"]["<<index2_tmp<<"] → "<<callBacks[index1][index2_tmp]->getClassName());
-							return true;
-						}
-						index2_tmp = base2->getBaseClassIndex(++depth2);
-						_DISP_TRACE("index2_tmp="<<index2_tmp<<" (pushed up)");
-					}
-				}
-				else if(index2_tmp == -1) {
-					while(1){
-						if(index1_tmp == -1){
-							_DISP_TRACE("Returning FALSE");
-							 return false;
-						}
-						if(callBacks[index1_tmp][index2]){
-							callBacksInfo[index1][index2] = callBacksInfo[index1_tmp][index2];
-							callBacks    [index1][index2] = callBacks    [index1_tmp][index2];
-	//						index1 = index1_tmp;
-							_DISP_TRACE("Found callback ["<<index1_tmp<<"]["<<index2<<"] → "<<callBacks[index1_tmp][index2]->getClassName());
-							return true;
-						}
-						index1_tmp = base1->getBaseClassIndex(++depth1);
-						_DISP_TRACE("index1_tmp="<<index1_tmp<<" (pushed up)");
-					}
-				}
-				//else if( index1_tmp != -1 && index2_tmp != -1 )
-				_DISP_TRACE("UNDEFINED/AMBIGUOUS, dumping dispatch matrix");
-				dumpDispatchMatrix2D(cerr);
-				_DISP_TRACE("end matrix dump.")
-				throw std::runtime_error("DynLibDispatcher: ambiguous or undefined dispatch for 2d multivirtual function, classes: "+base1->getClassName()+" "+base2->getClassName());
-				//return false;
-			#endif
+			/* This is python code for the algorithm:
+
+				def ff(x,sum): print x,sum-x,sum
+				for dist in range(0,5):
+					for ix1 in range(0,dist+1): ff(ix1,dist)
+
+				Increase depth sum from 0 up and look for possible matches, of which sum of distances beween the argument and the declared functor arg type equals depth.
+				
+				Two matches are considered euqally good (ambiguous) if they have the same depth. That raises exception.
+
+				If both indices are negative (reached the top of hierarchy for that indexable type) and nothing has been found for given depth, raise exception (undefined dispatch).
+
+				FIXME: by the original design, callBacks don't distinguish between dispatch that was already looked for,
+				but is undefined and dispatch that was never looked for before. This means that there can be lot of useless lookups;
+				e.g. if MetaInteractingGeometry2AABB is not in BoundingVoumeMetaEngine, it is looked up at every step.
+
+			*/
+			if(callBacks[index1][index2]){ _DISP_TRACE("Direct hit at ["<<index1<<"]["<<index2<<"] → "<<callBacks[index1][index2]->getClassName()); return true; }
+			int foundIx1,foundIx2; int maxDp1=-1, maxDp2=-1;
+			// if(base1->getBaseClassIndex(0)<0) maxDp1=0; if(base2->getBaseClassIndex(0)<0) maxDp2=0;
+			for(int dist=1; ; dist++){
+				bool distTooBig=true;
+				foundIx1=foundIx2=-1; // found no dispatch at this depth yet
+				for(int dp1=0; dp1<=dist; dp1++){
+					int dp2=dist-dp1;
+					if((maxDp1>=0 && dp1>maxDp1) || (maxDp2>=0 && dp2>maxDp2)) continue;
+					_DISP_TRACE(" Trying indices with depths "<<dp1<<" and "<<dp2<<", dist="<<dist);
+					int ix1=dp1>0?base1->getBaseClassIndex(dp1):index1, ix2=dp2>0?base2->getBaseClassIndex(dp2):index2;
+					if(ix1<0) maxDp1=dp1; if(ix2<0) maxDp2=dp2;
+					if(ix1<0 || ix2<0) continue; // hierarchy height exceeded in either dimension
+					distTooBig=false;
+					if(callBacks[ix1][ix2]){
+						if(foundIx1!=-1 && callBacks[foundIx1][foundIx2]!=callBacks[ix1][ix2]){ // we found a callback, but there already was one at this distance and it was different from the current one
+							cerr<<__FILE__<<":"<<__LINE__<<": ambiguous 2d dispatch ("<<"arg1="<<base1->getClassName()<<", arg2="<<base2->getClassName()<<", distance="<<dist<<"), dispatch matrix:"<<endl;
+							dumpDispatchMatrix2D(cerr,"AMBIGUOUS: "); throw runtime_error("Ambiguous dispatch.");
+						}
+						foundIx1=ix1; foundIx2=ix2;
+						callBacks[index1][index2]=callBacks[ix1][ix2]; callBacksInfo[index1][index2]=callBacksInfo[ix1][ix2];
+						_DISP_TRACE("Found callback ["<<ix1<<"]["<<ix2<<"] → "<<callBacks[ix1][ix2]->getClassName());
+					}
+				}
+				if(foundIx1!=-1) return true;
+				if(distTooBig){ _DISP_TRACE("Undefined dispatch, dist="<<dist); return false; /* undefined dispatch */ }
+			}
 		};
 
 		

=== modified file 'lib/multimethods/Indexable.hpp'
--- lib/multimethods/Indexable.hpp	2009-01-27 02:19:40 +0000
+++ lib/multimethods/Indexable.hpp	2009-12-13 19:27:57 +0000
@@ -11,12 +11,16 @@
 #pragma once
 
 #include <boost/scoped_ptr.hpp>
+#include<stdexcept>
+#include<string>
 
 /*! \brief Abstract interface for all Indexable class.
 	An indexable class is a class that will be managed by a MultiMethodManager.
 	The index the function getClassIndex() returns, corresponds to the index in the matrix where the class will be handled.
 */
 
+#define _THROW_NOT_OVERRIDDEN  throw std::logic_error(std::string("Derived class did not override ")+__PRETTY_FUNCTION__+", use REGISTER_INDEX_COUNTER and REGISTER_CLASS_INDEX.")
+
 class Indexable
 {
 	protected :
@@ -26,102 +30,58 @@
 		Indexable ();
 		virtual ~Indexable ();
 
-		virtual int& getClassIndex() { throw;};  /// Returns the id of the current class. This id is set by a multimethod manager
-		virtual const int& getClassIndex() const { throw;};
-		virtual int& getBaseClassIndex(int ) { throw;};
-		virtual const int& getBaseClassIndex(int ) const { throw;};
-		
-		virtual const int& getMaxCurrentlyUsedClassIndex() const { throw;};
-		virtual void incrementMaxCurrentlyUsedClassIndex() { throw;};
+		/// Returns the id of the current class. This id is set by a multimethod manager
+		virtual int& getClassIndex()                             { _THROW_NOT_OVERRIDDEN;}; 
+		virtual const int& getClassIndex() const                 { _THROW_NOT_OVERRIDDEN;};
+		virtual int& getBaseClassIndex(int )                     { _THROW_NOT_OVERRIDDEN;};
+		virtual const int& getBaseClassIndex(int ) const         { _THROW_NOT_OVERRIDDEN;};
+		virtual const int& getMaxCurrentlyUsedClassIndex() const { _THROW_NOT_OVERRIDDEN;};
+		virtual void incrementMaxCurrentlyUsedClassIndex()       { _THROW_NOT_OVERRIDDEN;};
 
 };
 
+#undef _THROW_NOT_OVERRIDDEN
+
 // this macro is used by classes that are a dimension in multimethod matrix
 
-// this assert was removed, because I must ask Base class for it's own index.
-//		assert(typeid(*this)==typeid(SomeClass));
-
-#define REGISTER_CLASS_INDEX(SomeClass,BaseClass)				\
-	private: static int& getClassIndexStatic()				\
-	{									\
-		static int index = -1;						\
-		return index;							\
-	}									\
-	public: virtual int& getClassIndex()					\
-	{									\
-		return getClassIndexStatic();					\
-	}									\
-	public: virtual const int& getClassIndex() const			\
-	{									\
-		return getClassIndexStatic();					\
-	}									\
-										\
-										\
-	public: virtual int& getBaseClassIndex(int depth)			\
-	{									\
-		static boost::scoped_ptr<BaseClass> baseClass(new BaseClass);	\
-		if(depth == 1)							\
-			return baseClass->getClassIndex();			\
-		else								\
-			return baseClass->getBaseClassIndex(--depth);		\
-	}									\
-	public: virtual const int& getBaseClassIndex(int depth) const		\
-	{									\
-		static boost::scoped_ptr<BaseClass> baseClass(new BaseClass);	\
-		if(depth == 1)							\
-			return baseClass->getClassIndex();			\
-		else								\
-			return baseClass->getBaseClassIndex(--depth);		\
+#define REGISTER_CLASS_INDEX(SomeClass,BaseClass)                                      \
+	private: static int& getClassIndexStatic() { static int index = -1; return index; } \
+	public: virtual int& getClassIndex()       { return getClassIndexStatic(); }        \
+	public: virtual const int& getClassIndex() const { return getClassIndexStatic(); }  \
+	public: virtual int& getBaseClassIndex(int depth) {              \
+		static boost::scoped_ptr<BaseClass> baseClass(new BaseClass); \
+		if(depth == 1) return baseClass->getClassIndex();             \
+		else           return baseClass->getBaseClassIndex(--depth);  \
+	}                                                                \
+	public: virtual const int& getBaseClassIndex(int depth) const {  \
+		static boost::scoped_ptr<BaseClass> baseClass(new BaseClass); \
+		if(depth == 1) return baseClass->getClassIndex();             \
+		else           return baseClass->getBaseClassIndex(--depth);  \
 	}
 
 // this macro is used by base class for classes that are a dimension in multimethod matrix
 // to keep track of maximum number of classes of their kin. Multimethod matrix can't
-// count this number (ie. as a size of the matrix) - because there are many multimethod matrices!
+// count this number (ie. as a size of the matrix), as there are many multimethod matrices
 
-#define REGISTER_INDEX_COUNTER(SomeClass)					\
-										\
-	private: static int& getClassIndexStatic()				\
-	{									\
-		static int index = -1;						\
-		return index;							\
-	}									\
-	public: virtual int& getClassIndex()					\
-	{									\
-		return getClassIndexStatic();					\
-	}									\
-	public: virtual const int& getClassIndex() const			\
-	{									\
-		return getClassIndexStatic();					\
-	}									\
-	public: virtual int& getBaseClassIndex(int)				\
-	{									\
-		throw;								\
-	}									\
-	public: virtual const int& getBaseClassIndex(int) const			\
-	{									\
-		throw;								\
-	}									\
-										\
-										\
-	private: static int& getMaxCurrentlyUsedIndexStatic()			\
-	{									\
-		static int maxCurrentlyUsedIndex = -1;				\
-		return maxCurrentlyUsedIndex;					\
-	}									\
-	public: virtual const int& getMaxCurrentlyUsedClassIndex() const	\
-	{									\
-		SomeClass * Indexable##SomeClass = 0;				\
-		Indexable##SomeClass = dynamic_cast<SomeClass*>(const_cast<SomeClass*>(this));		\
-		assert(Indexable##SomeClass);					\
-		return getMaxCurrentlyUsedIndexStatic();			\
-	}									\
-	public: virtual void incrementMaxCurrentlyUsedClassIndex()		\
-	{									\
-		SomeClass * Indexable##SomeClass = 0;				\
-		Indexable##SomeClass = dynamic_cast<SomeClass*>(this);		\
-		assert(Indexable##SomeClass);					\
-		int& max = getMaxCurrentlyUsedIndexStatic();			\
-		max++;								\
-	}									\
+#define REGISTER_INDEX_COUNTER(SomeClass) \
+	private: static int& getClassIndexStatic()       { static int index = -1; return index; }\
+	public: virtual int& getClassIndex()             { return getClassIndexStatic(); }       \
+	public: virtual const int& getClassIndex() const { return getClassIndexStatic(); }       \
+	public: virtual int& getBaseClassIndex(int)             { throw std::logic_error("Class " #SomeClass " should not have called createIndex() in its ctor (since it is a top-level indexable, using REGISTER_INDEX_COUNTER"); } \
+	public: virtual const int& getBaseClassIndex(int) const { throw std::logic_error("Class " #SomeClass " should not have called createIndex() in its ctor (since it is a top-level indexable, using REGISTER_INDEX_COUNTER"); } \
+	private: static int& getMaxCurrentlyUsedIndexStatic() { static int maxCurrentlyUsedIndex = -1; return maxCurrentlyUsedIndex; } \
+	public: virtual const int& getMaxCurrentlyUsedClassIndex() const {  \
+		SomeClass * Indexable##SomeClass = 0;                            \
+		Indexable##SomeClass = dynamic_cast<SomeClass*>(const_cast<SomeClass*>(this)); \
+		assert(Indexable##SomeClass);                                    \
+		return getMaxCurrentlyUsedIndexStatic();                         \
+	}                                                                   \
+	public: virtual void incrementMaxCurrentlyUsedClassIndex() {        \
+		SomeClass * Indexable##SomeClass = 0;                            \
+		Indexable##SomeClass = dynamic_cast<SomeClass*>(this);           \
+		assert(Indexable##SomeClass);                                    \
+		int& max = getMaxCurrentlyUsedIndexStatic();                     \
+		max++;                                                           \
+	}
 
 

=== modified file 'pkg/common/Engine/Dispatcher/InteractionGeometryFunctor.hpp'
--- pkg/common/Engine/Dispatcher/InteractionGeometryFunctor.hpp	2009-12-06 22:02:12 +0000
+++ pkg/common/Engine/Dispatcher/InteractionGeometryFunctor.hpp	2009-12-13 19:27:57 +0000
@@ -27,6 +27,7 @@
 	\param State&					first Body's State
 	\param State&					second Body's State
 	\param Vector3r& 				second Body's relative shift (for periodicity)
+	\param bool & force        force creation of the geometry, even if the interaction doesn't exist yet and the bodies are too far for regular contact (used from explicitAction)
 	\return shared_ptr<Interaction>&		it returns the Interaction to be built (given as last argument to the function)
 	
 */

=== modified file 'pkg/dem/meta/Tetra.hpp'
--- pkg/dem/meta/Tetra.hpp	2009-12-11 18:10:19 +0000
+++ pkg/dem/meta/Tetra.hpp	2009-12-13 19:27:57 +0000
@@ -49,12 +49,13 @@
 		Vector3r contactPoint;
 		Vector3r normal;
 
-		TetraBang(): InteractionGeometry(){};
+		TetraBang() { createIndex(); };
 		virtual ~TetraBang(){};
 	protected:
 		REGISTER_ATTRIBUTES(InteractionGeometry,(penetrationVolume)(equivalentCrossSection)(contactPoint)(normal)(equivalentPenetrationDepth)(maxPenetrationDepthA)(maxPenetrationDepthB));
 		FUNCTOR2D(TetraMold,TetraMold);
 		REGISTER_CLASS_AND_BASE(TetraBang,InteractionGeometry);
+		REGISTER_CLASS_INDEX(TetraBang,InteractionGeometry);
 };
 REGISTER_SERIALIZABLE(TetraBang);
 

=== modified file 'py/utils.py'
--- py/utils.py	2009-12-11 18:10:19 +0000
+++ py/utils.py	2009-12-13 19:27:57 +0000
@@ -25,18 +25,17 @@
 def saveVars(mark='',loadNow=False,**kw):
 	"""Save passed variables into the simulation so that it can be recovered when the simulation is loaded again.
 
-	For example, variables a=5, b=66 and c=7.5e-4 are defined. To save those, use
-
-	 utils.saveVars(a=a,b=b,c=c)
-
-	those variables will be save in the .xml file, when the simulation itself is saved. To recover those variables once
-	the .xml is loaded again, use
-
-	 utils.loadVars(mark)
+	For example, variables a=5, b=66 and c=7.5e-4 are defined. To save those, use::
+
+		>>> utils.saveVars(a=a,b=b,c=c)
+
+	those variables will be save in the .xml file, when the simulation itself is saved. To recover those variables once the .xml is loaded again, use
+
+		>>> utils.loadVars(mark)
 
 	and they will be defined in the __builtin__ namespace (i.e. available from anywhere in the python code).
 
-	If loadParam==True, variables will be loaded immediately after saving. That effectively makes **kw available in builtin namespace.
+	If *loadParam*==True, variables will be loaded immediately after saving. That effectively makes *\*\*kw* available in builtin namespace.
 	"""
 	import cPickle
 	Omega().tags['pickledPythonVariablesDictionary'+mark]=cPickle.dumps(kw)
@@ -104,36 +103,39 @@
 		`material`: int or string or Material instance
 			if int, O.materials[material] will be used; as a special case, if material==0 and there is no shared materials defined, utils.defaultMaterial() will be assigned to O.materials[0].
 
+	:return:
+		A Body instance with desired characteristics.
+
 	>>> O.reset()
 	>>> from yade import utils
 
 	Creating default shared material if none exists neither is given::
 
-	>>> len(O.materials)
-	0
-	>>> s0=utils.sphere([2,0,0],1)
-	>>> len(O.materials)
-	1
+		>>> len(O.materials)
+		0
+		>>> s0=utils.sphere([2,0,0],1)
+		>>> len(O.materials)
+		1
 
 	Instance of material can be given::
 
-	>>> s1=utils.sphere([0,0,0],1,wire=False,color=(0,1,0),material=ElasticMat(young=30e9,density=2e3))
-	>>> s1.shape['wire']
-	False
-	>>> s1.shape['diffuseColor']
-	Vector3(0,1,0)
-	>>> s1.mat['density']
-	2000.0
+		>>> s1=utils.sphere([0,0,0],1,wire=False,color=(0,1,0),material=ElasticMat(young=30e9,density=2e3))
+		>>> s1.shape['wire']
+		False
+		>>> s1.shape['diffuseColor']
+		Vector3(0,1,0)
+		>>> s1.mat['density']
+		2000.0
 
 	Material can be given by label::
 
-	>>> O.materials.append(GranularMat(young=10e9,poisson=.11,label='myMaterial'))
-	1
-	>>> s2=utils.sphere([0,0,2],1,material='myMaterial')
-	>>> s2.mat['label']
-	'myMaterial'
-	>>> s2.mat['poisson']
-	0.11
+		>>> O.materials.append(GranularMat(young=10e9,poisson=.11,label='myMaterial'))
+		1
+		>>> s2=utils.sphere([0,0,2],1,material='myMaterial')
+		>>> s2.mat['label']
+		'myMaterial'
+		>>> s2.mat['poisson']
+		0.11
 
 	"""
 	b=Body()
@@ -190,7 +192,8 @@
 	:Parameters:
 		`vertices`: [Vector3,Vector3,Vector3]
 			coordinates of vertices in the global coordinate system.
-		`noBound`: do not assign Body().bound
+		`noBound`:
+			do not assign Body().bound
 	
 	See utils.sphere's documentation for meaning of other parameters."""
 	b=Body()
@@ -212,7 +215,8 @@
 		`wallMask`: bitmask
 			 determines which walls will be created, in the order -x (1), +x (2), -y (4), +y (8), -z (16), +z (32).
 			 The numbers are ANDed; the default 63 means to create all walls.
-		`**kw`: passed to utils.facet.
+		`**kw`:
+			passed to utils.facet.
 	
 	:Return:
 		List of facets forming the box.
@@ -573,10 +577,7 @@
 	"""
 	Read parameters from a file and assign them to __builtin__ variables.
 
-	tableFile is a text file (with one value per blank-separated columns)
-	tableLine is number of line where to get the values from
-
-		The format of the file is as follows (commens starting with # and empty lines allowed)
+	The format of the file is as follows (commens starting with # and empty lines allowed)::
 		
 		# commented lines allowed anywhere
 		name1 name2 … # first non-blank line are column headings
@@ -585,15 +586,27 @@
 		val2  val2  … # 2nd 
 		…
 
-	The name `description' is special and is assigned to Omega().tags['description']
-
-	assigns Omega().tags['params']="name1=val1,name2=val2,…"
-	
-	assigns Omega().tags['defaultParams']="unassignedName1=defaultValue1,…"
-
-	saves all parameters (default as well as settable) using saveVars('table')
-
-	return value is the number of assigned parameters.
+	Assigned tags:
+
+	* *description* column is assigned to Omega().tags['description']; this column is synthesized if absent (see :ref:`TableParamReader`)
+	* Omega().tags['params']="name1=val1,name2=val2,…"
+	* Omega().tags['defaultParams']="unassignedName1=defaultValue1,…"
+
+	All parameters (default as well as settable) are saved using saveVars('table').
+
+	:parameters:
+		`tableFile`:
+			text file (with one value per blank-separated columns)
+		`tableLine`:
+			number of line where to get the values from.
+		`noTableOk`: bool
+			do not raise exception if the file cannot be open; use default values
+		`unknownOk`: bool
+			do not raise exception if unknown column name is found in the file; assign it as well
+
+	:return:
+		number of assigned parameters.
+
 	"""
 	tagsParams=[]
 	dictDefaults,dictParams={},{}

=== modified file 'py/yadeWrapper/yadeWrapper.cpp'
--- py/yadeWrapper/yadeWrapper.cpp	2009-12-11 18:10:19 +0000
+++ py/yadeWrapper/yadeWrapper.cpp	2009-12-13 19:27:57 +0000
@@ -151,7 +151,7 @@
 	}
 	body_id_t append(shared_ptr<Body> b){
 		// shoud be >=0, but Body is by default created with id 0... :-|
-		if(b->getId()>0){ PyErr_SetString(PyExc_IndexError,("Body already has id "+lexical_cast<string>(b->getId())+"set; appending such body is not allowed.").c_str()); python::throw_error_already_set(); }
+		if(b->getId()>=0){ PyErr_SetString(PyExc_IndexError,("Body already has id "+lexical_cast<string>(b->getId())+"set; appending such body (for the second time) is not allowed.").c_str()); python::throw_error_already_set(); }
 		return proxee->insert(b);
 	}
 	vector<body_id_t> appendList(vector<shared_ptr<Body> > bb){
@@ -479,7 +479,7 @@
 	}
 
 	bool isChildClassOf(const string& child, const string& base){
-		return (Omega::instance().isInheritingFrom(child,base));
+		return (Omega::instance().isInheritingFrom_recursive(child,base));
 	}
 
 	python::list plugins_get(){
@@ -600,17 +600,17 @@
 }
 
 // FIXME: those could be moved to the c++ classes themselves, right?
-template<typename DispatcherT, typename functorT>
-shared_ptr<DispatcherT> Dispatcher_ctor_list(const std::vector<shared_ptr<functorT> >& functors){
+template<typename DispatcherT>
+shared_ptr<DispatcherT> Dispatcher_ctor_list(const std::vector<shared_ptr<typename DispatcherT::functorType> >& functors){
 	shared_ptr<DispatcherT> instance(new DispatcherT);
-	FOREACH(shared_ptr<functorT> functor,functors) instance->add(functor);
+	FOREACH(shared_ptr<typename DispatcherT::functorType> functor,functors) instance->add(functor);
 	return instance;
 }
 // FIXME: this one as well
-template<typename DispatcherT, typename functorT>
-std::vector<shared_ptr<functorT> > Dispatcher_functors_get(shared_ptr<DispatcherT> self){
-	std::vector<shared_ptr<functorT> > ret;
-	FOREACH(const shared_ptr<Functor>& functor, self->functorArguments){ shared_ptr<functorT> functorRightType(dynamic_pointer_cast<functorT>(functor)); if(!functorRightType) throw logic_error("Internal error: Dispatcher of type "+self->getClassName()+" did not contain Functor of the required type "+typeid(functorT).name()+"?"); ret.push_back(functorRightType); }
+template<typename DispatcherT>
+std::vector<shared_ptr<typename DispatcherT::functorType> > Dispatcher_functors_get(shared_ptr<DispatcherT> self){
+	std::vector<shared_ptr<typename DispatcherT::functorType> > ret;
+	FOREACH(const shared_ptr<Functor>& functor, self->functorArguments){ shared_ptr<typename DispatcherT::functorType> functorRightType(dynamic_pointer_cast<typename DispatcherT::functorType>(functor)); if(!functorRightType) throw logic_error("Internal error: Dispatcher of type "+self->getClassName()+" did not contain Functor of the required type "+typeid(typename DispatcherT::functorType).name()+"?"); ret.push_back(functorRightType); }
 	return ret;
 }
 // FIXME: and this one as well
@@ -622,21 +622,19 @@
 	return instance;
 }
 
-template<typename someIndexable>
-int Indexable_getClassIndex(const shared_ptr<someIndexable> i){return i->getClassIndex();}
-//template<typename someIndexable>
-//int Indexable_getBaseClassIndex(const shared_ptr<someIndexable> i){return i->getBaseClassIndex(1);}
-template<typename someIndexable>
-vector<int> Indexable_getClassIndices(const shared_ptr<someIndexable> i){
-	int depth=1; vector<int> ret;
-	ret.push_back(i->getClassIndex());
+template<typename TopIndexable>
+int Indexable_getClassIndex(const shared_ptr<TopIndexable> i){return i->getClassIndex();}
+template<typename TopIndexable>
+python::list Indexable_getClassIndices(const shared_ptr<TopIndexable> i, bool convertToNames){
+	int depth=1; python::list ret; int idx0=i->getClassIndex();
+	if(convertToNames) ret.append(Dispatcher_indexToClassName<TopIndexable>(idx0));
+	else ret.append(idx0);
+	if(idx0<0) return ret; // don't continue and call getBaseClassIndex(), since we are at the top already
 	while(true){
-		//cerr<<"depth="<<depth;
-		int idx=i->getBaseClassIndex(depth);
-		//cerr<<", idx="<<idx<<endl;
+		int idx=i->getBaseClassIndex(depth++);
+		if(convertToNames) ret.append(Dispatcher_indexToClassName<TopIndexable>(idx));
+		else ret.append(idx);
 		if(idx<0) return ret;
-		ret.push_back(idx);
-		depth++;
 	}
 }
 		
@@ -828,16 +826,16 @@
 		.def("__init__",python::make_constructor(ParallelEngine_ctor_list))
 		.add_property("slaves",&ParallelEngine_slaves_get,&ParallelEngine_slaves_set);
 
-	#define EXPOSE_DISPATCHER(DispatcherT,functorT) python::class_<DispatcherT, shared_ptr<DispatcherT>, python::bases<Dispatcher>, noncopyable >(#DispatcherT).def("__init__",python::make_constructor(Dispatcher_ctor_list<DispatcherT,functorT>)).add_property("functors",&Dispatcher_functors_get<DispatcherT,functorT>).def("dump",&DispatcherT::dump);
-		EXPOSE_DISPATCHER(BoundDispatcher,BoundFunctor)
-		EXPOSE_DISPATCHER(InteractionGeometryDispatcher,InteractionGeometryFunctor)
-		EXPOSE_DISPATCHER(InteractionPhysicsDispatcher,InteractionPhysicsFunctor)
+	#define EXPOSE_DISPATCHER(DispatcherT) python::class_<DispatcherT, shared_ptr<DispatcherT>, python::bases<Dispatcher>, noncopyable >(#DispatcherT).def("__init__",python::make_constructor(Dispatcher_ctor_list<DispatcherT>)).add_property("functors",&Dispatcher_functors_get<DispatcherT>).def("dispMatrix",&DispatcherT::dump,python::arg("names")=true,"Return dictionary with contents of the dispatch matrix.").def("dispFunctor",&DispatcherT::getFunctor,"Return functor that would be dispatched for given argument(s); None if no dispatch; ambiguous dispatch throws.");
+		EXPOSE_DISPATCHER(BoundDispatcher)
+		EXPOSE_DISPATCHER(InteractionGeometryDispatcher)
+		EXPOSE_DISPATCHER(InteractionPhysicsDispatcher)
 		#ifdef YADE_PHYSPAR
-			EXPOSE_DISPATCHER(StateMetaEngine,StateEngineUnit)
-			EXPOSE_DISPATCHER(PhysicalActionDamper,PhysicalActionDamperUnit)
-			EXPOSE_DISPATCHER(PhysicalActionApplier,PhysicalActionApplierUnit)
+			EXPOSE_DISPATCHER(StateMetaEngine)
+			EXPOSE_DISPATCHER(PhysicalActionDamper)
+			EXPOSE_DISPATCHER(PhysicalActionApplier)
 		#endif
-		EXPOSE_DISPATCHER(LawDispatcher,LawFunctor)
+		EXPOSE_DISPATCHER(LawDispatcher)
 	#undef EXPOSE_DISPATCHER
 
 	#define EXPOSE_FUNCTOR(FunctorT) python::class_<FunctorT, shared_ptr<FunctorT>, python::bases<Functor>, noncopyable>(#FunctorT).def("__init__",python::raw_constructor(Serializable_ctor_kwAttrs<FunctorT>));
@@ -855,7 +853,7 @@
 	#define EXPOSE_CXX_CLASS_RENAMED(cxxName,pyName) python::class_<cxxName,shared_ptr<cxxName>, python::bases<Serializable>, noncopyable>(#pyName).def("__init__",python::raw_constructor(Serializable_ctor_kwAttrs<cxxName>))
 	#define EXPOSE_CXX_CLASS(className) EXPOSE_CXX_CLASS_RENAMED(className,className)
 	// expose indexable class, with access to the index
-	#define EXPOSE_CXX_CLASS_IX(className) EXPOSE_CXX_CLASS(className).add_property("classIndex",&Indexable_getClassIndex<className>,"Return class index of this instance.").add_property("classIndices",&Indexable_getClassIndices<className>,"Return list of indices of base classes, starting from the class instance itself, then immediate parent and so on to the top-level indexable at last.")
+	#define EXPOSE_CXX_CLASS_IX(className) EXPOSE_CXX_CLASS(className).add_property("dispIndex",&Indexable_getClassIndex<className>,"Return class index of this instance.").def("dispHierarchy",&Indexable_getClassIndices<className>,(python::arg("names")=true),"Return list of dispatch classes (from down upwards), starting with the class instance itself, top-level indexable at last. If names is true (default), return class names rather than numerical indices.")
 
 	EXPOSE_CXX_CLASS(Body)
 		// mold and geom are deprecated:

=== modified file 'scripts/default-test.py'
--- scripts/default-test.py	2009-12-06 17:18:16 +0000
+++ scripts/default-test.py	2009-12-13 19:27:57 +0000
@@ -89,8 +89,9 @@
 	reportText='\n'.join([80*'#'+'\n'+r[0]+': '+r[1]+'\n'+80*'#'+'\n'+r[2] for r in reports])
 	if mailTo and mailFrom:
 		from email.mime.text import MIMEText
+		import yade.config
 		msg=MIMEText(reportText)
-		msg['Subject']="Automated crash report for "+yade.runtime.executable+": "+",".join([r[0] for r in reports])
+		msg['Subject']="Automated crash report for Yade "+yade.config.revision+": "+",".join([r[0] for r in reports])
 		msg['From']=mailFrom
 		msg['To']=mailTo
 		msg['Reply-To']='yade-dev@xxxxxxxxxxxxxxxxxxx'