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"""Hollow ellipse dropped onto a short tip-up triangle, set up in Python.
11
12``main.cpp`` in this folder builds the same deck in C++, with the same
13geometry, materials and contact parameters.
14``python/tests/test_example_parity.py`` compares the two decks key by key, so
15that the comparison of the two runs is a comparison of the interfaces and not
16of two problems.
17
18Contact at the tip opens a crack. The ring separates into two pieces.
19"""
20
21from __future__ import annotations
22
23import os
24import sys
25from pathlib import Path
26HERE = Path(__file__).resolve().parent
27for _p in HERE.parents:
28 if (_p / "examples" / "peridem_env.py").is_file():
29 sys.path.insert(0, str(_p / "examples"))
30 break
31import peridem_env # noqa: F401,E402
32
33import peridem # noqa: E402
34from peridem import Deck, Geometry # noqa: E402
35from peridem.deck import MeshSpec, contact_stiffness, to_E, to_G # noqa: E402
36
37# --- geometry ---------------------------------------------------------------
38# A narrow tip concentrates the contact load.
39W = 0.0008
40H = 0.0012
41
42# A thin ring, so that a crack started at the tip separates it.
43A_OUT, B_OUT = 0.0020, 0.0014
44A_IN, B_IN = 0.00180, 0.00122
45ELL_THETA = 0.0
46TIP_GAP = 0.00008
47
48# --- material ---------------------------------------------------------------
49RHO_T, K_T, NU_T, GC_T = 1200.0, 2.16e7, 0.25, 200.0
50# Gc on the ellipse is low, so that damage at the tip runs across it.
51RHO_E, K_E, NU_E, GC_E = 1200.0, 2.16e7, 0.25, 1.0
52
53R_CONTACT_FACTOR = 0.95
54GRAVITY = 10.0
55IC_VY = -2.5
56
57MESH_SIZE = 0.00010
58HORIZON = 3.0 * MESH_SIZE
59FINAL_TIME = 0.0030
60NUM_STEPS = 30000
61
62MESH_TRI = "mesh_triangle.msh"
63MESH_ELL = "mesh_hollow_ellipse.msh"
64
65
66def build_deck(output_path: str | os.PathLike[str] = "runs/",
67 *, final_time: float = FINAL_TIME,
68 num_steps: int = NUM_STEPS,
69 mesh_size: float = MESH_SIZE,
70 horizon: float | None = None,
71 mesh_dir: str | os.PathLike[str] | None = None,
72 mesh_files: tuple[str, str] | None = None,
73 write_mesh: bool = True,
74 in_process_mesh: bool = False) -> Deck:
75 """Assemble the deck.
76
77 ``in_process_mesh`` keeps Gmsh output in memory and writes no ``.msh``,
78 which is what the README demo uses. The parity runs instead point both the
79 Python and the C++ side at the same ``.msh`` files under ``mesh_dir``.
80 """
81 horizon = 3.0 * mesh_size if horizon is None else horizon
82
83 E_t = to_E(K_T, NU_T)
84 G_t = to_G(E=E_t, nu=NU_T)
85 E_e = to_E(K_E, NU_E)
86 G_e = to_G(E=E_e, nu=NU_E)
87
88 d = Deck(dim=2, t_final=final_time, n_steps=num_steps,
89 bond_break="tension",
90 # Without this, self-contact across broken bonds closes the
91 # crack.
92 self_contact="none")
93
94 dt_out_n = max(1, num_steps // 40)
95 d.set_output(output_path,
96 tags=["Displacement", "Velocity", "Force", "Damage_Z",
97 "Damage", "Particle_ID", "Fixity"],
98 interval=dt_out_n, debug=1, pvd_collection=True)
99
100 d.set_gravity(0.0, -GRAVITY)
101 d.set_neighbor(update_criteria="simple_all", s_factor=8.0, update_interval=5,
102 near_bd_nodes_tol=0.5)
103
104 # --- particle types
105 tri = Geometry("triangle", [-0.5 * W, 0.0, 0.0,
106 0.5 * W, 0.0, 0.0,
107 0.0, H, 0.0])
108 ell = Geometry("ellipse_minus_ellipse",
109 [A_OUT, B_OUT, A_IN, B_IN, ELL_THETA, 0.0, 0.0, 0.0])
110
111 if in_process_mesh:
112 tri_mesh = MeshSpec(size=mesh_size)
113 ell_mesh = MeshSpec(size=mesh_size)
114 else:
115 base = Path(mesh_dir) if mesh_dir is not None else HERE / "inp"
116 names = mesh_files or (MESH_TRI, MESH_ELL)
117 tri_mesh = MeshSpec(file=base / names[0], size=mesh_size,
118 write=write_mesh)
119 ell_mesh = MeshSpec(file=base / names[1], size=mesh_size,
120 write=write_mesh)
121
122 g_tri = d.add_particle_type(tri, tri_mesh)
123 g_ell = d.add_particle_type(ell, ell_mesh)
124
125 # --- materials
126 m_tri = d.add_material(horizon=horizon, density=RHO_T, K=K_T, G=G_t,
127 Gc=GC_T, influence_fn_type=1)
128 m_ell = d.add_material(horizon=horizon, density=RHO_E, K=K_E, G=G_e,
129 Gc=GC_E, influence_fn_type=1)
130
131 # --- contact: group 0 is the triangle tip (wall), group 1 the ellipse
132 d.add_contact_pair(0, 0, contact_radius_factor=R_CONTACT_FACTOR,
133 Kn=contact_stiffness(K_T, K_T, horizon), eps=0.95,
134 beta_n_factor=100.0, K=K_T)
135 # Beta_n at the tip is large enough to start a crack, and damping is
136 # on to limit the node velocities after contact.
137 d.add_contact_pair(0, 1, contact_radius_factor=0.90,
138 Kn=contact_stiffness(K_T, K_E, horizon), eps=0.4,
139 beta_n_factor=8.0,
140 K=peridem.harmonic_mean(K_T, K_E))
141 d.add_contact_pair(1, 1, contact_radius_factor=R_CONTACT_FACTOR,
142 Kn=contact_stiffness(K_E, K_E, horizon), eps=0.95,
143 beta_n_factor=100.0, K=K_E)
144 d.set_contact_laws(damping_law="com_and_node",
145 friction_law="coulomb_simple")
146
147 # --- boundary and initial conditions
148 # The triangle is fixed in x and y at every node.
149 d.add_displacement_bc(particles=[0], direction=[1, 2],
150 zero_displacement=True)
151 d.add_initial_velocity([0.0, IC_VY, 0.0], particles=[1])
152
153 # The triangle is placed at its centroid and the ellipse above the tip.
154 tip_y = H
155 ell_cy = tip_y + TIP_GAP + B_OUT
156 cx, cy, cz = tri.center
157 d.place(cx, cy, 0.0, geometry=g_tri, material=m_tri, contact=0, wall=True)
158 d.place(0.0, ell_cy, 0.0, geometry=g_ell, material=m_ell, contact=1)
159
160 return d
161
162
163def main(argv: list[str] | None = None) -> int:
164 import argparse
165
166 p = argparse.ArgumentParser(description=__doc__)
167 p.add_argument("-o", "--output", default=str(HERE / "runs_py"))
168 p.add_argument("--final-time", type=float, default=FINAL_TIME)
169 p.add_argument("--num-steps", type=int, default=NUM_STEPS)
170 p.add_argument("--mesh-size", type=float, default=MESH_SIZE)
171 p.add_argument("--nthreads", type=int,
172 default=int(os.environ.get("NTHREADS", "4")))
173 p.add_argument("--in-process-mesh", action="store_true",
174 help="generate the meshes with Gmsh in memory, write no .msh")
175 p.add_argument("--write-deck", metavar="PATH",
176 help="write the assembled JSON deck and exit")
177 args = p.parse_args(argv)
178
179 out = Path(args.output).resolve()
180 d = build_deck(str(out) + "/", final_time=args.final_time,
181 num_steps=args.num_steps, mesh_size=args.mesh_size,
182 in_process_mesh=args.in_process_mesh,
183 mesh_dir=out.parent / "inp_py")
184 if args.write_deck:
185 print(d.write(args.write_deck))
186 return 0
187
188 out.mkdir(parents=True, exist_ok=True)
189 (out.parent / "inp_py").mkdir(parents=True, exist_ok=True)
190 peridem.init(n_threads=args.nthreads)
191 sim = d.run(workdir=HERE)
192 if peridem.mpi_rank() == 0:
193 print(f"done: nodes={sim.n_nodes} step={sim.step_index} t={sim.time}")
194 for p_ in sim.particles:
195 print(f" {p_!r} com={[round(c, 6) for c in p_.center_of_mass]}")
196 print(f"VTU under {out}")
197 return 0
198
199
200if __name__ == "__main__":
201 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