void Gl1_Membrane::drawLocalDisplacement(const Vector2r& nodePt, const Vector2r& xy, const shared_ptr<ScalarRange>& range, bool split, char arrow, int lineWd, const Real z){ Vector3r nodePt3(nodePt[0],nodePt[1],0); if(split){ Vector3r p1=Vector3r(nodePt[0]+xy[0],nodePt[1],0), c1=range->color(xy[0]), p2=Vector3r(nodePt[0],nodePt[1]+xy[1],0), c2=range->color(xy[1]); if(arrow==0){ glLineWidth(lineWd); GLUtils::GLDrawLine(nodePt3,p1,c1); GLUtils::GLDrawLine(nodePt3,p2,c2); } else { // this messes OpenGL coords up!?? GLUtils::GLDrawArrow(nodePt3,p1,c1,/*doubled*/arrow>1); GLUtils::GLDrawArrow(nodePt3,p2,c2,/*doubled*/arrow>1); } } else { Vector3r p1=Vector3r(nodePt[0]+xy[0],nodePt[1]+xy[1],0), c1=range->color(xy.norm()); if(arrow==0){ glLineWidth(lineWd); GLUtils::GLDrawLine(nodePt3,p1,c1); } else { // this messes OpenGL coords up!?? GLUtils::GLDrawArrow(nodePt3,p1,c1,/*doubled*/arrow>1); } } if(!isnan(z)){ glLineWidth(lineWd); GLUtils::GLDrawLine(nodePt3,Vector3r(nodePt[0],nodePt[1],z),range->color(z)); } }
py::object ParticleGenerator::pyPsd(bool mass, bool cumulative, bool normalize, const Vector2r& dRange, const Vector2r& tRange, int num) const { if(!save) throw std::runtime_error("ParticleGenerator.save must be True for calling ParticleGenerator.psd()"); auto tOk=[&tRange](const Real& t){ return isnan(tRange.minCoeff()) || (tRange[0]<=t && t<tRange[1]); }; vector<Vector2r> psd=DemFuncs::psd(genDiamMassTime,/*cumulative*/cumulative,/*normalize*/normalize,num,dRange, /*diameter getter*/[&tOk](const Vector3r& dmt) ->Real { return tOk(dmt[2])?dmt[0]:NaN; }, /*weight getter*/[&](const Vector3r& dmt) -> Real{ return mass?dmt[1]:1.; } );// return DemFuncs::seqVectorToPy(psd,[](const Vector2r& i)->Vector2r{ return i; },/*zip*/false); }
py::object Outlet::pyPsd(bool _mass, bool cumulative, bool normalize, int _num, const Vector2r& dRange, const Vector2r& tRange, bool zip, bool emptyOk, const py::list& locs__){ py::extract<vector<int>> ll(locs__); if(!ll.check()) throw std::runtime_error("Outlet.psd: locs must be a list of ints."); vector<int> locs_vec=ll(); std::set<int> locs_set; for(auto& i: locs_vec) locs_set.insert(i); if(!save) throw std::runtime_error("Outlet.psd(): Outlet.save must be True."); auto tOk=[&tRange](const Real& t){ return isnan(tRange.minCoeff()) || (tRange[0]<=t && t<tRange[1]); }; auto lOk=[&locs_set,this](const size_t& i){ return locs_set.empty() || (i<locs.size() && locs_set.count(locs[i])>0); }; vector<Vector2r> psd=DemFuncs::psd(diamMassTime,cumulative,normalize,_num,dRange, /*diameter getter*/[&tOk,&lOk](const Vector3r& dmt, const size_t& i)->Real{ return (tOk(dmt[2]) && lOk(i))?dmt[0]:NaN; }, /*weight getter*/[&_mass](const Vector3r& dmt, const size_t& i)->Real{ return _mass?dmt[1]:1.; }, /*emptyOk*/ emptyOk ); return DemFuncs::seqVectorToPy(psd,[](const Vector2r& i)->Vector2r{ return i; },/*zip*/zip); }
bool Law2_L6Geom_PelletPhys_Pellet::go(const shared_ptr<CGeom>& cg, const shared_ptr<CPhys>& cp, const shared_ptr<Contact>& C){ const L6Geom& g(cg->cast<L6Geom>()); PelletPhys& ph(cp->cast<PelletPhys>()); Real& Fn(ph.force[0]); Eigen::Map<Vector2r> Ft(&ph.force[1]); if(C->isFresh(scene)) C->data=make_shared<PelletCData>(); Real& uNPl(C->data->cast<PelletCData>().uNPl); Real& uN0(C->data->cast<PelletCData>().uN0); assert(C->data && dynamic_pointer_cast<PelletCData>(C->data)); if(iniEqlb && C->isFresh(scene)) uN0=g.uN; Real uN=g.uN-uN0; // normal displacement, taking iniEqlb in account // break contact if(uN>0) return false; Real d0=g.lens.sum(); if(ph.normPlastCoeff<=0) uNPl=0.; const Vector2r velT(g.vel[1],g.vel[2]); ph.torque=Vector3r::Zero(); // normal force Fn=ph.kn*(uN-uNPl); // trial force if(ph.normPlastCoeff>0){ // normal plasticity activated if(Fn>0){ if(ph.ka<=0) Fn=0; else{ Fn=min(Fn,adhesionForce(uN,uNPl,ph.ka)); assert(Fn>0); } } else { Real Fy=yieldForce(uN,d0,ph.kn,ph.normPlastCoeff); // normal plastic slip if(Fn<Fy){ Real uNPl0=uNPl; // needed when tracking energy uNPl=uN-Fy/ph.kn; if(unlikely(scene->trackEnergy)){ // backwards trapezoid integration Real Fy0=Fy+yieldForceDerivative(uN,d0,ph.kn,ph.normPlastCoeff)*(uNPl0-uNPl); Real dissip=.5*abs(Fy0+Fy)*abs(uNPl-uNPl0); scene->energy->add(dissip,plastSplit?"normPlast":"plast",plastSplit?normPlastIx:plastIx,EnergyTracker::IsIncrement | EnergyTracker::ZeroDontCreate); tryAddDissipState(DISSIP_NORM_PLAST,dissip,C); } if(thinRate>0 && thinRelRMin<1.){ const Vector2r bendVel(g.angVel[1],g.angVel[2]); Real dRad_0=thinRate*(uNPl0-uNPl)*(scene->dt*bendVel.norm()); for(const Particle* p:{C->leakPA(),C->leakPB()}){ if(!dynamic_cast<Sphere*>(p->shape.get())) continue; Real dRad=dRad_0; // copy to be modified auto& s=p->shape->cast<Sphere>(); //Real r0=(C->geom->cast<L6Geom>().lens[p.get()==C->pA.get()?0:1]); Real r0=cbrt(3*s.nodes[0]->getData<DemData>().mass/(4*M_PI*p->material->density)); Real rMin=r0*thinRelRMin; if(thinRefRad>0.) rMin*=pow(r0/thinRefRad,thinMinExp); if(s.radius<=rMin) continue; // 0..1 norm between rMin and r0 Real r01=(s.radius-rMin)/(r0-rMin); if(thinExp>0) dRad*=pow(r01,thinExp); if(thinRefRad>0.) dRad*=pow(r0/thinRefRad,thinRateExp); boost::mutex::scoped_lock lock(s.nodes[0]->getData<DemData>().lock); // cerr<<"#"<<p->id<<": radius "<<s.radius<<" -> "<<s.radius-dRad<<endl; s.radius=max(rMin,s.radius-dRad*r01); s.color=CompUtils::clamped(1-(s.radius-rMin)/(r0-rMin),0,1); } } Fn=Fy; } // in the elastic regime, Fn is trial force already } } /* add fake confinement */ if(confSigma!=0) Fn-=g.contA*confSigma*(confRefRad>0.?pow(g.contA/(M_PI*pow(confRefRad,2)),confExp):1.); // shear force Ft+=scene->dt*ph.kt*velT; Real maxFt=abs(Fn)*ph.tanPhi; assert(maxFt>=0); // shear plastic slip if(Ft.squaredNorm()>pow(maxFt,2)){ Real FtNorm=Ft.norm(); Real ratio=maxFt/FtNorm; if(unlikely(scene->trackEnergy)){ Real dissip=(.5*(FtNorm-maxFt)+maxFt)*(FtNorm-maxFt)/ph.kt; scene->energy->add(dissip,"plast",plastIx,EnergyTracker::IsIncrement | EnergyTracker::ZeroDontCreate); tryAddDissipState(DISSIP_SHEAR_PLAST,dissip,C); } Ft*=ratio; } assert(!isnan(Fn)); assert(!isnan(Ft[0]) && !isnan(Ft[1])); // elastic potential energy if(unlikely(scene->trackEnergy)) scene->energy->add(0.5*(pow(Fn,2)/ph.kn+Ft.squaredNorm()/ph.kt),"elast",elastPotIx,EnergyTracker::IsResettable); return true; }
bool Law2_L6Geom_HertzPhys_DMT::go(const shared_ptr<CGeom>& cg, const shared_ptr<CPhys>& cp, const shared_ptr<Contact>& C){ const L6Geom& g(cg->cast<L6Geom>()); HertzPhys& ph(cp->cast<HertzPhys>()); Real& Fn(ph.force[0]); Eigen::Map<Vector2r> Ft(&ph.force[1]); ph.torque=Vector3r::Zero(); const Real& dt(scene->dt); const Real& velN(g.vel[0]); const Vector2r velT(g.vel[1],g.vel[2]); // current normal stiffness Real kn0=ph.K*sqrt(ph.R); // TODO: this not really valid for Schwarz? ph.kn=(3/2.)*kn0*sqrt(-g.uN); // normal elastic and adhesion forces // those are only split in the DMT model, Fna is zero for Schwarz or Hertz Real Fne, Fna=0.; if(ph.alpha==0.){ if(g.uN>0){ // TODO: track nonzero energy of broken contact with adhesion // TODO: take residual shear force in account? // if(unlikely(scene->trackEnergy)) scene->energy->add(normalElasticEnergy(ph.kn0,0),"dmtComeGo",dmtIx,EnergyTracker::IsIncrement|EnergyTracker::ZeroDontCreate); return false; } // pure Hertz/DMT Fne=-kn0*pow_i_2(-g.uN,3); // elastic force // DMT adhesion ("sticking") force // See Dejaguin1975 ("Effect of Contact Deformation on the Adhesion of Particles"), eq (44) // Derjaguin has Fs=2πRφ(ε), which is derived for sticky sphere (with surface energy) // in contact with a rigid plane (without surface energy); therefore the value here is twice that of Derjaguin // See also Chiara's thesis, pg 39 eq (3.19). Fna=4*M_PI*ph.R*ph.gamma; } else { // Schwarz model // new contacts with adhesion add energy to the system, which is then taken away again if(unlikely(scene->trackEnergy) && C->isFresh(scene)){ // TODO: scene->energy->add(???,"dmtComeGo",dmtIx,EnergyTracker::IsIncrement) } const Real& gamma(ph.gamma); const Real& R(ph.R); const Real& alpha(ph.alpha); const Real& K(ph.K); Real delta=-g.uN; // inverse convention Real Pc=-6*M_PI*R*gamma/(pow(alpha,2)+3); Real xi=sqrt(((2*M_PI*gamma)/(3*K))*(1-3/(pow(alpha,2)+3))); Real deltaMin=-3*cbrt(R*pow(xi,4)); // -3R(-1/3)*ξ^(-4/3) // broken contact if(delta<deltaMin){ // TODO: track energy return false; } // solution brackets // XXX: a is for sure also greater than delta(a) for Hertz model, with delta>0 // this should be combined with aMin which gives the function apex Real aMin=pow_i_3(xi*R,2); // (ξR)^(2/3) Real a0=pow_i_3(4,2)*aMin; // (4ξR)^(2/3)=4^(2/3) (ξR)^(2/3) Real aLo=(delta<0?aMin:a0); Real aHi=aMin+sqrt(R*(delta-deltaMin)); auto delta_diff_ddiff=[&](const Real& a){ Real aInvSqrt=1/sqrt(a); return boost::math::make_tuple( pow(a,2)/R-4*xi*sqrt(a)-delta, // subtract delta as we need f(x)=0 2*a/R-2*xi*aInvSqrt, 2/R+xi*pow(aInvSqrt,3) ); }; // use a0 (defined as δ(a0)=0) as intial guess for new contacts, since they are likely close to the equilibrium condition // use the previous value for old contacts Real aInit=(C->isFresh(scene)?a0:ph.contRad); boost::uintmax_t iter=100; Real a=boost::math::tools::halley_iterate(delta_diff_ddiff,aInit,aLo,aHi,digits,iter); // increment call and iteration count #ifdef WOO_SCHWARZ_COUNTERS nCallsIters.add(0,1); nCallsIters.add(1,iter); #endif ph.contRad=a; Real Pne=pow(sqrt(pow(a,3)*(K/R))-alpha*sqrt(-Pc),2)+Pc; Fne=-Pne; // inverse convention if(isnan(Pne)){ cerr<<"R="<<R<<", K="<<K<<", xi="<<xi<<", alpha="<<alpha<<endl; cerr<<"delta="<<delta<<", deltaMin="<<deltaMin<<", aMin="<<aMin<<", aLo="<<aLo<<", aHi="<<aHi<<", a="<<a<<", iter="<<iter<<", Pne="<<Pne<<"; \n\n"; abort(); } } // viscous coefficient, both for normal and tangential force // Antypov2012 (10) // XXX: max(-g.uN,0.) for adhesive models so that eta is not NaN Real eta=(ph.alpha_sqrtMK>0?ph.alpha_sqrtMK*pow_1_4(max(-g.uN,0.)):0.); // cerr<<"eta="<<eta<<", -g.uN="<<-g.uN<<"; "; Real Fnv=eta*velN; // viscous force // DMT ONLY (for now at least): if(ph.alpha==0. && noAttraction && Fne+Fnv>0) Fnv=-Fne; // avoid viscosity which would induce attraction with DMT // total normal force Fn=Fne+Fna+Fnv; // normal viscous dissipation if(unlikely(scene->trackEnergy)) scene->energy->add(Fnv*velN*dt,"viscN",viscNIx,EnergyTracker::IsIncrement|EnergyTracker::ZeroDontCreate); // shear sense; zero shear stiffness in tension (XXX: should be different with adhesion) ph.kt=ph.kt0*sqrt(g.uN<0?-g.uN:0); Ft+=dt*ph.kt*velT; // sliding: take adhesion in account Real maxFt=std::abs(min(0.,Fn)*ph.tanPhi); if(Ft.squaredNorm()>pow(maxFt,2)){ // sliding Real FtNorm=Ft.norm(); Real ratio=maxFt/FtNorm; // sliding dissipation if(unlikely(scene->trackEnergy)) scene->energy->add((.5*(FtNorm-maxFt)+maxFt)*(FtNorm-maxFt)/ph.kt,"plast",plastIx,EnergyTracker::IsIncrement | EnergyTracker::ZeroDontCreate); // cerr<<"uN="<<g.uN<<",Fn="<<Fn<<",|Ft|="<<Ft.norm()<<",maxFt="<<maxFt<<",ratio="<<ratio<<",Ft2="<<(Ft*ratio).transpose()<<endl; Ft*=ratio; } else { // viscous tangent force (only applied in the absence of sliding) Ft+=eta*velT; if(unlikely(scene->trackEnergy)) scene->energy->add(eta*velT.dot(velT)*dt,"viscT",viscTIx,EnergyTracker::IsIncrement|EnergyTracker::ZeroDontCreate); } assert(!isnan(Fn)); assert(!isnan(Ft[0]) && !isnan(Ft[1])); // elastic potential energy if(unlikely(scene->trackEnergy)){ // XXX: this is incorrect with adhesion // skip if in tension, since we would get NaN from delta^(2/5) if(g.uN<0) scene->energy->add(normalElasticEnergy(kn0,-g.uN)+0.5*Ft.squaredNorm()/ph.kt,"elast",elastPotIx,EnergyTracker::IsResettable); } LOG_DEBUG("uN="<<g.uN<<", Fn="<<Fn<<"; duT/dt="<<velT[0]<<","<<velT[1]<<", Ft="<<Ft[0]<<","<<Ft[1]); return true; }