Download this testcase.

Bidimensional shear of a dry granular material

This example simulates a 2D simple shear test of a dry granular material confined between two walls with periodic lateral boundaries. The top and bottom walls move at constant velocities to impose a shear rate. Keywords ——– DEM, Periodic Boundaries, Extra Fields

import os, sys, shutil
import numpy as np
import gmsh
from migflow import scontact, time_integration

np.random.seed(0)

Output Directory

The output directory is (re)created

outputdir = "output" if len(sys.argv) < 2 else sys.argv[1]
shutil.rmtree(outputdir, ignore_errors=True)
os.makedirs(outputdir)

Geometrical parameters and mesh generation

h = 0.1  # domain height
w = 0.2  # domain width
r = 1e-3  # particle radius

Physical Parameters

g = np.array([0.0, 0.0])  # gravity (no gravity)
rhop = 1000  # particle density
shear_rate = 10  # imposed shear rate
Pp = 100  # confining pressure
friction_wall_sand = 1.0  # wall-sand friction coefficient
friction_sand_sand = 0.4  # sand-sand friction coefficient

Particle problem setup

Initialize the particle problem and set up friction coefficients.

p = scontact.ParticleProblem(2)
p.set_fixed_contact_geometry(0)
p.set_friction_coefficient(friction_wall_sand, "wall", "sand")
p.set_friction_coefficient(friction_sand_sand, "sand", "sand")

Periodic Boundaries

Create the periodic entity that store the transformation, then add the periodic segments linked to it.

trans = np.array([w, 0])
ent0 = p.add_boundary_periodic_entity(1, trans)
ent1 = p.add_boundary_periodic_entity(1, -trans)
p.add_boundary_periodic_segment((-w / 2, h), (-w / 2, -h), ent0)
p.add_boundary_periodic_segment((w / 2, h), (w / 2, -h), ent1)

Add top and bottom walls as rigid bodies

x0, x1 = np.array([-w / 2 - w / 20, 0]), np.array([w / 2 + w / 20, 0])
x_segment = np.vstack([x0, x1])
mass_seg = rhop * np.pi * r * w / 2
top_body = p.add_entities(
    np.array([0, h / 2]),
    mass_seg,
    0,
    segments_coordinates=x_segment[None, :, :],
    segments_tags="Top",
    segments_materials="wall",
)
bottom_body = p.add_entities(
    np.array([0, -h / 2]),
    mass_seg,
    0,
    segments_coordinates=x_segment[None, :, :],
    segments_tags="Bottom",
    segments_materials="wall",
)

Particle initialization

Generate hexagonal packing

eps = 1e-4
step = 2 * r + eps
lx, ly = w, h - 4 * r
y0 = -h / 2 + r + eps

x = np.arange(-lx / 2, lx / 2 - 2 * r, step)
y = np.arange(y0, y0 + ly, np.sqrt(3) * step)
x2 = np.arange(-lx / 2 + r, lx / 2 - 2 * r, step)
y2 = y + np.sqrt(3) * step / 2

x, y = np.meshgrid(x, y)
x2, y2 = np.meshgrid(x2, y2)
x = np.concatenate([x.ravel(), x2.ravel()])
y = np.concatenate([y.ravel(), y2.ravel()])

x_off = w - ((x.max() + step / 2) - (x.min() - step / 2))
x += r + x_off / 2
order = np.argsort(y)
x, y = x[order], y[order]

pbodies = np.array([], np.int32)
for xi, yi in zip(x, y):
    ri = np.random.uniform(0.5, 1.0) * r
    body = p.add_particle((xi, yi), ri, np.pi * ri**2 * rhop)
    pbodies = np.append(pbodies, body)

Simulation Parameters

outf = 5
dt = 1e-3
tEnd = 0.5
t = 0
i = 0

Initial Conditions for the walls

v_top = shear_rate * p.body_position()[top_body, 1]
v_bottom = shear_rate * p.body_position()[bottom_body, 1]
p.body_velocity()[top_body, 0] = v_top
p.body_velocity()[bottom_body, 0] = v_bottom
p.body_velocity()[pbodies, 0] = p.body_position()[pbodies, 1] * shear_rate

Computational Loop

def periodic_map(width, bodies):
    """Remap particles crossing periodic boundaries."""
    p.body_position()[bodies, 0] = (
        np.remainder(p.body_position()[bodies, 0] + 3 * width / 2, width) - width / 2
    )


def accumulate(f_contacts, n_divide):
    """Accumulate boundary forces on top and bottom walls."""
    f_contacts[0] += p.get_boundary_forces("Top") / n_divide
    f_contacts[1] += p.get_boundary_forces("Bottom") / n_divide


forces_particles = np.zeros_like(p.velocity())
forces_body = np.zeros_like(p.body_velocity())
forces_body[top_body, 1] = -Pp * w / 2
forces_body[bottom_body, 1] = Pp * w / 2
xold = p.body_position()[:, 0].copy()
displacement = np.zeros_like(p.position())
while t < tEnd:
    print(f"{i:4d}, {t:.6g}/{tEnd:.6g}")
    periodic_map(w, pbodies)
    if i % outf == 0:
        p.write_mig(outputdir, t, {"displacement": displacement})

    p.body_velocity()[top_body, 0] = v_top
    p.body_velocity()[bottom_body, 0] = v_bottom
    f_contacts = np.zeros((2, 2))

    time_integration._advance_particles(
        p,
        forces_particles,
        dt,
        min_nsub=1,
        max_nsub=5,
        contact_tol=1e-3 * r,
        fbody=forces_body,
        after_sub_iter=lambda n_divide: accumulate(f_contacts, n_divide),
    )

    p.body_position()[top_body, 0] = xold[top_body]
    p.body_position()[bottom_body, 0] = xold[bottom_body]
    displacement += p.velocity() * dt
    t += dt
    i += 1

Plot

python3 -m migflow.plot.migplot output --actors particles