~yade-dev/yade/0.80

« back to all changes in this revision

Viewing changes to pkg/dem/SpherePack.cpp

  • Committer: Anton Gladky
  • Date: 2012-05-02 21:50:42 UTC
  • Revision ID: gladky.anton@gmail.com-20120502215042-v1fa9r65usqe7kfk
0.80.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// © 2009 Václav Šmilauer <eudoxos@arcig.cz>
 
2
 
 
3
#include<yade/pkg/dem/SpherePack.hpp>
 
4
 
 
5
#include<yade/core/Omega.hpp>
 
6
#include<yade/core/Scene.hpp>
 
7
#include<yade/pkg/common/Sphere.hpp>
 
8
#include<yade/pkg/dem/Shop.hpp>
 
9
 
 
10
#include <boost/random/linear_congruential.hpp>
 
11
#include <boost/random/uniform_real.hpp>
 
12
#include <boost/random/variate_generator.hpp>
 
13
 
 
14
#include<yade/core/Timing.hpp>
 
15
 
 
16
// not a serializable in the sense of YADE_PLUGIN
 
17
 
 
18
CREATE_LOGGER(SpherePack);
 
19
 
 
20
using namespace std;
 
21
using namespace boost;
 
22
namespace py=boost::python;
 
23
 
 
24
 
 
25
void SpherePack::fromList(const py::list& l){
 
26
        pack.clear();
 
27
        size_t len=py::len(l);
 
28
        for(size_t i=0; i<len; i++){
 
29
                const py::tuple& t=py::extract<py::tuple>(l[i]);
 
30
                py::extract<Vector3r> vec(t[0]);
 
31
                if(vec.check()) { pack.push_back(Sph(vec(),py::extract<double>(t[1]),(py::len(t)>2?py::extract<int>(t[2]):-1))); continue; }
 
32
                PyErr_SetString(PyExc_TypeError, "List elements must be (Vector3, float) or (Vector3, float, int)!");
 
33
                py::throw_error_already_set();
 
34
        }
 
35
};
 
36
 
 
37
void SpherePack::fromLists(const vector<Vector3r>& centers, const vector<Real>& radii){
 
38
        pack.clear();
 
39
        if(centers.size()!=radii.size()) throw std::invalid_argument(("The same number of centers and radii must be given (is "+lexical_cast<string>(centers.size())+", "+lexical_cast<string>(radii.size())+")").c_str());
 
40
        size_t l=centers.size();
 
41
        for(size_t i=0; i<l; i++){
 
42
                add(centers[i],radii[i]);
 
43
        }
 
44
        cellSize=Vector3r::Zero();
 
45
}
 
46
 
 
47
py::list SpherePack::toList() const {
 
48
        py::list ret;
 
49
        FOREACH(const Sph& s, pack) ret.append(s.asTuple());
 
50
        return ret;
 
51
};
 
52
 
 
53
void SpherePack::fromFile(string file) {
 
54
        typedef pair<Vector3r,Real> pairVector3rReal;
 
55
        vector<pairVector3rReal> ss;
 
56
        Vector3r mn,mx;
 
57
        ss=Shop::loadSpheresFromFile(file,mn,mx,&cellSize);
 
58
        pack.clear();
 
59
        FOREACH(const pairVector3rReal& s, ss) pack.push_back(Sph(s.first,s.second));
 
60
}
 
61
 
 
62
void SpherePack::toFile(const string fname) const {
 
63
        ofstream f(fname.c_str());
 
64
        if(!f.good()) throw runtime_error("Unable to open file `"+fname+"'");
 
65
        if(cellSize!=Vector3r::Zero()){ f<<"##PERIODIC:: "<<cellSize[0]<<" "<<cellSize[1]<<" "<<cellSize[2]<<endl; }
 
66
        FOREACH(const Sph& s, pack){
 
67
                if(s.clumpId>=0) throw std::invalid_argument("SpherePack with clumps cannot be (currently) exported to a text file.");
 
68
                f<<s.c[0]<<" "<<s.c[1]<<" "<<s.c[2]<<" "<<s.r<<endl;
 
69
        }
 
70
        f.close();
 
71
};
 
72
 
 
73
void SpherePack::fromSimulation() {
 
74
        pack.clear();
 
75
        Scene* scene=Omega::instance().getScene().get();
 
76
        FOREACH(const shared_ptr<Body>& b, *scene->bodies){
 
77
                if(!b) continue;
 
78
                shared_ptr<Sphere> intSph=dynamic_pointer_cast<Sphere>(b->shape);
 
79
                if(!intSph) continue;
 
80
                pack.push_back(Sph(b->state->pos,intSph->radius,(b->isClumpMember()?b->clumpId:-1)));
 
81
        }
 
82
        if(scene->isPeriodic) {
 
83
                cellSize=scene->cell->getSize();
 
84
                isPeriodic = true;
 
85
        }
 
86
}
 
87
 
 
88
long SpherePack::makeCloud(Vector3r mn, Vector3r mx, Real rMean, Real rRelFuzz, int num, bool periodic, Real porosity, const vector<Real>& psdSizes, const vector<Real>& psdCumm, bool distributeMass, int seed, Matrix3r hSize){
 
89
        isPeriodic = periodic;
 
90
        static boost::minstd_rand randGen(seed!=0?seed:(int)TimingInfo::getNow(/* get the number even if timing is disabled globally */ true));
 
91
        static boost::variate_generator<boost::minstd_rand&, boost::uniform_real<Real> > rnd(randGen, boost::uniform_real<Real>(0,1));
 
92
        vector<Real> psdRadii; // holds plain radii (rather than diameters), scaled down in some situations to get the target number
 
93
        vector<Real> psdCumm2; // psdCumm but dimensionally transformed to match mass distribution      
 
94
        Vector3r size;
 
95
        bool hSizeFound =(hSize!=Matrix3r::Zero());//is hSize passed to the function?
 
96
        size=mx-mn;
 
97
        if (!hSizeFound) {hSize=size.asDiagonal();}
 
98
        if (hSizeFound && !periodic) LOG_WARN("hSize can be defined only for periodic cells.");
 
99
        Real volume=hSize.determinant();
 
100
        Matrix3r invHsize =hSize.inverse();
 
101
        Real area=abs(size[0]*size[2]+size[0]*size[1]+size[1]*size[2]);//2 terms will be null if one coordinate is 0, the other is the area
 
102
        if (!volume) {
 
103
                if (hSizeFound || !periodic) throw invalid_argument("The box as null volume, this is not supported. One exception is for flat box defined by min-max in periodic conditions, if hSize argument defines a non-null volume (or if the hSize argument is left undefined).");
 
104
                else LOG_WARN("The volume of the min-max box is null, we will assume that the packing is 2D. If it is not what you want then you defined wrong input values; check that min and max corners are defined correctly.");}
 
105
        int mode=-1; bool err=false;
 
106
        // determine the way we generate radii
 
107
        if(porosity<=0) {LOG_WARN("porosity must be >0, changing it for you. It will be ineffective if rMean>0."); porosity=0.5;}
 
108
        //If rMean is not defined, then in will be defined in RDIST_NUM
 
109
        if(rMean>0) mode=RDIST_RMEAN;
 
110
        else if(num>0 && psdSizes.size()==0) {
 
111
                mode=RDIST_NUM;
 
112
                // the term (1+rRelFuzz²) comes from the mean volume for uniform distribution : Vmean = 4/3*pi*Rmean*(1+rRelFuzz²) 
 
113
                if (volume) rMean=pow(volume*(1-porosity)/(Mathr::PI*(4/3.)*(1+rRelFuzz*rRelFuzz)*num),1/3.);
 
114
                else {//The volume is null, we will generate a 2D packing with the following rMean
 
115
                        if (!area) throw invalid_argument("The box defined has null volume AND null surface. Define at least maxCorner of the box, or hSize if periodic.");
 
116
                        rMean=pow(area*(1-porosity)/(Mathr::PI*(1+rRelFuzz*rRelFuzz)*num),0.5);}
 
117
        }
 
118
        // transform sizes and cummulated fractions values in something convenient for the generation process
 
119
        if(psdSizes.size()>0){
 
120
                err=(mode>=0); mode=RDIST_PSD;
 
121
                if(psdSizes.size()!=psdCumm.size()) throw invalid_argument(("SpherePack.makeCloud: psdSizes and psdCumm must have same dimensions ("+lexical_cast<string>(psdSizes.size())+"!="+lexical_cast<string>(psdCumm.size())).c_str());
 
122
                if(psdSizes.size()<=1) throw invalid_argument("SpherePack.makeCloud: psdSizes must have at least 2 items");
 
123
                if((*psdCumm.begin())!=0. && (*psdCumm.rbegin())!=1.) throw invalid_argument("SpherePack.makeCloud: first and last items of psdCumm *must* be exactly 0 and 1.");
 
124
                psdRadii.reserve(psdSizes.size());
 
125
                for(size_t i=0; i<psdSizes.size(); i++) {
 
126
                        psdRadii.push_back(/* radius, not diameter */ .5*psdSizes[i]);
 
127
                        if(distributeMass) {
 
128
                                //psdCumm2 is first obtained by integrating the number of particles over the volumic PSD (dN/dSize = totV*(dPassing/dSize)*1/(4/3πr³)). The total cumulated number will be the number of spheres in volume*(1-porosity), it is used to decide if the PSD will be scaled down. psdCumm2 is normalized below in order to fit in [0,1]. (Bruno C.)
 
129
                                if (i==0) psdCumm2.push_back(0);
 
130
                                else psdCumm2.push_back(psdCumm2[i-1] + 3.0* (volume?volume:(area*psdSizes[psdSizes.size()-1])) *(1-porosity)/Mathr::PI*(psdCumm[i]-psdCumm[i-1])/(psdSizes[i]-psdSizes[i-1])*(pow(psdSizes[i-1],-2)-pow(psdSizes[i],-2)));
 
131
                        }
 
132
                        LOG_DEBUG(i<<". "<<psdRadii[i]<<", cdf="<<psdCumm[i]<<", cdf2="<<(distributeMass?lexical_cast<string>(psdCumm2[i]):string("--")));
 
133
                        // check monotonicity
 
134
                        if(i>0 && (psdSizes[i-1]>psdSizes[i] || psdCumm[i-1]>psdCumm[i])) throw invalid_argument("SpherePack:makeCloud: psdSizes and psdCumm must be both non-decreasing.");
 
135
                }
 
136
                // check the consistency between sizes, num, and poro if all three are imposed. If target number will not fit in (1-poro)*volume, scale down particles sizes
 
137
                if (num>1){
 
138
                        appliedPsdScaling=1;
 
139
                        if(distributeMass) {
 
140
                                if (psdCumm2[psdSizes.size()-1]<num) appliedPsdScaling=pow(psdCumm2[psdSizes.size()-1]/num,1./3.);
 
141
                        } else {
 
142
                                double totVol=0;
 
143
                                for(size_t i=1; i<psdSizes.size(); i++) totVol+= 4/3*Mathr::PI*(psdCumm[i]-psdCumm[i-1])*num*
 
144
                                        pow(0.5*(psdSizes[i]+psdSizes[i-1]),3)*(1+pow(0.5*(psdSizes[i]-psdSizes[i-1]),2));
 
145
                                Real volumeRatio = totVol/((1-porosity)*(volume?volume:(area*psdSizes[psdSizes.size()-1])));
 
146
                                if (volumeRatio>1) appliedPsdScaling=pow(volumeRatio,-1./3.);
 
147
                        }
 
148
                        if (appliedPsdScaling<1) for(size_t i=0; i<psdSizes.size(); i++) psdRadii[i]*=appliedPsdScaling;
 
149
                }
 
150
                //Normalize psdCumm2 so it's between 0 and 1
 
151
                if(distributeMass) for(size_t i=1; i<psdSizes.size(); i++) psdCumm2[i]/=psdCumm2[psdSizes.size()-1];
 
152
        }
 
153
        if(err || mode<0) throw invalid_argument("SpherePack.makeCloud: at least one of rMean, porosity, psdSizes & psdCumm arguments must be specified. rMean can't be combined with psdSizes.");
 
154
        // adjust uniform distribution parameters with distributeMass; rMean has the meaning (dimensionally) of _volume_
 
155
        const int maxTry=1000;
 
156
        if(periodic && volume && !hSizeFound)(cellSize=size);
 
157
        Real r=0;
 
158
        for(int i=0; (i<num) || (num<0); i++) {
 
159
                Real norm, rand;
 
160
                //Determine radius of the next sphere that will be placed in space. If (num>0), generate radii the deterministic way, in decreasing order, else radii are stochastic since we don't know what the final number will be
 
161
                if (num>0) rand = ((Real)num-(Real)i+0.5)/((Real)num+1.);
 
162
                else rand = rnd();
 
163
                int t;
 
164
                switch(mode){
 
165
                        case RDIST_RMEAN:
 
166
                        //FIXME : r is never defined, it will be zero at first iteration, but it will have values in the next ones. Some magic?
 
167
                        case RDIST_NUM:
 
168
                                if(distributeMass) r=pow3Interp(rand,rMean*(1-rRelFuzz),rMean*(1+rRelFuzz));
 
169
                                else r=rMean*(2*(rand-.5)*rRelFuzz+1); // uniform distribution in rMean*(1±rRelFuzz)
 
170
                                break;
 
171
                        case RDIST_PSD:
 
172
                                if(distributeMass){
 
173
                                        int piece=psdGetPiece(rand,psdCumm2,norm);
 
174
                                        r=pow3Interp(norm,psdRadii[piece],psdRadii[piece+1]);
 
175
                                } else {
 
176
                                        int piece=psdGetPiece(rand,psdCumm,norm);
 
177
                                        r=psdRadii[piece]+norm*(psdRadii[piece+1]-psdRadii[piece]);}
 
178
                }
 
179
                // try to put the sphere into a free spot
 
180
                for(t=0; t<maxTry; ++t){
 
181
                        Vector3r c;
 
182
                        if(!periodic) { for(int axis=0; axis<3; axis++) c[axis]=mn[axis]+(size[axis]?(size[axis]-2*r)*rnd()+r:0);}//we handle 2D with the special case size[axis]==0
 
183
                        else {  for(int axis=0; axis<3; axis++) c[axis]=rnd();//coordinates in [0,1]
 
184
                                c=mn+hSize*c;}//coordinates in reference frame (inside the base cell)
 
185
                        size_t packSize=pack.size(); bool overlap=false;
 
186
                        if(!periodic) for(size_t j=0;j<packSize;j++) {if(pow(pack[j].r+r,2)>=(pack[j].c-c).squaredNorm()) {overlap=true; break;}}
 
187
                        else {
 
188
                                for(size_t j=0; j<packSize; j++){
 
189
                                        Vector3r dr=Vector3r::Zero();
 
190
                                        if (!hSizeFound) {//The box is axis-aligned, use the wrap methods
 
191
                                                for(int axis=0; axis<3; axis++) dr[axis]=size[axis]? min(cellWrapRel(c[axis],pack[j].c[axis],pack[j].c[axis]+size[axis]),cellWrapRel(pack[j].c[axis],c[axis],c[axis]+size[axis])) : 0;
 
192
                                        } else {//not aligned, find closest neighbor in a cube of size 1, then transform distance to cartesian coordinates
 
193
                                                Vector3r c1c2=invHsize*(pack[j].c-c);
 
194
                                                for(int axis=0; axis<3; axis++){
 
195
                                                        if (abs(c1c2[axis])<abs(c1c2[axis] - Mathr::Sign(c1c2[axis]))) dr[axis]=c1c2[axis];
 
196
                                                        else dr[axis] = c1c2[axis] - Mathr::Sign(c1c2[axis]);}
 
197
                                                dr=hSize*dr;//now in cartesian coordinates
 
198
                                        }
 
199
                                        if(pow(pack[j].r+r,2)>= dr.squaredNorm()){ overlap=true; break; }
 
200
                                }
 
201
                        }
 
202
                        if(!overlap) { pack.push_back(Sph(c,r)); break; }
 
203
                }
 
204
                if (t==maxTry) {
 
205
                        if(num>0) {
 
206
                                if (mode!=RDIST_RMEAN) {
 
207
                                        //if rMean is not imposed, then we call makeCloud recursively, scaling the PSD down until the target num is obtained
 
208
                                        Real nextPoro = porosity+(1-porosity)/10.;
 
209
                                        LOG_WARN("Exceeded "<<maxTry<<" tries to insert non-overlapping sphere to packing. Only "<<i<<" spheres was added, although you requested "<<num<<". Trying again with porosity "<<nextPoro<<". The size distribution is being scaled down");
 
210
                                        pack.clear();
 
211
                                        return makeCloud(mn, mx, -1., rRelFuzz, num, periodic, nextPoro, psdSizes, psdCumm, distributeMass,seed,hSizeFound?hSize:Matrix3r::Zero());}
 
212
                                else LOG_WARN("Exceeded "<<maxTry<<" tries to insert non-overlapping sphere to packing. Only "<<i<<" spheres was added, although you requested "<<num<<".");
 
213
                        }
 
214
                        return i;}
 
215
        }
 
216
        if (appliedPsdScaling<1) LOG_WARN("The size distribution has been scaled down by a factor pack.appliedPsdScaling="<<appliedPsdScaling);
 
217
        return pack.size();
 
218
}
 
219
 
 
220
void SpherePack::cellFill(Vector3r vol){
 
221
        Vector3i count;
 
222
        for(int i=0; i<3; i++) count[i]=(int)(ceil(vol[i]/cellSize[i]));
 
223
        LOG_DEBUG("Filling volume "<<vol<<" with cell "<<cellSize<<", repeat counts are "<<count);
 
224
        cellRepeat(count);
 
225
}
 
226
 
 
227
void SpherePack::cellRepeat(Vector3i count){
 
228
        if(cellSize==Vector3r::Zero()){ throw std::runtime_error("cellRepeat cannot be used on non-periodic packing."); }
 
229
        if(count[0]<=0 || count[1]<=0 || count[2]<=0){ throw std::invalid_argument("Repeat count components must be positive."); }
 
230
        size_t origSize=pack.size();
 
231
        pack.reserve(origSize*count[0]*count[1]*count[2]);
 
232
        for(int i=0; i<count[0]; i++){
 
233
                for(int j=0; j<count[1]; j++){
 
234
                        for(int k=0; k<count[2]; k++){
 
235
                                if((i==0) && (j==0) && (k==0)) continue; // original cell
 
236
                                Vector3r off(cellSize[0]*i,cellSize[1]*j,cellSize[2]*k);
 
237
                                for(size_t l=0; l<origSize; l++){
 
238
                                        const Sph& s=pack[l]; pack.push_back(Sph(s.c+off,s.r));
 
239
                                }
 
240
                        }
 
241
                }
 
242
        }
 
243
        cellSize=Vector3r(cellSize[0]*count[0],cellSize[1]*count[1],cellSize[2]*count[2]);
 
244
}
 
245
 
 
246
int SpherePack::psdGetPiece(Real x, const vector<Real>& cumm, Real& norm){
 
247
        int sz=cumm.size(); int i=0;
 
248
        while(i<sz && cumm[i]<=x) i++; // upper interval limit index
 
249
        if((i==sz-1) && cumm[i]<=x){ i=sz-2; norm=1.; return i;}
 
250
        i--; // lower interval limit intex
 
251
        norm=(x-cumm[i])/(cumm[i+1]-cumm[i]);
 
252
        //LOG_TRACE("count="<<sz<<", x="<<x<<", piece="<<i<<" in "<<cumm[i]<<"…"<<cumm[i+1]<<", norm="<<norm);
 
253
        return i;
 
254
}
 
255
 
 
256
py::tuple SpherePack::psd(int bins, bool mass) const {
 
257
        if(pack.size()==0) return py::make_tuple(py::list(),py::list()); // empty packing
 
258
        // find extrema
 
259
        Real minD=std::numeric_limits<Real>::infinity(); Real maxD=-minD;
 
260
        // volume, but divided by π*4/3
 
261
        Real vol=0; long N=pack.size();
 
262
        FOREACH(const Sph& s, pack){ maxD=max(2*s.r,maxD); minD=min(2*s.r,minD); vol+=pow(s.r,3); }
 
263
        if(minD==maxD){ minD-=.5; maxD+=.5; } // emulates what numpy.histogram does
 
264
        // create bins and bin edges
 
265
        vector<Real> hist(bins,0); vector<Real> cumm(bins+1,0); /* cummulative values compute from hist at the end */
 
266
        vector<Real> edges(bins+1); for(int i=0; i<=bins; i++){ edges[i]=minD+i*(maxD-minD)/bins; }
 
267
        // weight each grain by its "volume" relative to overall "volume"
 
268
        FOREACH(const Sph& s, pack){
 
269
                int bin=int(bins*(2*s.r-minD)/(maxD-minD)); bin=min(bin,bins-1); // to make sure
 
270
                if (mass) hist[bin]+=pow(s.r,3)/vol; else hist[bin]+=1./N;
 
271
        }
 
272
        for(int i=0; i<bins; i++) cumm[i+1]=min(1.,cumm[i]+hist[i]); // cumm[i+1] is OK, cumm.size()==bins+1
 
273
        return py::make_tuple(edges,cumm);
 
274
}
 
275
 
 
276
/* possible enhacement: proportions parameter, so that the domain is not cube, but box with sides having given proportions */
 
277
long SpherePack::particleSD2(const vector<Real>& radii, const vector<Real>& passing, int numSph, bool periodic, Real cloudPorosity, int seed){
 
278
        typedef Eigen::Matrix<Real,Eigen::Dynamic,Eigen::Dynamic> MatrixXr;
 
279
        typedef Eigen::Matrix<Real,Eigen::Dynamic,1> VectorXr;
 
280
        
 
281
        int dim=radii.size()+1;
 
282
        if(passing.size()!=radii.size()) throw std::invalid_argument("SpherePack.particleSD2: radii and passing must have the same length.");
 
283
        MatrixXr M=MatrixXr::Zero(dim,dim);
 
284
        VectorXr rhs=VectorXr::Zero(dim);
 
285
        /*
 
286
        
 
287
        We know percentages for each fraction (Δpi) and their radii (ri), and want to find
 
288
        the number of sphere for each fraction Ni and total solid volume Vs. For each fraction,
 
289
        we know that the volume is equal to Ni*(4/3*πri³), which must be equal to Vs*Δpi (Δpi is
 
290
        relative solid volume of the i-th fraction).
 
291
 
 
292
        The last equation says that total number of particles (sum of fractions) is equal to N,
 
293
        which is the total number of particles requested by the user.
 
294
 
 
295
           N1     N2     N3    Vs       rhs
 
296
 
 
297
        4/3πr₁³   0      0     -Δp₁   | 0
 
298
          0     4/3πr₂³  0     -Δp₂   | 0
 
299
          0       0    4/3πr₃³ -Δp₃   | 0
 
300
     1       1      1      0     | N
 
301
 
 
302
        */
 
303
        for(int i=0; i<dim-1; i++){
 
304
                M(i,i)=(4/3.)*Mathr::PI*pow(radii[i],3);
 
305
                M(i,dim-1)=-(passing[i]-(i>0?passing[i-1]:0))/100.;
 
306
                M(dim-1,i)=1;
 
307
        }
 
308
        rhs[dim-1]=numSph;
 
309
        // NumsVs=M^-1*rhs: number of spheres and volume of solids
 
310
        VectorXr NumsVs(dim); NumsVs=M.inverse()*rhs;
 
311
        Real Vs=NumsVs[dim-1]; // total volume of solids
 
312
        Real Vtot=Vs/(1-cloudPorosity); // total volume of cell containing the packing
 
313
        Vector3r cellSize=pow(Vtot,1/3.)*Vector3r().Ones(); // for now, assume always cubic sample
 
314
        Real rMean=pow(Vs/(numSph*(4/3.)*Mathr::PI),1/3.); // make rMean such that particleSD will compute the right Vs (called a bit confusingly Vtot anyway) inversely
 
315
        // cerr<<"Vs="<<Vs<<", Vtot="<<Vtot<<", rMean="<<rMean<<endl;
 
316
        // cerr<<"cellSize="<<cellSize<<", rMean="<<rMean<<", numSph="<<numSph<<endl;
 
317
        return particleSD(Vector3r::Zero(),cellSize,rMean,periodic,"",numSph,radii,passing,false);
 
318
};
 
319
 
 
320
// TODO: header, python wrapper, default params
 
321
 
 
322
// Discrete particle size distribution
 
323
long SpherePack::particleSD(Vector3r mn, Vector3r mx, Real rMean, bool periodic, string name, int numSph, const vector<Real>& radii, const vector<Real>& passing, bool passingIsNotPercentageButCount, int seed){
 
324
        vector<Real> numbers;
 
325
        if(!passingIsNotPercentageButCount){
 
326
                Real Vtot=numSph*4./3.*Mathr::PI*pow(rMean,3.); // total volume of the packing (computed with rMean)
 
327
                
 
328
                // calculate number of spheres necessary per each radius to match the wanted psd
 
329
                // passing has to contain increasing values
 
330
                for (size_t i=0; i<radii.size(); i++){
 
331
                        Real volS=4./3.*Mathr::PI*pow(radii[i],3.);
 
332
                        if (i==0) {numbers.push_back(passing[i]/100.*Vtot/volS);}
 
333
                        else {numbers.push_back((passing[i]-passing[i-1])/100.*Vtot/volS);} // 
 
334
                        cout<<"fraction #"<<i<<" ("<<passing[i]<<"%, r="<<radii[i]<<"): "<<numbers[i]<<" spheres, fraction/cloud volumes "<<volS<<"/"<<Vtot<<endl;
 
335
                }
 
336
        } else {
 
337
                FOREACH(Real p, passing) numbers.push_back(p);
 
338
        }
 
339
 
 
340
        static boost::minstd_rand randGen(seed!=0?seed:(int)TimingInfo::getNow(true));
 
341
        static boost::variate_generator<boost::minstd_rand&, boost::uniform_real<> > rnd(randGen, boost::uniform_real<>(0,1));
 
342
 
 
343
        const int maxTry=1000;
 
344
        Vector3r size=mx-mn;
 
345
        if(periodic)(cellSize=size);
 
346
        for (int ii=(int)radii.size()-1; ii>=0; ii--){
 
347
                Real r=radii[ii]; // select radius
 
348
                for(int i=0; i<numbers[ii]; i++) { // place as many spheres as required by the psd for the selected radius into the free spot
 
349
                        int t;
 
350
                        for(t=0; t<maxTry; ++t){
 
351
                                Vector3r c;
 
352
                                if(!periodic) { for(int axis=0; axis<3; axis++) c[axis]=mn[axis]+r+(size[axis]-2*r)*rnd(); }
 
353
                                else { for(int axis=0; axis<3; axis++) c[axis]=mn[axis]+size[axis]*rnd(); }
 
354
                                size_t packSize=pack.size(); bool overlap=false;
 
355
                                if(!periodic){
 
356
                                        for(size_t j=0; j<packSize; j++){ if(pow(pack[j].r+r,2) >= (pack[j].c-c).squaredNorm()) { overlap=true; break; } }
 
357
                                } else {
 
358
                                        for(size_t j=0; j<packSize; j++){
 
359
                                                Vector3r dr;
 
360
                                                for(int axis=0; axis<3; axis++) dr[axis]=min(cellWrapRel(c[axis],pack[j].c[axis],pack[j].c[axis]+size[axis]),cellWrapRel(pack[j].c[axis],c[axis],c[axis]+size[axis]));
 
361
                                                if(pow(pack[j].r+r,2)>= dr.squaredNorm()){ overlap=true; break; }
 
362
                                        }
 
363
                                }
 
364
                                if(!overlap) { pack.push_back(Sph(c,r)); break; }
 
365
                        }
 
366
                        if (t==maxTry) {
 
367
                                if(numbers[ii]>0) LOG_WARN("Exceeded "<<maxTry<<" tries to insert non-overlapping sphere to packing. Only "<<i<<" spheres was added, although you requested "<<numbers[ii]<<" with radius "<<radii[ii]);
 
368
                                return i;
 
369
                        }
 
370
                }
 
371
        }
 
372
        return pack.size();
 
373
}
 
374
 
 
375
// 2d function
 
376
long SpherePack::particleSD_2d(Vector2r mn, Vector2r mx, Real rMean, bool periodic, string name, int numSph, const vector<Real>& radii, const vector<Real>& passing, bool passingIsNotPercentageButCount, int seed){
 
377
        vector<Real> numbers;
 
378
        if(!passingIsNotPercentageButCount){
 
379
                Real Vtot=numSph*4./3.*Mathr::PI*pow(rMean,3.); // total volume of the packing (computed with rMean)
 
380
                
 
381
                // calculate number of spheres necessary per each radius to match the wanted psd
 
382
                // passing has to contain increasing values
 
383
                for (size_t i=0; i<radii.size(); i++){
 
384
                        Real volS=4./3.*Mathr::PI*pow(radii[i],3.);
 
385
                        if (i==0) {numbers.push_back(passing[i]/100.*Vtot/volS);}
 
386
                        else {numbers.push_back((passing[i]-passing[i-1])/100.*Vtot/volS);} // 
 
387
                        cout<<"fraction #"<<i<<" ("<<passing[i]<<"%, r="<<radii[i]<<"): "<<numbers[i]<<" spheres, fraction/cloud volumes "<<volS<<"/"<<Vtot<<endl;
 
388
                }
 
389
        } else {
 
390
                FOREACH(Real p, passing) numbers.push_back(p);
 
391
        }
 
392
 
 
393
        static boost::minstd_rand randGen(seed!=0?seed:(int)TimingInfo::getNow(true));
 
394
        static boost::variate_generator<boost::minstd_rand&, boost::uniform_real<> > rnd(randGen, boost::uniform_real<>(0,1));
 
395
 
 
396
        const int maxTry=1000;
 
397
        Vector2r size=mx-mn; 
 
398
        //if(periodic)(cellSize=size); in this case, it must be defined in py script as cell needs the third dimension
 
399
        for (int ii=(int)radii.size()-1; ii>=0; ii--){
 
400
                Real r=radii[ii]; // select radius
 
401
                for(int i=0; i<numbers[ii]; i++) { // place as many spheres as required by the psd for the selected radius into the free spot
 
402
                        int t;
 
403
                        for(t=0; t<maxTry; ++t){
 
404
                                Vector3r c;
 
405
                                if(!periodic) { for(int axis=0; axis<2; axis++) c[axis]=mn[axis]+r+(size[axis]-2*r)*rnd(); }
 
406
                                else { for(int axis=0; axis<2; axis++) c[axis]=mn[axis]+size[axis]*rnd(); }
 
407
                                size_t packSize=pack.size(); bool overlap=false;
 
408
                                if(!periodic){
 
409
                                        for(size_t j=0; j<packSize; j++){ if(pow(pack[j].r+r,2) >= (pack[j].c-c).squaredNorm()) { overlap=true; break; } }
 
410
                                } else {
 
411
                                        for(size_t j=0; j<packSize; j++){
 
412
                                                Vector3r dr;
 
413
                                                for(int axis=0; axis<2; axis++) dr[axis]=min(cellWrapRel(c[axis],pack[j].c[axis],pack[j].c[axis]+size[axis]),cellWrapRel(pack[j].c[axis],c[axis],c[axis]+size[axis]));
 
414
                                                if(pow(pack[j].r+r,2)>= dr.squaredNorm()){ overlap=true; break; }
 
415
                                        }
 
416
                                }
 
417
                                if(!overlap) { pack.push_back(Sph(c,r)); break; }
 
418
                        }
 
419
                        if (t==maxTry) {
 
420
                                if(numbers[ii]>0) LOG_WARN("Exceeded "<<maxTry<<" tries to insert non-overlapping sphere to packing. Only "<<i<<" spheres was added, although you requested "<<numbers[ii]<<" with radius "<<radii[ii]);
 
421
                                return i;
 
422
                        }
 
423
                }
 
424
        }
 
425
        return pack.size();
 
426
}
 
427
 
 
428
long SpherePack::makeClumpCloud(const Vector3r& mn, const Vector3r& mx, const vector<shared_ptr<SpherePack> >& _clumps, bool periodic, int num){
 
429
        // recenter given clumps and compute their margins
 
430
        vector<SpherePack> clumps; /* vector<Vector3r> margins; */ Vector3r boxMargins(Vector3r::Zero()); Real maxR=0;
 
431
        vector<Real> boundRad; // squared radii of bounding sphere for each clump
 
432
        FOREACH(const shared_ptr<SpherePack>& c, _clumps){
 
433
                SpherePack c2(*c); 
 
434
                c2.translate(c2.midPt()); //recenter
 
435
                clumps.push_back(c2);
 
436
                Real r=0;
 
437
                FOREACH(const Sph& s, c2.pack) r=max(r,s.c.norm()+s.r);
 
438
                boundRad.push_back(r);
 
439
                Vector3r cMn,cMx; c2.aabb(cMn,cMx); // centered at zero now, this gives margin
 
440
                //margins.push_back(periodic?cMx:Vector3r::Zero()); 
 
441
                //boxMargins=boxMargins.cwise().max(cMx);
 
442
                FOREACH(const Sph& s, c2.pack) maxR=max(maxR,s.r); // keep track of maximum sphere radius
 
443
        }
 
444
        std::list<ClumpInfo> clumpInfos;
 
445
        Vector3r size=mx-mn;
 
446
        if(periodic)(cellSize=size);
 
447
        const int maxTry=200;
 
448
        int nGen=0; // number of clumps generated
 
449
        // random point coordinate generator, with non-zero margins if aperiodic
 
450
        static boost::minstd_rand randGen(TimingInfo::getNow(true));
 
451
        typedef boost::variate_generator<boost::minstd_rand&, boost::uniform_real<> > UniRandGen;
 
452
        static UniRandGen rndX(randGen,boost::uniform_real<>(mn[0],mx[0]));
 
453
        static UniRandGen rndY(randGen,boost::uniform_real<>(mn[1],mx[1]));
 
454
        static UniRandGen rndZ(randGen,boost::uniform_real<>(mn[2],mx[2]));
 
455
        static UniRandGen rndUnit(randGen,boost::uniform_real<>(0,1));
 
456
        while(nGen<num || num<0){
 
457
                int clumpChoice=rand()%clumps.size();
 
458
                int tries=0;
 
459
                while(true){ // check for tries at the end
 
460
                        Vector3r pos(rndX(),rndY(),rndZ()); // random point
 
461
                        // TODO: check this random orientation is homogeneously distributed
 
462
                        Quaternionr ori(rndUnit(),rndUnit(),rndUnit(),rndUnit()); ori.normalize();
 
463
                        // copy the packing and rotate
 
464
                        SpherePack C(clumps[clumpChoice]); C.rotateAroundOrigin(ori); C.translate(pos);
 
465
                        const Real& rad(boundRad[clumpChoice]);
 
466
                        ClumpInfo ci; // to be used later, but must be here because of goto's
 
467
 
 
468
                        // find collisions
 
469
                        // check against bounding spheres of other clumps, and only check individual spheres if there is overlap
 
470
                        if(!periodic){
 
471
                                // check overlap with box margins first
 
472
                                if((pos+rad*Vector3r::Ones()).cwise().max(mx)!=mx || (pos-rad*Vector3r::Ones()).cwise().min(mn)!=mn){ FOREACH(const Sph& s, C.pack) if((s.c+s.r*Vector3r::Ones()).cwise().max(mx)!=mx || (s.c-s.r*Vector3r::Ones()).cwise().min(mn)!=mn) goto overlap; }
 
473
                                // check overlaps with bounding spheres of other clumps
 
474
                                FOREACH(const ClumpInfo& cInfo, clumpInfos){
 
475
                                        bool detailedCheck=false;
 
476
                                        // check overlaps between individual spheres and bounding sphere of the other clump
 
477
                                        if((pos-cInfo.center).squaredNorm()<pow(rad+cInfo.rad,2)){ FOREACH(const Sph& s, C.pack) if(pow(s.r+cInfo.rad,2)>(s.c-cInfo.center).squaredNorm()){ detailedCheck=true; break; }}
 
478
                                        // check sphere-by-sphere, since bounding spheres did overlap
 
479
                                        if(detailedCheck){ FOREACH(const Sph& s, C.pack) for(int id=cInfo.minId; id<=cInfo.maxId; id++) if((s.c-pack[id].c).squaredNorm()<pow(s.r+pack[id].r,2)) goto overlap;}
 
480
                                }
 
481
                        } else {
 
482
                                FOREACH(const ClumpInfo& cInfo, clumpInfos){
 
483
                                        // bounding spheres overlap (in the periodic space)
 
484
                                        if(periPtDistSq(pos,cInfo.center)<pow(rad+cInfo.rad,2)){
 
485
                                                bool detailedCheck=false;
 
486
                                                // check spheres with bounding sphere of the other clump
 
487
                                                FOREACH(const Sph& s, C.pack) if(pow(s.r+cInfo.rad,2)>periPtDistSq(s.c,cInfo.center)){ detailedCheck=true; break; }
 
488
                                                // check sphere-by-sphere
 
489
                                                if(detailedCheck){ FOREACH(const Sph& s, C.pack) for(int id=cInfo.minId; id<=cInfo.maxId; id++) if(periPtDistSq(s.c,pack[id].c)<pow(s.r+pack[id].r,2)) goto overlap; }
 
490
                                        }
 
491
                                }
 
492
                        }
 
493
 
 
494
                        #if 0
 
495
                        // crude algorithm: check all spheres against all other spheres (slow!!)
 
496
                        // use vtkPointLocator, add all existing points and check distance of r+maxRadius, then refine
 
497
                        // for periodicity, duplicate points close than boxMargins to the respective boundary
 
498
                        if(!periodic){
 
499
                                for(size_t i=0; i<C.pack.size(); i++){
 
500
                                        for(size_t j=0; j<pack.size(); j++){
 
501
                                                const Vector3r& c(C.pack[i].c); const Real& r(C.pack[i].r);
 
502
                                                if(pow(r+pack[j].r,2)>=(c-pack[j].c).squaredNorm()) goto overlap;
 
503
                                                // check that we are not over the box boundary
 
504
                                                // this could be handled by adjusting the position random interval (by taking off the smallest radius in the clump)
 
505
                                                // but usually the margin band is relatively small and this does not make the code as hairy 
 
506
                                                if((c+r*Vector3r::Ones()).cwise().max(mx)!=mx || (c-r*Vector3r::Ones()).cwise().min(mn)!=mn) goto overlap; 
 
507
                                        }
 
508
                                }
 
509
                        }else{
 
510
                                for(size_t i=0; i<C.pack.size(); i++){
 
511
                                        for(size_t j=0; j<pack.size(); j++){
 
512
                                                const Vector3r& c(C.pack[i].c); const Real& r(C.pack[i].r);
 
513
                                                Vector3r dr;
 
514
                                                for(int axis=0; axis<3; axis++) dr[axis]=min(cellWrapRel(c[axis],pack[j].c[axis],pack[j].c[axis]+size[axis]),cellWrapRel(pack[j].c[axis],c[axis],c[axis]+size[axis]));
 
515
                                                if(pow(pack[j].r+r,2)>= dr.squaredNorm()) goto overlap;
 
516
                                        }
 
517
                                }
 
518
                        }
 
519
                        #endif
 
520
 
 
521
                        // add the clump, if no collisions
 
522
                        /*number clumps consecutively*/ ci.clumpId=nGen; ci.center=pos; ci.rad=rad; ci.minId=pack.size(); ci.maxId=pack.size()+C.pack.size(); 
 
523
                        FOREACH(const Sph& s, C.pack){
 
524
                                pack.push_back(Sph(s.c,s.r,ci.clumpId));
 
525
                        }
 
526
                        clumpInfos.push_back(ci);
 
527
                        nGen++;
 
528
                        //cerr<<"O";
 
529
                        break; // break away from the try-loop
 
530
 
 
531
                        overlap:
 
532
                        //cerr<<".";
 
533
                        if(tries++==maxTry){ // last loop 
 
534
                                if(num>0) LOG_WARN("Exceeded "<<maxTry<<" attempts to place non-overlapping clump. Only "<<nGen<<" clumps were added, although you requested "<<num);
 
535
                                return nGen;
 
536
                        }
 
537
                }
 
538
        }
 
539
        return nGen;
 
540
}
 
541
 
 
542
bool SpherePack::hasClumps() const { FOREACH(const Sph& s, pack){ if(s.clumpId>=0) return true; } return false; }
 
543
python::tuple SpherePack::getClumps() const{
 
544
        std::map<int,py::list> clumps;
 
545
        py::list standalone; size_t packSize=pack.size();
 
546
        for(size_t i=0; i<packSize; i++){
 
547
                const Sph& s(pack[i]);
 
548
                if(s.clumpId<0) { standalone.append(i); continue; }
 
549
                if(clumps.count(s.clumpId)==0) clumps[s.clumpId]=py::list();
 
550
                clumps[s.clumpId].append(i);
 
551
        }
 
552
        py::list clumpList;
 
553
        typedef std::pair<int,py::list> intListPair;
 
554
        FOREACH(const intListPair& c, clumps) clumpList.append(c.second);
 
555
        return python::make_tuple(standalone,clumpList); 
 
556
}
 
557