vector<Real> DemFuncs::contactCoordQuantiles(const shared_ptr<DemField>& dem, const vector<Real>& quantiles, const shared_ptr<Node>& node, const AlignedBox3r& box){ vector<Real> ret; ret.reserve(quantiles.size()); if(quantiles.empty()) return ret; vector<Real> coords; // count contacts first // this count will be off when range is given; it will be larger, so copying is avoided at the cost of some spurious allocation size_t N; #if 0 N=boost::range::count_if(*dem->contacts,[&](const shared_ptr<Contact>& C)->bool{ return C->isReal(); }); coords.reserve(N); #endif for(const shared_ptr<Contact>& C: *dem->contacts){ if(!C->isReal()) continue; Vector3r p=node?node->glob2loc(C->geom->node->pos):C->geom->node->pos; if(!box.isEmpty() && !box.contains(p)) continue; coords.push_back(p[2]); }; // no contacts yet, return NaNs if(coords.empty()){ for(size_t i=0; i<quantiles.size(); i++) ret.push_back(NaN); return ret; } boost::range::sort(coords); N=coords.size(); // should be the same as N before for(Real q: quantiles){ // clamp the value to 0,1 q=min(max(0.,q),1.); if(isnan(q)) q=0.; // just to make sure int i=min((int)((N-1)*q),(int)N-1); ret.push_back(coords[i]); } return ret; }
Real DemFuncs::porosity(const shared_ptr<DemField>& dem, const shared_ptr<Node>& node, const AlignedBox3r& box){ Real Vs=0; // solid volume Real V=box.volume(); // box volume for(const auto& p: *dem->particles){ if(!p->shape->isA<Sphere>()) continue; const Vector3r& pos=p->shape->nodes[0]->pos; if(!box.contains(node?node->glob2loc(pos):pos)) continue; Vs+=(4/3.)*M_PI*pow(p->shape->cast<Sphere>().radius,3); } return Vs/V; }
/* See https://yade-dem.org/doc/yade.utils.html#yade.utils.avgNumInteractions for docs */ Real DemFuncs::coordNumber(const shared_ptr<DemField>& dem, const shared_ptr<Node>& node, const AlignedBox3r& box, int mask, bool skipFree){ long C2=0; // twice the number of contacts (counted for each participating particle) long N0=0; // number of particles without contact (floaters) long N1=0; // number of particles with one contact (rattlers) long N=0; // number of all particles for(const auto& p: *dem->particles){ const Vector3r& pos=p->shape->nodes[0]->pos; if(mask && (mask&p->mask)==0) continue; if(p->shape->nodes[0]->getData<DemData>().isClumped()) throw std::runtime_error("Not yet implemented for clumps."); if(!box.contains(node?node->glob2loc(pos):pos)) continue; int n=p->countRealContacts(); if(n==0) N0++; else if(n==1) N1++; N++; C2+=n; } if(skipFree) return (C2-N1)*1./(N-N0-N1); else return C2*1./N; }
vector<Vector2r> DemFuncs::boxPsd(const Scene* scene, const DemField* dem, const AlignedBox3r& box, bool mass, int num, int mask, Vector2r rRange){ bool haveBox=!isnan(box.min()[0]) && !isnan(box.max()[0]); return psd( *dem->particles|boost::adaptors::filtered([&](const shared_ptr<Particle>&p){ return p && p->shape && p->shape->nodes.size()==1 && (mask?(p->mask&mask):true) && (bool)(dynamic_pointer_cast<woo::Sphere>(p->shape)) && (haveBox?box.contains(p->shape->nodes[0]->pos):true); }), /*cumulative*/true,/*normalize*/true, num, rRange, /*diameter getter*/[](const shared_ptr<Particle>&p) ->Real { return 2.*p->shape->cast<Sphere>().radius; }, /*weight getter*/[&](const shared_ptr<Particle>&p) -> Real{ return mass?p->shape->nodes[0]->getData<DemData>().mass:1.; } ); }
void RandomInlet::run(){ DemField* dem=static_cast<DemField*>(field.get()); if(!generator) throw std::runtime_error("RandomInlet.generator==None!"); if(materials.empty()) throw std::runtime_error("RandomInlet.materials is empty!"); if(collideExisting){ if(!collider){ for(const auto& e: scene->engines){ collider=dynamic_pointer_cast<Collider>(e); if(collider) break; } if(!collider) throw std::runtime_error("RandomInlet: no Collider found within engines (needed for collisions detection with already existing particles; if you don't need that, set collideExisting=False.)"); } if(dynamic_pointer_cast<InsertionSortCollider>(collider)) static_pointer_cast<InsertionSortCollider>(collider)->forceInitSort=true; } if(isnan(massRate)) throw std::runtime_error("RandomInlet.massRate must be given (is "+to_string(massRate)+"); if you want to generate as many particles as possible, say massRate=0."); if(massRate<=0 && maxAttempts==0) throw std::runtime_error("RandomInlet.massFlowRate<=0 (no massFlowRate prescribed), but RandomInlet.maxAttempts==0. (unlimited number of attempts); this would cause infinite loop."); if(maxAttempts<0){ std::runtime_error("RandomInlet.maxAttempts must be non-negative. Negative value, leading to meaking engine dead, is achieved by setting atMaxAttempts=RandomInlet.maxAttDead now."); } spheresOnly=generator->isSpheresOnly(); padDist=generator->padDist(); if(isnan(padDist) || padDist<0) throw std::runtime_error(generator->pyStr()+".padDist(): returned invalid value "+to_string(padDist)); // as if some time has already elapsed at the very start // otherwise mass flow rate is too big since one particle during Δt exceeds it easily // equivalent to not running the first time, but without time waste if(stepPrev==-1 && stepPeriod>0) stepPrev=-stepPeriod; long nSteps=scene->step-stepPrev; // to be attained in this step; stepGoalMass+=massRate*scene->dt*nSteps; // stepLast==-1 if never run, which is OK vector<AlignedBox3r> genBoxes; // of particles created in this step vector<shared_ptr<Particle>> generated; Real stepMass=0.; SpherePack spheres; // if(spheresOnly){ spheres.pack.reserve(dem->particles->size()*1.2); // something extra for generated particles // HACK!!! bool isBox=dynamic_cast<BoxInlet*>(this); AlignedBox3r box; if(isBox){ box=this->cast<BoxInlet>().box; } for(const auto& p: *dem->particles){ if(p->shape->isA<Sphere>() && (!isBox || box.contains(p->shape->nodes[0]->pos))) spheres.pack.push_back(SpherePack::Sph(p->shape->nodes[0]->pos,p->shape->cast<Sphere>().radius)); } } while(true){ // finished forever if(everythingDone()) return; // finished in this step if(massRate>0 && mass>=stepGoalMass) break; shared_ptr<Material> mat; Vector3r pos=Vector3r::Zero(); Real diam; vector<ParticleGenerator::ParticleAndBox> pee; int attempt=-1; while(true){ attempt++; /***** too many tries, give up ******/ if(attempt>=maxAttempts){ generator->revokeLast(); // last particle could not be placed if(massRate<=0){ LOG_DEBUG("maxAttempts="<<maxAttempts<<" reached; since massRate is not positive, we're done in this step"); goto stepDone; } switch(atMaxAttempts){ case MAXATT_ERROR: throw std::runtime_error("RandomInlet.maxAttempts reached ("+lexical_cast<string>(maxAttempts)+")"); break; case MAXATT_DEAD:{ LOG_INFO("maxAttempts="<<maxAttempts<<" reached, making myself dead."); this->dead=true; return; } case MAXATT_WARN: LOG_WARN("maxAttempts "<<maxAttempts<<" reached before required mass amount was generated; continuing, since maxAttemptsError==False"); break; case MAXATT_SILENT: break; default: throw std::invalid_argument("Invalid value of RandomInlet.atMaxAttempts="+to_string(atMaxAttempts)+"."); } } /***** each maxAttempts/attPerPar, try a new particles *****/ if((attempt%(maxAttempts/attemptPar))==0){ LOG_DEBUG("attempt "<<attempt<<": trying with a new particle."); if(attempt>0) generator->revokeLast(); // if not at the beginning, revoke the last particle // random choice of material with equal probability if(materials.size()==1) mat=materials[0]; else{ size_t i=max(size_t(materials.size()*Mathr::UnitRandom()),materials.size()-1);; mat=materials[i]; } // generate a new particle std::tie(diam,pee)=(*generator)(mat,scene->time); assert(!pee.empty()); LOG_TRACE("Placing "<<pee.size()<<"-sized particle; first component is a "<<pee[0].par->getClassName()<<", extents from "<<pee[0].extents.min().transpose()<<" to "<<pee[0].extents.max().transpose()); } pos=randomPosition(diam,padDist); // overridden in child classes LOG_TRACE("Trying pos="<<pos.transpose()); for(const auto& pe: pee){ // make translated copy AlignedBox3r peBox(pe.extents); peBox.translate(pos); // box is not entirely within the factory shape if(!validateBox(peBox)){ LOG_TRACE("validateBox failed."); goto tryAgain; } const shared_ptr<woo::Sphere>& peSphere=dynamic_pointer_cast<woo::Sphere>(pe.par->shape); if(spheresOnly){ if(!peSphere) throw std::runtime_error("RandomInlet.spheresOnly: is true, but a nonspherical particle ("+pe.par->shape->pyStr()+") was returned by the generator."); Real r=peSphere->radius; Vector3r subPos=peSphere->nodes[0]->pos; for(const auto& s: spheres.pack){ // check dist && don't collide with another sphere from this clump // (abuses the *num* counter for clumpId) if((s.c-(pos+subPos)).squaredNorm()<pow(s.r+r,2)){ LOG_TRACE("Collision with a particle in SpherePack (a particle generated in this step)."); goto tryAgain; } } // don't add to spheres until all particles will have been checked for overlaps (below) } else { // see intersection with existing particles bool overlap=false; if(collideExisting){ vector<Particle::id_t> ids=collider->probeAabb(peBox.min(),peBox.max()); for(const auto& id: ids){ LOG_TRACE("Collider reports intersection with #"<<id); if(id>(Particle::id_t)dem->particles->size() || !(*dem->particles)[id]) continue; const shared_ptr<Shape>& sh2((*dem->particles)[id]->shape); // no spheres, or they are too close if(!peSphere || !sh2->isA<woo::Sphere>() || 1.1*(pos-sh2->nodes[0]->pos).squaredNorm()<pow(peSphere->radius+sh2->cast<Sphere>().radius,2)) goto tryAgain; } } // intersection with particles generated by ourselves in this step for(size_t i=0; i<genBoxes.size(); i++){ overlap=(genBoxes[i].squaredExteriorDistance(peBox)==0.); if(overlap){ const auto& genSh(generated[i]->shape); // for spheres, try to compute whether they really touch if(!peSphere || !genSh->isA<Sphere>() || (pos-genSh->nodes[0]->pos).squaredNorm()<pow(peSphere->radius+genSh->cast<Sphere>().radius,2)){ LOG_TRACE("Collision with "<<i<<"-th particle generated in this step."); goto tryAgain; } } } } } LOG_DEBUG("No collision (attempt "<<attempt<<"), particle will be created :-) "); if(spheresOnly){ // num will be the same for all spheres within this clump (abuse the *num* counter) for(const auto& pe: pee){ Vector3r subPos=pe.par->shape->nodes[0]->pos; Real r=pe.par->shape->cast<Sphere>().radius; spheres.pack.push_back(SpherePack::Sph((pos+subPos),r,/*clumpId*/(pee.size()==1?-1:num))); } } break; tryAgain: ; // try to position the same particle again } // particle was generated successfully and we have place for it for(const auto& pe: pee){ genBoxes.push_back(AlignedBox3r(pe.extents).translate(pos)); generated.push_back(pe.par); } num+=1; #ifdef WOO_OPENGL Real color_=isnan(color)?Mathr::UnitRandom():color; #endif if(pee.size()>1){ // clump was generated //LOG_WARN("Clumps not yet tested properly."); vector<shared_ptr<Node>> nn; for(auto& pe: pee){ auto& p=pe.par; p->mask=mask; #ifdef WOO_OPENGL assert(p->shape); if(color_>=0) p->shape->color=color_; // otherwise keep generator-assigned color #endif if(p->shape->nodes.size()!=1) LOG_WARN("Adding suspicious clump containing particle with more than one node (please check, this is perhaps not tested"); for(const auto& n: p->shape->nodes){ nn.push_back(n); n->pos+=pos; } dem->particles->insert(p); } shared_ptr<Node> clump=ClumpData::makeClump(nn,/*no central node pre-given*/shared_ptr<Node>(),/*intersection*/false); auto& dyn=clump->getData<DemData>(); if(shooter) (*shooter)(clump); if(scene->trackEnergy) scene->energy->add(-DemData::getEk_any(clump,true,true,scene),"kinInlet",kinEnergyIx,EnergyTracker::ZeroDontCreate); if(dyn.angVel!=Vector3r::Zero()){ throw std::runtime_error("pkg/dem/RandomInlet.cpp: generated particle has non-zero angular velocity; angular momentum should be computed so that rotation integration is correct, but it was not yet implemented."); // TODO: compute initial angular momentum, since we will (very likely) use the aspherical integrator } ClumpData::applyToMembers(clump,/*reset*/false); // apply velocity #ifdef WOO_OPENGL boost::mutex::scoped_lock lock(dem->nodesMutex); #endif dyn.linIx=dem->nodes.size(); dem->nodes.push_back(clump); mass+=clump->getData<DemData>().mass; stepMass+=clump->getData<DemData>().mass; } else { auto& p=pee[0].par; p->mask=mask; assert(p->shape); #ifdef WOO_OPENGL if(color_>=0) p->shape->color=color_; #endif assert(p->shape->nodes.size()==1); // if this fails, enable the block below const auto& node0=p->shape->nodes[0]; assert(node0->pos==Vector3r::Zero()); node0->pos+=pos; auto& dyn=node0->getData<DemData>(); if(shooter) (*shooter)(node0); if(scene->trackEnergy) scene->energy->add(-DemData::getEk_any(node0,true,true,scene),"kinInlet",kinEnergyIx,EnergyTracker::ZeroDontCreate); mass+=dyn.mass; stepMass+=dyn.mass; assert(node0->hasData<DemData>()); dem->particles->insert(p); #ifdef WOO_OPENGL boost::mutex::scoped_lock lock(dem->nodesMutex); #endif dyn.linIx=dem->nodes.size(); dem->nodes.push_back(node0); // handle multi-nodal particle (unused now) #if 0 // TODO: track energy of the shooter // in case the particle is multinodal, apply the same to all nodes if(shooter) shooter(p.shape->nodes[0]); const Vector3r& vel(p->shape->nodes[0]->getData<DemData>().vel); const Vector3r& angVel(p->shape->nodes[0]->getData<DemData>().angVel); for(const auto& n: p.shape->nodes){ auto& dyn=n->getData<DemData>(); dyn.vel=vel; dyn.angVel=angVel; mass+=dyn.mass; n->linIx=dem->nodes.size(); dem->nodes.push_back(n); } #endif } }; stepDone: setCurrRate(stepMass/(nSteps*scene->dt)); }