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"""Two deformable circles: one fixed, one dropped onto it.
11
12Two grains, one contact pair, gravity and an initial velocity. Both meshes are
13built by Gmsh in the calling process and nothing is read from disk.
14
15``test/test_data/peridem/twop_circ_inbuilt/main.cpp``, compiled with
16TWOP_CONTACT_EXAMPLE as the target example_twop_circ_contact, builds the same
17deck in C++.
18"""
19
20from __future__ import annotations
21
22import math
23import os
24import sys
25from pathlib import Path
26
27HERE = Path(__file__).resolve().parent
28for _p in HERE.parents:
29 if (_p / "examples" / "peridem_env.py").is_file():
30 sys.path.insert(0, str(_p / "examples"))
31 break
32import peridem_env # noqa: F401,E402
33
34import peridem # noqa: E402
35from peridem import Deck, Geometry # noqa: E402
36from peridem.deck import MeshSpec, contact_stiffness, to_E, to_G # noqa: E402
37
38R1 = R2 = 0.001
39PARTICLE_DIST = 0.001 # drop height
40DENSITY = 1200.0
41K = 2.16e7
42NU = 0.25
43GC = 50.0
44
45R_CONTACT_FACTOR = 0.95
46EPSILON = 0.9
47BETA_N_FACTOR = 100.0
48FRICTION_COEFF = 0.5
49GRAVITY = 10.0
50
51FINAL_TIME = 0.012
52NUM_STEPS = 36000
53
54TAGS = ["Displacement", "Velocity", "Force", "Damage_Z", "Damage",
55 "Particle_ID"]
56
57
58def build_deck(output_path: str | os.PathLike[str] = "out/", *,
59 final_time: float = FINAL_TIME, num_steps: int = NUM_STEPS,
60 mesh_size: float | None = None, horizon: float | None = None,
61 damping_on: bool = False, eps: float = EPSILON,
62 zero_ic: bool = False,
63 mesh_dir: str | os.PathLike[str] | None = None,
64 write_mesh: bool = True,
65 in_process_mesh: bool = False) -> Deck:
66 mesh_size = min(R1, R2) / 5.0 if mesh_size is None else mesh_size
67 horizon = 3.0 * mesh_size if horizon is None else horizon
68
69 E = to_E(K, NU)
70 G = to_G(E=E, nu=NU)
71
72 d = Deck(dim=2, t_final=final_time, n_steps=num_steps)
73 d.set_output(output_path, tags=TAGS, interval=num_steps // 10, debug=2,
74 pvd_collection=True)
75 d.set_gravity(0.0, -GRAVITY)
76 d.set_neighbor(update_criteria="simple_all", s_factor=10.0, update_interval=40,
77 near_bd_nodes_tol=0.5)
78
79 if in_process_mesh:
80 meshes = [MeshSpec(size=mesh_size), MeshSpec(size=mesh_size)]
81 else:
82 base = Path(mesh_dir) if mesh_dir is not None else HERE / "inp"
83 meshes = [MeshSpec(file=base / f"mesh_cir_{i}.msh", size=mesh_size,
84 write=write_mesh) for i in (1, 2)]
85
86 g1 = d.add_particle_type(Geometry("circle", [R1, 0.0, 0.0, 0.0]), meshes[0])
87 g2 = d.add_particle_type(Geometry("circle", [R2, 0.0, 0.0, 0.0]), meshes[1])
88
89 m1 = d.add_material(horizon=horizon, density=DENSITY, K=K, G=G, Gc=GC,
90 influence_fn_type=1)
91 m2 = d.add_material(horizon=horizon, density=DENSITY, K=K, G=G, Gc=GC,
92 influence_fn_type=1)
93
94 kn = contact_stiffness(K, K, horizon)
95 for i, j in ((0, 0), (0, 1), (1, 1)):
96 d.add_contact_pair(i, j, contact_radius_factor=R_CONTACT_FACTOR, Kn=kn,
97 eps=eps, mu=FRICTION_COEFF, damping_on=damping_on,
98 friction_on=False, beta_n_factor=BETA_N_FACTOR,
99 K=K)
100 d.set_contact_laws(damping_law="com_and_node",
101 friction_law="coulomb_simple")
102
103 # Particle 0 is fixed and particle 1 falls onto it.
104 d.add_displacement_bc(particles=[0], direction=[1, 2],
105 zero_displacement=True)
106 vy = 0.0
107 if not zero_ic:
108 fallen = PARTICLE_DIST - horizon
109 if fallen > 0.0:
110 vy = -math.sqrt(2.0 * GRAVITY * fallen)
111 d.add_initial_velocity([0.0, vy, 0.0], particles=[1])
112
113 top_gap = PARTICLE_DIST
114 d.place(R1, R1, 0.0, geometry=g1, material=m1, contact=0)
115 d.place(R1, 2.0 * R1 + R2 + top_gap, 0.0, geometry=g2, material=m2,
116 contact=1, theta=math.pi)
117 return d
118
119
120def main(argv: list[str] | None = None) -> int:
121 import argparse
122
123 p = argparse.ArgumentParser(description=__doc__)
124 p.add_argument("-o", "--output", default=str(HERE / "runs_py"))
125 p.add_argument("--final-time", type=float, default=FINAL_TIME)
126 p.add_argument("--num-steps", type=int, default=NUM_STEPS)
127 p.add_argument("--in-process-mesh", action="store_true",
128 help="build both discs with Gmsh in memory, write no .msh")
129 p.add_argument("--nthreads", type=int,
130 default=int(os.environ.get("NTHREADS", "4")))
131 p.add_argument("--snapshot", metavar="PNG", default=None)
132 p.add_argument("--write-deck", metavar="PATH", default=None)
133 args = p.parse_args(argv)
134
135 out = Path(args.output).resolve()
136 d = build_deck(str(out / "out") + "/", final_time=args.final_time,
137 num_steps=args.num_steps,
138 in_process_mesh=args.in_process_mesh,
139 mesh_dir=out / "inp")
140 if args.write_deck:
141 print(d.write(args.write_deck))
142 return 0
143
144 (out / "out").mkdir(parents=True, exist_ok=True)
145 (out / "inp").mkdir(parents=True, exist_ok=True)
146 peridem.init(n_threads=args.nthreads)
147 sim = d.run(workdir=HERE)
148 if peridem.mpi_rank() != 0:
149 return 0
150
151 fixed, falling = sim.particle(0), sim.particle(1)
152 # Same measure as the C++ RestitutionProbe: centre-node separation minus
153 # the two bounding radii.
154 separation = math.dist(falling.x_center, fixed.x_center)
155 gap = separation - fixed.bounding_radius - falling.bounding_radius
156 print(f"done: nodes={sim.n_nodes} t={sim.time:g}")
157 print(f"centre-to-centre gap at final time: {gap:.6e} m")
158 print(f"max damage: {float(sim.damage.max()):.4f}")
159 if args.snapshot:
160 print(peridem.snapshot(peridem.last_vtu(out / "out"), args.snapshot,
161 color="|Velocity|",
162 title="two circles: |v| at final time"))
163 print(f"VTU under {out / 'out'}")
164 return 0
165
166
167if __name__ == "__main__":
168 raise SystemExit(main())
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