← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2257: 1. Add Peri3dController, done by Jan and me during a rainy day

 

------------------------------------------------------------
revno: 2257
committer: Václav Šmilauer <eudoxos@xxxxxxxx>
branch nick: trunk
timestamp: Wed 2010-05-26 19:49:15 +0200
message:
  1. Add Peri3dController, done by Jan and me during a rainy day
  2. Implement memoizeDb for pack.randomPeriPack
  3. add polar decomposition to Matrix3 in python
  4. make the 3d view update even if there are no bodies
added:
  scripts/test/peri3d.py
modified:
  doc/references.bib
  gui/qt3/SimulationController.cpp
  pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.cpp
  pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.hpp
  pkg/dem/meta/ConcretePM.cpp
  py/mathWrap/miniEigen.cpp
  py/pack/pack.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 'doc/references.bib'
--- doc/references.bib	2010-05-07 11:15:06 +0000
+++ doc/references.bib	2010-05-26 17:49:15 +0000
@@ -105,4 +105,17 @@
   publisher = {American Physical Society}
 }
 
+@inproceedings{Kuhl2001,
+  title = {Microplane modelling and particle modelling of cohesive-frictional materials},
+  author = {E. Kuhl and G. A. D'Addetta and M. Leukart and E. Ramm},
+  booktitle = {Continuous and Discontinuous Modelling of Cohesive-Frictional Materials},
+  publisher = {Springer Berlin / Heidelberg},
+  issn = {1616-6361},
+  isbn = {978-3-540-41525-1},
+  volume = {568},
+  series = {Lecture Notes in Physics},
+  year = {2001},
+  pages = {31--46},
+  doi = {10.1007/3-540-44424-6_3},
+}
 

=== modified file 'gui/qt3/SimulationController.cpp'
--- gui/qt3/SimulationController.cpp	2010-05-24 21:14:57 +0000
+++ gui/qt3/SimulationController.cpp	2010-05-26 17:49:15 +0000
@@ -430,7 +430,7 @@
 	if(sbRefreshTime->value()!=refreshTime) sbRefreshTime->setValue(refreshTime);
 
 	/* enable/disable controls here, dynamically */
-	hasSimulation=(scene ? scene->bodies->size()>0 : false );
+	hasSimulation=(scene); // ? scene->bodies->size()>0 : false );
 	bool	isRunning=Omega::instance().isRunning() || syncRunning,
 		hasTimeStepper=scene->timeStepperPresent(),
 		usesTimeStepper=scene->timeStepperActive(),

=== modified file 'pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.cpp'
--- pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.cpp	2010-05-24 19:56:22 +0000
+++ pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.cpp	2010-05-26 17:49:15 +0000
@@ -10,7 +10,7 @@
 
 using namespace std;
 
-YADE_PLUGIN((PeriIsoCompressor)(PeriTriaxController))
+YADE_PLUGIN((PeriIsoCompressor)(PeriTriaxController)(Peri3dController))
 
 
 CREATE_LOGGER(PeriIsoCompressor);
@@ -245,3 +245,128 @@
 		}
 	}
 }
+
+#ifndef YADE_WM3
+
+CREATE_LOGGER(Peri3dController);
+void Peri3dController::update(){
+	/* strain
+		polar decomposition of transformation:
+			compute strain tensor from the non-rotational part of trsf, then rotate it back to global coords
+	*/
+	Matrix3r rot,nonrot; //nonrot=skew+normal deformation
+	const Matrix3r& trsf=scene->cell->trsf;
+	Eigen::SVD<Matrix3r>(trsf).computeUnitaryPositive(&rot,&nonrot);
+	// FIXME: should be rot*(log(nonrot)), but matrix logarithm is not yet implemented in eigen
+	strain=rot*(nonrot-Matrix3r::Identity());
+	LOG_TRACE("Updated strain value\n"<<strain);
+	/* stress and stiffness
+	*/
+	K=Matrix6r::Zero();
+	stress=Matrix3r::Zero();
+	const Real volume=scene->cell->trsf.determinant()*scene->cell->refSize[0]*scene->cell->refSize[1]*scene->cell->refSize[2];
+	FOREACH(const shared_ptr<Interaction>& I, *scene->interactions){
+		if(!I->isReal()) continue;
+		Dem3DofGeom* geom=YADE_CAST<Dem3DofGeom*>(I->interactionGeometry.get());
+		NormShearPhys* phys=YADE_CAST<NormShearPhys*>(I->interactionPhysics.get());
+		// not clear whether this should be the reference or the current distance
+		// current: gives consistent results for same configuration with different initial state
+		// reference: does not change stress tensor when the same forces exist on interactions with changing length
+		//const Real& d0=geom->refLength;
+		const Real d0=(geom->se31.position-geom->se32.position).norm();
+		const Vector3r& n=geom->normal;
+		const Vector3r& fT=phys->shearForce;
+		const Real fN=phys->normalForce.dot(n);
+		for(int i=0; i<3; i++) for(int j=0;j<3; j++){
+			stress(i,j)+=d0*(fN*n[i]*n[j]+.5*(fT[i]*n[j]+fT[j]*n[i]));
+		}
+		const Real& kN=phys->kn; const Real& kT=phys->ks;
+		// mapping between 6x6 matrix indices and tensor indices (Voigt notation)
+		const int map[6][6][4]={
+			{{0,0,0,0},{0,0,1,1},{0,0,2,2},{0,0,1,2},{0,0,2,0},{0,0,0,1}},
+			{{8,8,8,8},{1,1,1,1},{1,1,2,2},{1,1,1,2},{1,1,2,0},{1,1,0,1}},
+			{{8,8,8,8},{8,8,8,8},{2,2,2,2},{2,2,1,2},{2,2,2,0},{2,2,0,1}},
+			{{8,8,8,8},{8,8,8,8},{8,8,8,8},{1,2,1,2},{1,2,2,0},{1,2,0,1}},
+			{{8,8,8,8},{8,8,8,8},{8,8,8,8},{8,8,8,8},{2,0,2,0},{2,0,0,1}},
+			{{8,8,8,8},{8,8,8,8},{8,8,8,8},{8,8,8,8},{8,8,8,8},{0,1,0,1}}};
+		const int kron[3][3]={{1,0,0},{0,1,0},{0,0,1}}; // Kronecker delta
+		for(int p=0; p<6; p++) for(int q=p;q<6;q++){
+			int i=map[p][q][0], j=map[p][q][1], k=map[p][q][2], l=map[p][q][3];
+			K(p,q)+=d0*d0*(kN*n[i]*n[j]*n[k]*n[l]+kT*(.25*(n[j]*n[k]*kron[i][l]+n[j]*n[l]*kron[i][k]+n[i]*n[k]*kron[j][l]+n[i]*n[l]*kron[j][k])-n[i]*n[j]*n[k]*n[l]));
+		}
+	}
+	stress/=volume;
+	K/=volume;
+	for(int p=0; p<6; p++)for(int q=p+1;q<6;q++) K(q,p)=K(p,q);
+	LOG_TRACE("Updated stress\n"<<stress);
+	LOG_TRACE("Updated stiffness tensor\n"<<K);
+}
+
+void Peri3dController::action(){
+	// TODO: only call sometimes
+	update();
+	typedef Eigen::Matrix<Real,Eigen::Dynamic,Eigen::Dynamic> MatrixXr;
+	typedef Eigen::Matrix<Real,Eigen::Dynamic,1> VectorXr;
+	/* 
+	sigma = K * eps
+
+	decompose the stiffness matrix depending on what is prescribed
+
+	| sigma_u | = | Kuu Kup | * | eps_u |
+	| sigma_p |   | Kpu Kpp |   | eps_p |
+
+	then
+
+	eps_u=(Kuu^-1)*(sigma_u-Kup eps_p)
+
+	(we replace all eps and sigma by their rates in the computation)
+	*/
+	int numP=0,numU=0; // number of prescribed and unprescribed strain components
+	for(int i=0;i<6;i++) if(stressMask&(1<<i))numU++;
+	numP=6-numU;
+	// sub-matrices of the stiffness matrix
+	MatrixXr Kuu(numU,numU), Kup(numU,numP);
+	// epsP, epsU, sigU are _rates_
+	VectorXr epsP(numP), epsU(numU), sigU(numU);
+	// conversion from Voigt indices to 3x3 tensor indices (upper-triangle part)
+	const int mapI[6]={0,1,2,1,2,0};
+	const int mapJ[6]={0,1,2,2,0,1};
+	int jU=0,jP=0;
+	for(int j=0; j<6; j++){
+		// prescribed stress, i.e. un-prescribed strain
+		if(stressMask&(1<<j)){
+			sigU[jU]=goal(mapI[j],mapJ[j]);
+			int iU=0;
+			for(int i=0;i<6;i++){ if((stressMask&(1<<i))) Kuu(iU++,jU)=K(i,j);}
+			jU++;
+		} else {
+			epsP[jP]=(j<3?1.:2.)*goal(mapI[j],mapJ[j]); /* multiply tensor shear by 2, see http://en.wikiversity.org/wiki/Introduction_to_Elasticity/Constitutive_relations */
+			int iP=0;
+			for(int i=0;i<6;i++){ if((stressMask&(1<<i))) Kup(iP++,jP)=K(i,j);}
+			jP++;
+		}
+	}
+	assert(jU==numU); assert(jP==numP);
+	LOG_TRACE("Kuu=\n"<<Kuu<<"\nKup=\n"<<Kup<<"\nepsP=\n"<<epsP<<"\nsigU=\n"<<sigU);
+	// if Kuu is (nearly) singular, the goal strain is inf and the corresponding velGrad component then NaN
+	// in such case, sanitize it with some random value (irrelevant which one)
+	// FIXME: find a better way for this than determinant (expensive for larger matrices)
+	if(Kuu.rows()*Kuu.cols()>0 && Kuu.determinant()<1e-20){ Kuu+=MatrixXr(Kuu.cols(),Kuu.rows()).setIdentity(); LOG_TRACE("Kuu after sanitization\n"<<Kuu); }
+	epsU=Kuu.inverse()*(sigU-Kup*epsP);
+	// assemble strain tensor (as matrix), from prescribed values epsP and computed values epsU
+	jU=0; jP=0;
+	Matrix3r eps;
+	for(int j=0; j<6; j++){
+		if(stressMask&(1<<j)) eps(mapI[j],mapJ[j])=epsU[jU++];
+		else eps(mapI[j],mapJ[j])=(j<3?1.:.5)*epsP[jP++]; /* multiply shear components back by 1/2 when converting from Voigt vector back to tensor */
+	}
+	eps(2,1)=eps(1,2); eps(0,2)=eps(2,0); eps(1,0)=eps(0,1);
+	Matrix3r& velGrad=scene->cell->velGrad;
+	// rate of (goal strain - current strain)
+	velGrad=(eps-strain)/scene->dt;
+	Real mx=max(abs(velGrad.minCoeff()),abs(velGrad.maxCoeff()));
+	if(mx>abs(maxStrainRate)) velGrad*=abs(maxStrainRate)/mx;
+	LOG_TRACE("epsU=\n"<<epsU<<"\neps=\n"<<"\nabs(maxCoeff)="<<mx<<"\nvelGrad=\n"<<velGrad);
+	// TODO: check unbalanced force and run some hook when the goal state is achieved
+}
+#endif

=== modified file 'pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.hpp'
--- pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.hpp	2010-04-25 13:18:11 +0000
+++ pkg/dem/Engine/GlobalEngine/PeriIsoCompressor.hpp	2010-05-26 17:49:15 +0000
@@ -70,3 +70,31 @@
 	DECLARE_LOGGER;
 };
 REGISTER_SERIALIZABLE(PeriTriaxController);
+#ifndef YADE_WM3
+
+#include<Eigen/SVD>
+class Peri3dController: public BoundaryController{
+	public:
+		typedef Eigen::Matrix<Real,6,6> Matrix6r;
+		typedef Eigen::Matrix<Real,6,1> Vector6r;
+
+		// stiffness matrix, updated automatically
+		Matrix6r K;
+
+		virtual void action();
+		void update();
+	YADE_CLASS_BASE_DOC_ATTRS(Peri3dController,BoundaryController,"Experimental controller of full strain/stress tensors on periodic cell. Stress and strain tensors are computed using formulas derived in [Kuhl2001]_, in particular equations (33) and (35).",
+		((Matrix3r,strain,Matrix3r::Zero(),"Current deformation tensor |yupdate|"))
+		((Matrix3r,stress,Matrix3r::Zero(),"Current stress tensor |yupdate|"))
+		((Matrix3r,goal,Matrix3r::Zero(),"Goal state."))
+		((int,stressMask,((void)"all strains",0),"mask determining whether components of :yref:`goal<Peri3dController.goal>` are strain (0) or stress (1). The order is 00,11,22,12,02,01 from the least significant bit. (e.g. 0b000011 is stress 00 and stress 11)."))
+		((Real,maxStrainRate,1,"Maximum absolute value of strain rate (both normal and shear components of :yref:`Cell.velGrad`)"))
+		// not yet used
+		//((Real,currUnbalanced,NaN,"current unbalanced force |yupdate|"))
+		//((Real,maxUnbalanced,1e-4,"Maximum unbalanced force"))
+	);
+	DECLARE_LOGGER;
+};
+REGISTER_SERIALIZABLE(Peri3dController);
+
+#endif

=== modified file 'pkg/dem/meta/ConcretePM.cpp'
--- pkg/dem/meta/ConcretePM.cpp	2010-05-13 20:19:38 +0000
+++ pkg/dem/meta/ConcretePM.cpp	2010-05-26 17:49:15 +0000
@@ -133,12 +133,7 @@
 
 
 #ifdef YADE_CPM_FULL_MODEL_AVAILABLE
-	// assure interoperability with older version
-	#define SquaredLength squaredNorm
-	#define Length norm
-		#include"../../../../brefcom-mm.hh"
-	#undef SquaredLength
-	#undef Length
+	#include"../../../../brefcom-mm.hh"
 #endif
 
 

=== modified file 'py/mathWrap/miniEigen.cpp'
--- py/mathWrap/miniEigen.cpp	2010-05-11 10:25:49 +0000
+++ py/mathWrap/miniEigen.cpp	2010-05-26 17:49:15 +0000
@@ -112,6 +112,10 @@
 static Vector3i Vector3i_cross(const Vector3i& self, const Vector3i& v){ return self.cross(v); }
 static bool Quaternionr__eq__(const Quaternionr& q1, const Quaternionr& q2){ return q1==q2; }
 static bool Quaternionr__neq__(const Quaternionr& q1, const Quaternionr& q2){ return q1!=q2; }
+#ifndef YADE_WM3
+	#include<Eigen/SVD>
+	static bp::tuple Matrix3r_polarDecomposition(const Matrix3r& self){ Matrix3r unitary,positive; Eigen::SVD<Matrix3r>(self).computeUnitaryPositive(&unitary,&positive); return bp::make_tuple(unitary,positive); }
+#endif
 
 #define WM3_COMPAT
 
@@ -230,6 +234,10 @@
 		.def("determinant",&Matrix3r::determinant)
 		.def("inverse",&Matrix3r_inverse)
 		.def("transpose",&Matrix3r_transpose)
+		#ifndef YADE_WM3
+			.def("polarDecomposition",&Matrix3r_polarDecomposition)
+		#endif
+
 		//
 		.def("__neg__",&Matrix3r__neg__)
 		.def("__add__",&Matrix3r__add__Matrix3r).def("__iadd__",&Matrix3r__iadd__Matrix3r)

=== modified file 'py/pack/pack.py'
--- py/pack/pack.py	2010-05-14 10:01:37 +0000
+++ py/pack/pack.py	2010-05-26 17:49:15 +0000
@@ -221,6 +221,71 @@
 		if predicate(s[0],s[1]): ret+=[utils.sphere(s[0],radius=s[1],**kw)]
 	return ret
 
+def _memoizePacking(memoizeDb,sp,radius,rRelFuzz,wantPeri,fullDim):
+	if not memoizeDb: return
+	import cPickle,sqlite3,time,os
+	if os.path.exists(memoizeDb):
+		conn=sqlite3.connect(memoizeDb)
+	else:
+		conn=sqlite3.connect(memoizeDb)
+		c=conn.cursor()
+		c.execute('create table packings (radius real, rRelFuzz real, dimx real, dimy real, dimz real, N integer, timestamp real, periodic integer, pack blob)')
+	c=conn.cursor()
+	packBlob=buffer(cPickle.dumps(sp.toList_pointsAsTuples(),cPickle.HIGHEST_PROTOCOL))
+	packDim=sp.cellSize if wantPeri else fullDim
+	c.execute('insert into packings values (?,?,?,?,?,?,?,?,?)',(radius,rRelFuzz,packDim[0],packDim[1],packDim[2],len(sp),time.time(),wantPeri,packBlob,))
+	c.close()
+	conn.commit()
+	print "Packing saved to the database",memoizeDb
+
+def _getMemoizedPacking(memoizeDb,radius,rRelFuzz,x1,y1,z1,fullDim,wantPeri,fillPeriodic,spheresInCell,memoDbg=False):
+	"""Return suitable SpherePack read from *memoizeDb* if found, None otherwise.
+		
+		:param fillPeriodic: whether to fill fullDim by repeating periodic packing
+		:param wantPeri: only consider periodic packings
+	"""
+	import os,os.path,sqlite3,time,cPickle,sys
+	if memoDbg:
+		def memoDbgMsg(s): print s
+	else:
+		def memoDbgMsg(s): pass
+	if not memoizeDb or not os.path.exists(memoizeDb):
+		if memoizeDb: memoDbgMsg("Database %s does not exist."%memoizeDb)
+		return None
+	# find suitable packing and return it directly
+	conn=sqlite3.connect(memoizeDb); c=conn.cursor();
+	try:
+		c.execute('select radius,rRelFuzz,dimx,dimy,dimz,N,timestamp,periodic from packings order by N')
+	except sqlite3.OperationalError:
+		raise RuntimeError("ERROR: database `"+memoizeDb+"' not compatible with randomDensePack (corrupt, deprecated format or not a db created by randomDensePack)")
+	for row in c:
+		R,rDev,X,Y,Z,NN,timestamp,isPeri=row[0:8]; scale=radius/R
+		rDev*=scale; X*=scale; Y*=scale; Z*=scale
+		memoDbgMsg("Considering packing (radius=%g±%g,N=%g,dim=%g×%g×%g,%s,scale=%g), created %s"%(R,.5*rDev,NN,X,Y,Z,"periodic" if isPeri else "non-periodic",scale,time.asctime(time.gmtime(timestamp))))
+		if not isPeri and wantPeri: memoDbgMsg("REJECT: is not periodic, which is requested."); continue
+		if (rRelFuzz==0 and rDev!=0) or (rRelFuzz!=0 and rDev==0) or (rRelFuzz!=0 and abs((rDev-rRelFuzz)/rRelFuzz)>1e-2): memoDbgMsg("REJECT: radius fuzz differs too much (%g, %g desired)"%(rDev,rRelFuzz)); continue # radius fuzz differs too much
+		if isPeri and wantPeri:
+			if spheresInCell>NN: memoDbgMsg("REJECT: Number of spheres in the packing too small"); continue
+			if abs((y1/x1)/(Y/X)-1)>0.3 or abs((z1/x1)/(Z/X)-1)>0.3: memoDbgMsg("REJECT: proportions (y/x=%g, z/x=%g) differ too much from what is desired (%g, %g)."%(Y/X,Z/X,y1/x1,z1/x1)); continue
+		else:
+			if (X<fullDim[0] or Y<fullDim[1] or Z<fullDim[2]): memoDbgMsg("REJECT: not large enough"); continue # not large enough
+		memoDbgMsg("ACCEPTED");
+		print "Found suitable packing in %s (radius=%g±%g,N=%g,dim=%g×%g×%g,%s,scale=%g), created %s"%(memoizeDb,R,rDev,NN,X,Y,Z,"periodic" if isPeri else "non-periodic",scale,time.asctime(time.gmtime(timestamp)))
+		c.execute('select pack from packings where timestamp=?',(timestamp,))
+		sp=SpherePack(cPickle.loads(str(c.fetchone()[0])))
+		if isPeri and wantPeri:
+			sp.cellSize=(X,Y,Z);
+			if fillPeriodic: sp.cellFill(Vector3(fullDim[0],fullDim[1],fullDim[2]));
+		sp.scale(scale);
+		#sp.cellSize=(0,0,0) # resetting cellSize avoids warning when rotating
+		return sp
+		#if orientation: sp.rotate(*orientation.toAxisAngle())
+		#return filterSpherePack(predicate,sp,material=material)
+	#print "No suitable packing in database found, running",'PERIODIC compression' if wantPeri else 'triaxial'
+	#sys.stdout.flush()
+
+
+
 def randomDensePack(predicate,radius,material=-1,dim=None,cropLayers=0,rRelFuzz=0.,spheresInCell=0,memoizeDb=None,useOBB=True,memoDbg=False,color=None):
 	"""Generator of random dense packing with given geometry properties, using TriaxialTest (aperiodic)
 	or PeriIsoCompressor (periodic). The priodicity depens on whether	the spheresInCell parameter is given.
@@ -274,37 +339,13 @@
 		maxR=radius*(1+rRelFuzz)
 		x1=max(x1,8*maxR); y1=max(y1,8*maxR); z1=max(z1,8*maxR); vol1=x1*y1*z1
 		N100*=vol1/vol0 # volume might have been increased, increase number of spheres to keep porosity the same
-	if(memoizeDb and os.path.exists(memoizeDb)):
-		if memoDbg:
-			def memoDbgMsg(s): print s
-		else:
-			def memoDbgMsg(s): pass
-		# find suitable packing and return it directly
-		conn=sqlite3.connect(memoizeDb); c=conn.cursor();
-		try:
-			c.execute('select radius,rRelFuzz,dimx,dimy,dimz,N,timestamp,periodic from packings order by N')
-		except sqlite3.OperationalError:
-			raise RuntimeError("ERROR: database `"+memoizeDb+"' not compatible with randomDensePack (corrupt, deprecated format or not a db created by randomDensePack)")
-		for row in c:
-			R,rDev,X,Y,Z,NN,timestamp,isPeri=row[0:8]; scale=radius/R
-			rDev*=scale; X*=scale; Y*=scale; Z*=scale
-			memoDbgMsg("Considering packing (radius=%g±%g,N=%g,dim=%g×%g×%g,%s,scale=%g), created %s"%(R,.5*rDev,NN,X,Y,Z,"periodic" if isPeri else "non-periodic",scale,time.asctime(time.gmtime(timestamp))))
-			if (rRelFuzz==0 and rDev!=0) or (rRelFuzz==0 and rDev!=0) or (rRelFuzz!=0 and abs((rDev-rRelFuzz)/rRelFuzz)>1e-2): memoDbgMsg("REJECT: radius fuzz differs too much (%g, %g desired)"%(rDev,rRelFuzz)); continue # radius fuzz differs too much
-			if isPeri and wantPeri:
-				if spheresInCell>NN: memoDbgMsg("REJECT: Number of spheres in the packing too small"); continue
-				if abs((y1/x1)/(Y/X)-1)>0.3 or abs((z1/x1)/(Z/X)-1)>0.3: memoDbgMsg("REJECT: proportions (y/x=%g, z/x=%g) differ too much from what is desired (%g, %g)."%(Y/X,Z/X,y1/x1,z1/x1)); continue
-			else:
-				if (X<fullDim[0] or Y<fullDim[1] or Z<fullDim[2]): memoDbgMsg("REJECT: not large enough"); continue # not large enough
-			memoDbgMsg("ACCEPTED");
-			print "Found suitable packing in %s (radius=%g±%g,N=%g,dim=%g×%g×%g,%s,scale=%g), created %s"%(memoizeDb,R,rDev,NN,X,Y,Z,"periodic" if isPeri else "non-periodic",scale,time.asctime(time.gmtime(timestamp)))
-			c.execute('select pack from packings where timestamp=?',(timestamp,))
-			sp=SpherePack(cPickle.loads(str(c.fetchone()[0])))
-			if isPeri and wantPeri:
-				sp.cellSize=(X,Y,Z); sp.cellFill(Vector3(fullDim[0],fullDim[1],fullDim[2])); sp.cellSize=(0,0,0) # resetting cellSize avoids warning when rotating
-			sp.scale(scale);
-			if orientation: sp.rotate(*orientation.toAxisAngle())
+		sp=_getMemoizedPacking(memoizeDb,radius,rRelFuzz,x1,y1,z1,fullDim,wantPeri,fillPeriodic=True,spheresInCell=spheresInCell,memoDbg=False)
+		if sp:
+			if orientation:
+				sp.cellSize=(0,0,0) # resetting cellSize avoids warning when rotating
+				sp.rotate(*orientation.toAxisAngle())
 			return filterSpherePack(predicate,sp,material=material)
-		print "No suitable packing in database found, running",'PERIODIC compression' if wantPeri else 'triaxial'
+		else: print "No suitable packing in database found, running",'PERIODIC compression' if wantPeri else 'triaxial'
 		sys.stdout.flush()
 	O.switchScene(); O.resetThisScene() ### !!
 	if wantPeri:
@@ -337,27 +378,14 @@
 		O.run(); O.wait()
 		sp=SpherePack(); sp.fromSimulation()
 	O.switchScene() ### !!
-	if(memoizeDb):
-		if os.path.exists(memoizeDb):
-			conn=sqlite3.connect(memoizeDb)
-		else:
-			conn=sqlite3.connect(memoizeDb)
-			c=conn.cursor()
-			c.execute('create table packings (radius real, rRelFuzz real, dimx real, dimy real, dimz real, N integer, timestamp real, periodic integer, pack blob)')
-		c=conn.cursor()
-		packBlob=buffer(cPickle.dumps(sp.toList_pointsAsTuples(),cPickle.HIGHEST_PROTOCOL))
-		packDim=sp.cellSize if wantPeri else fullDim
-		c.execute('insert into packings values (?,?,?,?,?,?,?,?,?)',(radius,rRelFuzz,packDim[0],packDim[1],packDim[2],len(sp),time.time(),wantPeri,packBlob,))
-		c.close()
-		conn.commit()
-		print "Packing saved to the database",memoizeDb
+	_memoizePacking(memoizeDb,sp,radius,rRelFuzz,wantPeri,fullDim)
 	if wantPeri: sp.cellFill(Vector3(fullDim[0],fullDim[1],fullDim[2]))
 	if orientation:
 		sp.cellSize=(0,0,0); # reset periodicity to avoid warning when rotating periodic packing
 		sp.rotate(*orientation.toAxisAngle())
 	return filterSpherePack(predicate,sp,material=material,color=color)
 
-def randomPeriPack(radius,rRelFuzz,initSize):
+def randomPeriPack(radius,rRelFuzz,initSize,memoizeDb=None):
 	"""Generate periodic dense packing.	EXPERIMENTAL, you at your own risk.
 
 	A cell of initSize is stuffed with as many spheres as possible (ignore the warning from SpherePack::makeCloud about
@@ -374,6 +402,8 @@
 
 	"""
 	from math import pi
+	sp=_getMemoizedPacking(memoizeDb,radius,rRelFuzz,initSize[0],initSize[1],initSize[2],fullDim=Vector3(0,0,0),wantPeri=True,fillPeriodic=False,spheresInCell=-1,memoDbg=True)
+	if sp: return sp
 	O.switchScene(); O.resetThisScene()
 	sp=SpherePack()
 	O.periodic=True
@@ -387,6 +417,7 @@
 	O.run(); O.wait()
 	ret=SpherePack()
 	ret.fromSimulation()
+	_memoizePacking(memoizeDb,sp,radius,rRelFuzz,wantPeri=True,fullDim=Vector3(0,0,0)) # fullDim unused
 	O.switchScene()
 	return ret
 

=== added file 'scripts/test/peri3d.py'
--- scripts/test/peri3d.py	1970-01-01 00:00:00 +0000
+++ scripts/test/peri3d.py	2010-05-26 17:49:15 +0000
@@ -0,0 +1,29 @@
+from yade import pack,log
+sp=pack.randomPeriPack(.5,.5,Vector3(10,10,10),memoizeDb='/tmp/packDb.sqlite')
+O.periodic=True
+O.cell.refSize=sp.cellSize
+O.bodies.append([utils.sphere(c,r) for c,r in sp])
+O.dt=utils.PWaveTimeStep()
+log.setLevel('Peri3dController',log.TRACE)
+O.engines=[
+	ForceResetter(),
+	BoundDispatcher([Bo1_Sphere_Aabb()]),
+	InsertionSortCollider(),
+	InteractionDispatchers(
+		[Ig2_Sphere_Sphere_Dem3DofGeom()],
+		[Ip2_FrictMat_FrictMat_FrictPhys()],
+		[Law2_Dem3DofGeom_FrictPhys_Basic()]
+	),
+	NewtonIntegrator(homotheticCellResize=1,damping=.4),
+	#Peri3dController(goal=Matrix3(-.1,0,0, 0,-.1,0, 0,0,-.1),stressMask=0,maxStrainRate=1e-1,label='ctrl'),
+	#Peri3dController(goal=Matrix3(0,.2,0, 0,0,0, 0,0,0),stressMask=0,maxStrainRate=1,label='ctrl'),
+	Peri3dController(goal=Matrix3(-1e3,.1,0, 0,.1,0, 0,0,0),stressMask=0b0001,maxStrainRate=1,label='ctrl'),
+	PeriodicPythonRunner(iterPeriod=10,command='addData()')
+]
+O.step()
+from yade import plot
+def addData():
+	plot.addData(sxx=ctrl.stress[0,0],syy=ctrl.stress[1,1],szz=ctrl.stress[2,2],exx=ctrl.strain[0,0],eyy=ctrl.strain[1,1],ezz=ctrl.strain[2,2],t=O.time)
+
+plot.plots={'exx':('sxx'),}
+