yade-dev team mailing list archive
-
yade-dev team
-
Mailing list archive
-
Message #02293
[Branch ~yade-dev/yade/trunk] Rev 1809: Adaptation of viscoelastic model classes and some others to new material/state classes:
------------------------------------------------------------
revno: 1809
committer: Sergei D. <sega@think>
branch nick: trunk
timestamp: Sun 2009-11-22 18:37:49 +0300
message:
Adaptation of viscoelastic model classes and some others to new material/state classes:
1. Add a meta package ViscoelasticPM, which now contains all classes for viscoelastic model.
2. Fix STLImporter, getViscoelasticFromSpheresInteraction
3. Fix bug in utils.sphere, utils._commonBodySetup
added:
pkg/dem/meta/ViscoelasticPM.cpp
pkg/dem/meta/ViscoelasticPM.hpp
modified:
doc/Doxyfile
pkg/common/Engine/DeusExMachina/RotationEngine.cpp
pkg/dem/Engine/EngineUnit/InteractingFacet2InteractingSphere4SpheresContactGeometry.cpp
pkg/dem/PreProcessor/STLImporter.cpp
pkg/dem/PreProcessor/STLImporter.hpp
pkg/dem/meta/Shop.cpp
pkg/dem/meta/Shop.hpp
py/_utils.cpp
py/utils.py
py/yadeWrapper/yadeWrapper.cpp
--
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/Doxyfile'
--- doc/Doxyfile 2009-08-21 10:06:00 +0000
+++ doc/Doxyfile 2009-11-22 15:37:49 +0000
@@ -1368,7 +1368,7 @@
# fallback. It is recommended to install and use dot, since it yields more
# powerful graphs.
-CLASS_DIAGRAMS = NO
+CLASS_DIAGRAMS = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. Doxygen will then run the mscgen tool (see
=== modified file 'pkg/common/Engine/DeusExMachina/RotationEngine.cpp'
--- pkg/common/Engine/DeusExMachina/RotationEngine.cpp 2009-11-21 16:46:58 +0000
+++ pkg/common/Engine/DeusExMachina/RotationEngine.cpp 2009-11-22 15:37:49 +0000
@@ -89,6 +89,3 @@
}
-
-YADE_REQUIRE_FEATURE(PHYSPAR);
-
=== modified file 'pkg/dem/Engine/EngineUnit/InteractingFacet2InteractingSphere4SpheresContactGeometry.cpp'
--- pkg/dem/Engine/EngineUnit/InteractingFacet2InteractingSphere4SpheresContactGeometry.cpp 2009-11-21 10:36:08 +0000
+++ pkg/dem/Engine/EngineUnit/InteractingFacet2InteractingSphere4SpheresContactGeometry.cpp 2009-11-22 15:37:49 +0000
@@ -130,5 +130,5 @@
YADE_PLUGIN((InteractingFacet2InteractingSphere4SpheresContactGeometry));
-YADE_REQUIRE_FEATURE(PHYSPAR);
+//YADE_REQUIRE_FEATURE(PHYSPAR);
=== modified file 'pkg/dem/PreProcessor/STLImporter.cpp'
--- pkg/dem/PreProcessor/STLImporter.cpp 2009-11-21 16:46:58 +0000
+++ pkg/dem/PreProcessor/STLImporter.cpp 2009-11-22 15:37:49 +0000
@@ -11,10 +11,9 @@
CREATE_LOGGER(STLImporter);
-STLImporter::STLImporter()
+STLImporter::STLImporter(): number_of_facets(0)
{
- wire=false;
- number_of_facets=0;
+
}
bool STLImporter::open(const char* filename)
@@ -37,9 +36,8 @@
return true;
}
-void STLImporter::import(shared_ptr<BodyContainer> bodies, unsigned int begin, bool noInteractingGeometry)
+void STLImporter::import(shared_ptr<BodyContainer> bodies)
{
- unsigned int b_id = begin;
for(int i=0,e=tr.size(); i<e; i+=3)
{
Vector3r v[3]={tr[i],tr[i+1],tr[i+2]};
@@ -64,11 +62,13 @@
gFacet->vertices.push_back(v[j]-icc);
#endif
}
-
- (*bodies)[b_id]->state->pos=icc;
- (*bodies)[b_id]->state->ori=Quaternionr::IDENTITY;
- if (!noInteractingGeometry) (*bodies)[b_id]->interactingGeometry = iFacet;
-
- ++b_id;
+ //iFacet->postProcessAttributes(true);
+ //postProcessAttributes is protected
+
+ shared_ptr<Body> b(new Body());
+ b->state->pos=icc;
+ b->state->ori=Quaternionr::IDENTITY;
+ b->interactingGeometry = iFacet;
+ bodies->insert(b);
}
}
=== modified file 'pkg/dem/PreProcessor/STLImporter.hpp'
--- pkg/dem/PreProcessor/STLImporter.hpp 2009-11-17 12:26:35 +0000
+++ pkg/dem/PreProcessor/STLImporter.hpp 2009-11-22 15:37:49 +0000
@@ -24,11 +24,8 @@
/// number of facets
unsigned int number_of_facets;
- /// set wire attribute for facets (by default false)
- bool wire;
-
/// import geometry
- void import(shared_ptr<BodyContainer> bodies, unsigned int begin=0, bool noInteractingGeometry=false);
+ void import(shared_ptr<BodyContainer> bodies);
DECLARE_LOGGER;
protected:
=== modified file 'pkg/dem/meta/Shop.cpp'
--- pkg/dem/meta/Shop.cpp 2009-11-21 16:46:58 +0000
+++ pkg/dem/meta/Shop.cpp 2009-11-22 15:37:49 +0000
@@ -33,7 +33,7 @@
#include<yade/pkg-dem/InteractingBox2InteractingSphere4SpheresContactGeometry.hpp>
#include<yade/pkg-dem/SimpleElasticRelationships.hpp>
//#include<yade/pkg-dem/SimpleViscoelasticBodyParameters.hpp>
-
+#include<yade/pkg-dem/ViscoelasticPM.hpp>
/*class InteractingSphere2AABB;
class InteractingBox2AABB;
class MetaInteractingGeometry;
@@ -1151,6 +1151,17 @@
}
#endif
+void Shop::getViscoelasticFromSpheresInteraction( Real m, Real tc, Real en, Real es, shared_ptr<SimpleViscoelasticMat> b)
+{
+ b->kn = m/tc/tc * ( Mathr::PI*Mathr::PI + Mathr::Pow(Mathr::Log(en),2) );
+ b->cn = -2.0*m/tc * Mathr::Log(en);
+ b->ks = 2.0/7.0 * m/tc/tc * ( Mathr::PI*Mathr::PI + Mathr::Pow(Mathr::Log(es),2) );
+ b->cs = -2.0/7.0 * m/tc * Mathr::Log(es);
+
+ if (Math<Real>::FAbs(b->cn) <= Math<Real>::ZERO_TOLERANCE ) b->cn=0;
+ if (Math<Real>::FAbs(b->cs) <= Math<Real>::ZERO_TOLERANCE ) b->cs=0;
+}
+
/* This function is copied almost verbatim from scientific python, module Visualization, class ColorScale
*
*/
=== modified file 'pkg/dem/meta/Shop.hpp'
--- pkg/dem/meta/Shop.hpp 2009-11-21 16:46:58 +0000
+++ pkg/dem/meta/Shop.hpp 2009-11-22 15:37:49 +0000
@@ -25,6 +25,7 @@
class MetaBody;
class Body;
class SimpleViscoelasticBodyParameters;
+class SimpleViscoelasticMat;
class GranularMat;
using namespace std;
@@ -99,6 +100,7 @@
/// time tc and restitution coefficients en,es.
static void getViscoelasticFromSpheresInteraction(Real m, Real tc, Real en, Real es, shared_ptr<SimpleViscoelasticBodyParameters> b);
#endif
+ static void getViscoelasticFromSpheresInteraction(Real m, Real tc, Real en, Real es, shared_ptr<SimpleViscoelasticMat> b);
//! Get unbalanced force of the whole simulation
static Real unbalancedForce(bool useMaxForce=false, MetaBody* _rb=NULL);
=== added file 'pkg/dem/meta/ViscoelasticPM.cpp'
--- pkg/dem/meta/ViscoelasticPM.cpp 1970-01-01 00:00:00 +0000
+++ pkg/dem/meta/ViscoelasticPM.cpp 2009-11-22 15:37:49 +0000
@@ -0,0 +1,95 @@
+// 2009 © Sergei Dorofeenko <sega@xxxxxxxxxxxxxxxx>
+#include"ViscoelasticPM.hpp"
+#include<yade/core/State.hpp>
+#include<yade/pkg-dem/SpheresContactGeometry.hpp>
+#include<yade/core/Omega.hpp>
+#include<yade/core/MetaBody.hpp>
+
+YADE_PLUGIN((SimpleViscoelasticMat)(SimpleViscoelasticPhys)(Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys)(Law2_Spheres_Viscoelastic_SimpleViscoelastic));
+
+/* SimpleViscoelasticMat */
+SimpleViscoelasticMat::~SimpleViscoelasticMat(){}
+
+/* SimpleViscoelasticPhys */
+SimpleViscoelasticPhys::~SimpleViscoelasticPhys(){}
+
+/* Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys */
+void Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys::go(const shared_ptr<Material>& b1, const shared_ptr<Material>& b2, const shared_ptr<Interaction>& interaction) {
+ if(interaction->interactionPhysics) return;
+ SimpleViscoelasticMat* sdec1 = static_cast<SimpleViscoelasticMat*>(b1.get());
+ SimpleViscoelasticMat* sdec2 = static_cast<SimpleViscoelasticMat*>(b2.get());
+ SimpleViscoelasticPhys* phys = new SimpleViscoelasticPhys();
+ phys->kn = sdec1->kn * sdec2->kn / (sdec1->kn + sdec2->kn);
+ phys->ks = sdec1->ks * sdec2->ks / (sdec1->ks + sdec2->ks);
+ phys->cn = ( (sdec1->cn==0) ? 0 : 1/sdec1->cn ) + ( (sdec2->cn==0) ? 0 : 1/sdec2->cn );
+ phys->cs = ( (sdec1->cs==0) ? 0 : 1/sdec1->cs ) + ( (sdec2->cs==0) ? 0 : 1/sdec2->cs );
+ if (phys->cn) phys->cn = 1/phys->cn;
+ if (phys->cs) phys->cs = 1/phys->cs;
+ phys->tangensOfFrictionAngle = std::tan(std::min(sdec1->frictionAngle, sdec2->frictionAngle));
+ phys->shearForce = Vector3r(0,0,0);
+ phys->prevNormal = Vector3r(0,0,0);
+ interaction->interactionPhysics = shared_ptr<SimpleViscoelasticPhys>(phys);
+}
+
+/* Law2_Spheres_Viscoelastic_SimpleViscoelastic */
+void Law2_Spheres_Viscoelastic_SimpleViscoelastic::go(shared_ptr<InteractionGeometry>& _geom, shared_ptr<InteractionPhysics>& _phys, Interaction* I, MetaBody* rootBody){
+
+ const SpheresContactGeometry& geom=*static_cast<SpheresContactGeometry*>(_geom.get());
+ SimpleViscoelasticPhys& phys=*static_cast<SimpleViscoelasticPhys*>(_phys.get());
+
+ int id1 = I->getId1();
+ int id2 = I->getId2();
+
+ if (geom.penetrationDepth<0) {
+ rootBody->interactions->requestErase(id1,id2);
+ return;
+ }
+
+ const BodyContainer& bodies = *rootBody->bodies;
+
+ const State& de1 = *static_cast<State*>(bodies[id1]->state.get());
+ const State& de2 = *static_cast<State*>(bodies[id2]->state.get());
+
+ Vector3r& shearForce = phys.shearForce;
+
+ if (I->isFresh(rootBody)) shearForce=Vector3r(0,0,0);
+
+ Real dt = Omega::instance().getTimeStep();
+
+ Vector3r axis = phys.prevNormal.Cross(geom.normal);
+ shearForce -= shearForce.Cross(axis);
+ Real angle = dt*0.5*geom.normal.Dot(de1.angVel + de2.angVel);
+ axis = angle*geom.normal;
+ shearForce -= shearForce.Cross(axis);
+
+ Vector3r c1x = (geom.contactPoint - de1.pos);
+ Vector3r c2x = (geom.contactPoint - de2.pos);
+ /// The following definition of c1x and c2x is to avoid "granular ratcheting"
+ /// (see F. ALONSO-MARROQUIN, R. GARCIA-ROJO, H.J. HERRMANN,
+ /// "Micro-mechanical investigation of granular ratcheting, in Cyclic Behaviour of Soils and Liquefaction Phenomena",
+ /// ed. T. Triantafyllidis (Balklema, London, 2004), p. 3-10 - and a lot more papers from the same authors)
+ //Vector3r _c1x_ = geom->radius1*geom->normal;
+ //Vector3r _c2x_ = -geom->radius2*geom->normal;
+ //Vector3r relativeVelocity = (de2->velocity+de2->angularVelocity.Cross(_c2x_)) - (de1->velocity+de1->angularVelocity.Cross(_c1x_));
+ Vector3r relativeVelocity = (de2.vel+de2.angVel.Cross(c2x)) - (de1.vel+de1.angVel.Cross(c1x));
+ Real normalVelocity = geom.normal.Dot(relativeVelocity);
+ Vector3r shearVelocity = relativeVelocity-normalVelocity*geom.normal;
+ shearForce -= (phys.ks*dt+phys.cs)*shearVelocity;
+
+ phys.normalForce = ( phys.kn * geom.penetrationDepth - phys.cn * normalVelocity ) * geom.normal;
+ phys.prevNormal = geom.normal;
+
+ Real maxFs = phys.normalForce.SquaredLength() * std::pow(phys.tangensOfFrictionAngle,2);
+ if( shearForce.SquaredLength() > maxFs )
+ {
+ maxFs = Mathr::Sqrt(maxFs) / shearForce.Length();
+ shearForce *= maxFs;
+ }
+
+ Vector3r f = phys.normalForce + shearForce;
+ addForce (id1,-f,rootBody);
+ addForce (id2, f,rootBody);
+ addTorque(id1,-c1x.Cross(f),rootBody);
+ addTorque(id2, c2x.Cross(f),rootBody);
+}
+
=== added file 'pkg/dem/meta/ViscoelasticPM.hpp'
--- pkg/dem/meta/ViscoelasticPM.hpp 1970-01-01 00:00:00 +0000
+++ pkg/dem/meta/ViscoelasticPM.hpp 2009-11-22 15:37:49 +0000
@@ -0,0 +1,76 @@
+// 2009 © Sergei Dorofeenko <sega@xxxxxxxxxxxxxxxx>
+// This file contains a set of classes for modelling of viscoelastic
+// particles.
+
+#pragma once
+
+#include<yade/core/Material.hpp>
+#include<yade/pkg-dem/ElasticContactInteraction.hpp>
+#include<yade/pkg-common/InteractionPhysicsEngineUnit.hpp>
+#include<yade/pkg-common/ConstitutiveLaw.hpp>
+
+/* Simple viscoelastic model */
+
+/// Material
+/// Note: Shop::getViscoelasticFromSpheresInteraction can get kn,cn,ks,cs from a analytical solution of a pair spheres interaction problem.
+class SimpleViscoelasticMat : public Material {
+ public :
+ /// Normal elasticity
+ Real kn;
+ /// Normal viscosity
+ Real cn;
+ /// Shear elasticity
+ Real ks;
+ /// Shear viscosity
+ Real cs;
+ /// Friction angle
+ Real frictionAngle;
+ SimpleViscoelasticMat(){ createIndex(); }
+ virtual ~SimpleViscoelasticMat();
+ REGISTER_ATTRIBUTES(Material,(kn)(ks)(cn)(cs)(frictionAngle));
+ REGISTER_CLASS_AND_BASE(SimpleViscoelasticMat,Material);
+ REGISTER_CLASS_INDEX(SimpleViscoelasticMat,Material);
+};
+REGISTER_SERIALIZABLE(SimpleViscoelasticMat);
+
+/// Interaction physics
+class SimpleViscoelasticPhys : public ElasticContactInteraction {
+ public :
+ /// Normal viscous
+ Real cn;
+ /// Shear viscous
+ Real cs;
+ SimpleViscoelasticPhys(){ createIndex(); }
+ virtual ~SimpleViscoelasticPhys();
+ protected :
+ REGISTER_ATTRIBUTES(ElasticContactInteraction,(cn)(cs));
+ REGISTER_CLASS_AND_BASE(SimpleViscoelasticPhys,ElasticContactInteraction);
+ REGISTER_CLASS_INDEX(SimpleViscoelasticPhys,ElasticContactInteraction);
+};
+REGISTER_SERIALIZABLE(SimpleViscoelasticPhys);
+
+/// Convert material to interaction physics.
+// Uses the rule of consecutively connection.
+class Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys: public InteractionPhysicsEngineUnit {
+ public :
+ virtual void go(const shared_ptr<Material>& b1,
+ const shared_ptr<Material>& b2,
+ const shared_ptr<Interaction>& interaction);
+ REGISTER_ATTRIBUTES(InteractionPhysicsEngineUnit,/* */)
+ FUNCTOR2D(SimpleViscoelasticMat,SimpleViscoelasticMat);
+ REGISTER_CLASS_AND_BASE(Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys, InteractionPhysicsEngineUnit);
+
+};
+REGISTER_SERIALIZABLE(Ip2_SimleViscoelasticMat_SimpleViscoelasticMat_SimpleViscoelasticPhys);
+
+/// Constitutive law
+/// This class provides linear viscoelastic contact model
+class Law2_Spheres_Viscoelastic_SimpleViscoelastic: public ConstitutiveLaw {
+ public :
+ virtual void go(shared_ptr<InteractionGeometry>&, shared_ptr<InteractionPhysics>&, Interaction*, MetaBody*);
+ FUNCTOR2D(SpheresContactGeometry,SimpleViscoelasticPhys);
+ REGISTER_CLASS_AND_BASE(Law2_Spheres_Viscoelastic_SimpleViscoelastic,ConstitutiveLaw);
+ REGISTER_ATTRIBUTES(ConstitutiveLaw,/* */);
+};
+REGISTER_SERIALIZABLE(Law2_Spheres_Viscoelastic_SimpleViscoelastic);
+
=== modified file 'py/_utils.cpp'
--- py/_utils.cpp 2009-11-21 16:46:58 +0000
+++ py/_utils.cpp 2009-11-22 15:37:49 +0000
@@ -10,6 +10,7 @@
#include<yade/pkg-common/NormalShearInteractions.hpp>
#include<yade/lib-computational-geometry/Hull2d.hpp>
#include<cmath>
+#include<yade/pkg-dem/ViscoelasticPM.hpp>
#include<numpy/ndarrayobject.h>
@@ -183,6 +184,17 @@
return d;
}
#endif
+python::dict getViscoelasticFromSpheresInteraction(Real m, Real tc, Real en, Real es)
+{
+ shared_ptr<SimpleViscoelasticMat> b = shared_ptr<SimpleViscoelasticMat>(new SimpleViscoelasticMat());
+ Shop::getViscoelasticFromSpheresInteraction(m,tc,en,es,b);
+ python::dict d;
+ d["kn"]=b->kn;
+ d["cn"]=b->cn;
+ d["ks"]=b->ks;
+ d["cs"]=b->cs;
+ return d;
+}
/* reset highlight of all bodies */
void highlightNone(){
FOREACH(const shared_ptr<Body>& b, *Omega::instance().getRootBody()->bodies){
@@ -428,6 +440,7 @@
#ifdef YADE_PHYSPAR
def("getViscoelasticFromSpheresInteraction",getViscoelasticFromSpheresInteraction);
#endif
+ def("getViscoelasticFromSpheresInteraction",getViscoelasticFromSpheresInteraction);
def("unbalancedForce",&Shop::unbalancedForce,unbalancedForce_overloads(args("useMaxForce")));
def("kineticEnergy",Shop__kineticEnergy);
def("sumBexForces",sumBexForces);
=== modified file 'py/utils.py'
--- py/utils.py 2009-11-21 23:50:06 +0000
+++ py/utils.py 2009-11-22 15:37:49 +0000
@@ -79,10 +79,10 @@
b.mat=Material(materialClass)
b.mat.updateExistingAttrs(matKw2)
mass=volume*b.mat['density']
- b.state['mass'],b.state['inertia']=mass,geomInertia*mass
+ b.state['mass'],b.state['inertia']=mass,geomInertia*b.mat['density']
if not noBound: b.bound=BoundingVolume('AABB',diffuseColor=[0,1,0])
-def sphere(center,radius,dynamic=True,wire=False,color=None,density=1,highlight=False,**matKw):
+def sphere(center,radius,dynamic=True,wire=False,color=None,highlight=False,**matKw):
"""Create default sphere, with given parameters. Physical properties such as mass and inertia are calculated automatically."""
b=Body()
b.mold=InteractingSphere(radius=radius,diffuseColor=color if color else randomColor(),wire=wire,highlight=highlight)
@@ -300,29 +300,21 @@
pylab.ylabel('Body count')
pylab.show()
-
-def import_stl_geometry(file, young=30e9,poisson=.3,color=[0,1,0],frictionAngle=0.5236,wire=True,noBoundingVolume=False,noInteractingGeometry=False,physParamsClass='BodyMacroParameters',**physParamsAttr):
+def import_stl_geometry(file, dynamic=False,wire=True,color=None,highlight=False,noBoundingVolume=False, **matKw):
""" Import geometry from stl file, create facets and return list of their ids."""
imp = STLImporter()
- imp.wire = wire
imp.open(file)
begin=len(O.bodies)
- for i in xrange(imp.number_of_facets):
- b=Body()
- b['isDynamic']=False
- pp={'se3':[0,0,0,1,0,0,0],'young':young,'poisson':poisson,'frictionAngle':frictionAngle}
- pp.update(physParamsAttr)
- b.phys=PhysicalParameters(physParamsClass)
- b.phys.updateExistingAttrs(pp)
- if not noBoundingVolume:
- b.bound=BoundingVolume('AABB',diffuseColor=[0,1,0])
- O.bodies.append(b)
- imp.import_geometry(O.bodies,begin,noInteractingGeometry)
+ imp.import_geometry(O.bodies)
imported=range(begin,begin+imp.number_of_facets)
for i in imported:
- if not noInteractingGeometry:
- O.bodies[i].mold.postProcessAttributes(True)
- O.bodies[i].mold['diffuseColor']=color
+ b=O.bodies[i]
+ b['isDynamic']=dynamic
+ b.mold.postProcessAttributes(True)
+ b.mold['diffuseColor']=color if color else randomColor()
+ b.mold['wire']=wire
+ b.mold['highlight']=highlight
+ _commonBodySetup(b,0,Vector3(0,0,0),noBound=noBoundingVolume,**matKw)
return imported
=== modified file 'py/yadeWrapper/yadeWrapper.cpp'
--- py/yadeWrapper/yadeWrapper.cpp 2009-11-21 16:46:58 +0000
+++ py/yadeWrapper/yadeWrapper.cpp 2009-11-22 15:37:49 +0000
@@ -508,9 +508,8 @@
class pySTLImporter : public STLImporter {
public:
- void py_import(pyBodyContainer bc, unsigned int begin=0, bool noInteractingGeometry=false) { import(bc.proxee,begin,noInteractingGeometry); }
+ void py_import(pyBodyContainer bc) { import(bc.proxee); }
};
-BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(STLImporter_import_overloads,py_import,1,3);
/*****************************************************************************
@@ -737,8 +736,7 @@
python::class_<pySTLImporter>("STLImporter")
.def("open",&pySTLImporter::open)
.add_property("number_of_facets",&pySTLImporter::number_of_facets)
- .def_readwrite("wire",&pySTLImporter::wire)
- .def("import_geometry",&pySTLImporter::py_import,STLImporter_import_overloads());
+ .def("import_geometry",&pySTLImporter::py_import);
//////////////////////////////////////////////////////////////
///////////// proxyless wrappers