PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
problem.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -------------------------------------------
3# Copyright (c) 2021 - 2026 Prashant K. Jha
4# -------------------------------------------
5# PeriDEM https://github.com/prashjha/PeriDEM
6#
7# Distributed under the Boost Software License, Version 1.0. (See accompanying
8# file LICENSE)
9
10"""Compression of a small circular-grain pack, set up in Python.
11
12Parameters from Jha et al., J. Mech. Phys. Solids 151 (2021) 104376, section
134.3: material M1, lc = R/5, horizon = 3 lc, Rc = 0.95 h, C-bar = 100 and plate
14velocity -0.06 m/s. The pack here is 4 by 3 grains and not the 502 of the
15paper. The grain positions, the open channel that contains them and the moving
16plate are derived from R and the pack size, and no deck file is read.
17
18``test/test_data/peridem/jha2021_comp_n50/main.cpp`` builds the same deck in
19C++. ``python/tests/test_example_parity.py`` compares the two decks key by
20key.
21"""
22
23from __future__ import annotations
24
25import os
26import sys
27from pathlib import Path
28
29HERE = Path(__file__).resolve().parent
30for _p in HERE.parents:
31 if (_p / "examples" / "peridem_env.py").is_file():
32 sys.path.insert(0, str(_p / "examples"))
33 break
34import peridem_env # noqa: F401,E402
35
36import peridem # noqa: E402
37from peridem import Deck, Geometry # noqa: E402
38from peridem.deck import MeshSpec, contact_stiffness, to_E, to_G # noqa: E402
39
40R = 0.001
41MESH_SIZE = R / 5.0
42DENSITY = 1200.0
43K = 2.16e7
44NU = 0.25
45GC = 50.0
46WALL_VY = -0.06
47GRAVITY = 10.0
48
49NCOLS, NROWS = 4, 3
50FINAL_TIME = 0.004
51NUM_STEPS = 20000
52SEARCH_INTERVAL = 40
53
54TAGS = ["Displacement", "Velocity", "Force", "Damage_Z", "Damage",
55 "Particle_ID", "Contact_Nodes"]
56
57MESH_FILES = ("mesh_cir.msh", "mesh_fixed_container.msh",
58 "mesh_moving_container.msh")
59
60
61def pack_geometry(ncols: int = NCOLS, nrows: int = NROWS, r: float = R,
62 mesh_size: float = MESH_SIZE) -> dict[str, object]:
63 """Pack spacing and container dimensions, all derived from R and the mesh.
64
65 The gap between grains is slightly larger than the contact radius, so that
66 no pair is in contact at t = 0. Gravity and the plate bring pairs into
67 contact.
68 """
69 horizon = 3.0 * mesh_size
70 h_est = 0.7 * mesh_size # realized hmin on a Gmsh disc is ~0.7 lc
71 rc_est = 0.95 * h_est
72 padding = 1.15 * rc_est
73 rwp = horizon + padding
74 wall_t = rwp - padding
75
76 step = 2.0 * r + padding
77 lin = 2.0 * padding + 2.0 * r + (ncols - 1) * step
78 win = 2.0 * padding + 2.0 * r + (nrows - 1) * step
79
80 sites = [(padding + r + i * step, padding + r + j * step)
81 for j in range(nrows) for i in range(ncols)]
82 return {
83 "horizon": horizon, "padding": padding, "rwp": rwp, "wall_t": wall_t,
84 "Lin": lin, "Win": win, "sites": sites,
85 # The fixed open channel and the plate that compresses the pack.
86 "cup": [-rwp, -rwp, lin + rwp, win + wall_t, wall_t, 0.0],
87 "plate": [-padding, win, 0.0, lin + padding, win + wall_t, 0.0],
88 }
89
90
91def build_deck(output_path: str | os.PathLike[str] = "runs/", *,
92 ncols: int = NCOLS, nrows: int = NROWS,
93 final_time: float = FINAL_TIME, num_steps: int = NUM_STEPS,
94 mesh_size: float = MESH_SIZE,
95 search_interval: int = SEARCH_INTERVAL,
96 mesh_dir: str | os.PathLike[str] | None = None,
97 write_mesh: bool = True,
98 in_process_mesh: bool = False) -> Deck:
99 g = pack_geometry(ncols, nrows, R, mesh_size)
100 horizon = float(g["horizon"])
101 sites = g["sites"]
102 n_pack = len(sites)
103
104 E = to_E(K, NU)
105 G = to_G(E=E, nu=NU)
106 Kn = contact_stiffness(K, K, horizon)
107
108 d = Deck(dim=2, t_final=final_time, n_steps=num_steps)
109 d.set_comment("jha2021_comp_contact")
110
111 dt_out = max(1, num_steps // 4)
112 d.set_output(output_path, tags=TAGS, interval=dt_out, debug=1,
113 dt_test_out=max(1, dt_out // 10), tag_pp="0",
114 pvd_collection=True)
115 d.set_gravity(0.0, -GRAVITY)
116 d.set_neighbor(update_criteria="simple_all", s_factor=5.0, update_interval=search_interval,
117 near_bd_nodes_tol=0.5)
118
119 grain = Geometry("circle", [R, 0.0, 0.0, 0.0])
120 cup = Geometry("open_rect_channel_2d", list(g["cup"]))
121 plate = Geometry("rectangle", list(g["plate"]))
122
123 if in_process_mesh:
124 meshes = [MeshSpec(size=mesh_size) for _ in range(3)]
125 else:
126 base = Path(mesh_dir) if mesh_dir is not None else HERE
127 meshes = [MeshSpec(file=base / name, size=mesh_size, write=write_mesh)
128 for name in MESH_FILES]
129
130 g_grain = d.add_particle_type(grain, meshes[0])
131 g_cup = d.add_particle_type(cup, meshes[1])
132 g_plate = d.add_particle_type(plate, meshes[2])
133
134 m_grain = d.add_material(horizon=horizon, density=DENSITY, K=K, G=G, Gc=GC,
135 influence_fn_type=1)
136 m_wall = d.add_material(horizon=horizon, density=DENSITY, K=K, G=G, Gc=GC,
137 influence_fn_type=1)
138
139 for i, j in ((0, 0), (0, 1), (1, 1)):
140 d.add_contact_pair(i, j, contact_radius_factor=0.95, Kn=Kn, eps=0.95,
141 beta_n_factor=100.0, K=K)
142
143 # Particle ids: 0..n_pack-1 grains, then the cup, then the plate.
144 id_cup, id_plate = n_pack, n_pack + 1
145 d.add_displacement_bc(particles=[id_cup], direction=[1, 2],
146 zero_displacement=True)
147 d.add_displacement_bc(particles=[id_plate], direction=[2],
148 time_fn_type="linear", time_fn_params=[WALL_VY],
149 spatial_fn_type="constant")
150
151 for x, y in sites:
152 d.place(x, y, geometry=g_grain, material=m_grain, contact=0)
153 cx, cy, _ = cup.center
154 d.place(cx, cy, geometry=g_cup, material=m_wall, contact=1, wall=True)
155 px, py, _ = plate.center
156 d.place(px, py, geometry=g_plate, material=m_wall, contact=1, wall=True)
157
158 d.set_test("compressive_test", wall_id=id_plate, wall_force_direction=2)
159 return d
160
161
162def main(argv: list[str] | None = None) -> int:
163 import argparse
164
165 p = argparse.ArgumentParser(description=__doc__)
166 p.add_argument("-o", "--output", default=str(HERE / "runs_py"))
167 p.add_argument("--ncols", type=int, default=NCOLS)
168 p.add_argument("--nrows", type=int, default=NROWS)
169 p.add_argument("--final-time", type=float, default=FINAL_TIME)
170 p.add_argument("--num-steps", type=int, default=NUM_STEPS)
171 p.add_argument("--mesh-size", type=float, default=MESH_SIZE)
172 p.add_argument("--in-process-mesh", action="store_true",
173 help="build the three meshes with Gmsh in memory")
174 p.add_argument("--nthreads", type=int,
175 default=int(os.environ.get("NTHREADS", "4")))
176 p.add_argument("--snapshot", metavar="PNG", default=None)
177 p.add_argument("--write-deck", metavar="PATH", default=None)
178 args = p.parse_args(argv)
179
180 out = Path(args.output).resolve()
181 d = build_deck(str(out) + "/", ncols=args.ncols, nrows=args.nrows,
182 final_time=args.final_time, num_steps=args.num_steps,
183 mesh_size=args.mesh_size,
184 in_process_mesh=args.in_process_mesh,
185 mesh_dir=out.parent / "meshes_py")
186 if args.write_deck:
187 print(d.write(args.write_deck))
188 return 0
189
190 out.mkdir(parents=True, exist_ok=True)
191 (out.parent / "meshes_py").mkdir(parents=True, exist_ok=True)
192 peridem.init(n_threads=args.nthreads)
193 sim = d.run(workdir=HERE)
194 if peridem.mpi_rank() != 0:
195 return 0
196 print(f"done: grains={sim.n_particles} walls={sim.n_walls} "
197 f"nodes={sim.n_nodes} t={sim.time:g}")
198 ys = [p_.center_of_mass[1] for p_ in sim.particles]
199 print(f"grain centroid y: min={min(ys):.6e} max={max(ys):.6e}")
200 if args.snapshot:
201 print(peridem.snapshot(peridem.last_vtu(out), args.snapshot,
202 color="|Displacement|",
203 title="n12 compression: |u|"))
204 print(f"VTU under {out}")
205 return 0
206
207
208if __name__ == "__main__":
209 raise SystemExit(main())
dict[str, object] pack_geometry(int ncols=NCOLS, int nrows=NROWS, float r=R, float mesh_size=MESH_SIZE)
Definition problem.py:62
Deck build_deck(str|os.PathLike[str] output_path="runs/out/", *str preset="short", float|None final_time=None, int|None num_steps=None, int|None output_interval=None, float omega=OMEGA, str|os.PathLike[str] mesh_dir=MESH_DIR, str|os.PathLike[str] csv=CSV)
Definition problem.py:132