PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
problem Namespace Reference

Functions

list[dict[str, float]] read_sites (str|os.PathLike[str] csv=CSV)
 
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)
 
int main (list[str]|None argv=None)
 
None validate_sites (list[dict[str, float]] rows)
 
dict[str, object] pack_geometry (int ncols=NCOLS, int nrows=NROWS, float r=R, float mesh_size=MESH_SIZE)
 
Deck build_deck (str|os.PathLike[str] output_path="runs/", *int ncols=NCOLS, int nrows=NROWS, float final_time=FINAL_TIME, int num_steps=NUM_STEPS, float mesh_size=MESH_SIZE, int search_interval=SEARCH_INTERVAL, str|os.PathLike[str]|None mesh_dir=None, bool write_mesh=True, bool in_process_mesh=False)
 
Deck build_deck (str|os.PathLike[str] output_path="runs/", *float final_time=FINAL_TIME, int num_steps=NUM_STEPS, float mesh_size=MESH_SIZE, float|None horizon=None, str|os.PathLike[str]|None mesh_dir=None, tuple[str, str]|None mesh_files=None, bool write_mesh=True, bool in_process_mesh=False)
 
float _nu (float E_, float K_)
 
list[list[float]] notch_void_boxes (float h, float notch_half, float notch_w, float notch_depth, float z_lo, float z_hi)
 
Deck build_deck (str|os.PathLike[str] output_path="runs/out/", *bool dim3=False, bool quick=False, float|None final_time=None, float|None dt=None, int|None num_steps=None, str|os.PathLike[str]|None mesh_dir=None, float v_impact=IMPACT_V)
 
int seed_prenotch (sim, dict[str, float] info)
 
np.ndarray run_with_arrival_times (sim, dict[str, float] info, *float threshold=0.30)
 
tuple[float, int, float]|None crack_speed (sim, np.ndarray arrival, dict[str, float] info)
 
Deck build_deck (str|os.PathLike[str] output_path="out/", *float final_time=FINAL_TIME, int num_steps=NUM_STEPS, float|None mesh_size=None, float|None horizon=None, bool damping_on=False, float eps=EPSILON, bool zero_ic=False, str|os.PathLike[str]|None mesh_dir=None, bool write_mesh=True, bool in_process_mesh=False)
 
Deck build_deck (str|os.PathLike[str] output_path="runs/", *float final_time=FINAL_TIME, int num_steps=NUM_STEPS, int output_interval=1000, str|os.PathLike[str]|None mesh=None, float|None mesh_size=None, list[str]|None tags=None)
 
Deck build_deck (str|os.PathLike[str] output_path="runs/", *float final_time=FINAL_TIME, int num_steps=NUM_STEPS, int output_interval=2000, float mesh_size=MESH_SIZE, float horizon=HORIZON, list[str]|None tags=None)
 

Variables

 HERE = Path(__file__).resolve().parent
 
str MESH_DIR = HERE / "meshes"
 
str CSV = HERE / "particle_locations_0.csv"
 
float HORIZON = 6.0e-4
 
float DENSITY = 1200.0
 
float R_IN = 0.02
 
 R_SMALL
 
 R_LARGE
 
float OMEGA = -20.0 * math.pi
 
float GRAVITY = 10.0
 
dict R_REF = {0: R_SMALL, 1: R_SMALL, 2: R_SMALL, 3: R_LARGE, 4: R_LARGE, 5: R_LARGE}
 
list MESH_FILES
 
list WALL_PARAMS
 
dict KN
 
dict K_MAT = {0: 1.0e4, 1: 1.0e5, 2: 1.0e5}
 
float KN_FACTOR = 1.0
 
list GRAIN_SHAPES
 
dict PRESETS
 
list TAGS
 
float MESH_SIZE = R_SMALL / 5.0
 
float R_OUT = R_IN + 1.5 * MESH_SIZE
 
float L_BAR = 0.005
 
float W_BAR = 1.5 * MESH_SIZE
 
tuple ROT_CENTER = (-0.2 * R_IN, 0.2 * R_IN, 0.0)
 
float BETA_N_FACTOR = 100.0
 
float EPSILON = 0.95
 
float CONTACT_RADIUS_FACTOR = 0.95
 
float FRICTION_COEFF = 0.5
 
int SEARCH_INTERVAL = 40
 
float SEARCH_FACTOR = 10.0
 
float R = 0.001
 
float K = 2.16e7
 
float NU = 0.25
 
float GC = 50.0
 
float WALL_VY = -0.06
 
 NCOLS
 
 NROWS
 
float FINAL_TIME = 0.004
 
int NUM_STEPS = 20000
 
float W = 0.0008
 
float H = 0.0012
 
 A_OUT
 
 B_OUT
 
 A_IN
 
 B_IN
 
float ELL_THETA = 0.0
 
float TIP_GAP = 0.00008
 
 RHO_T
 
 K_T
 
 NU_T
 
 GC_T
 
 RHO_E
 
 K_E
 
 NU_E
 
 GC_E
 
float R_CONTACT_FACTOR = 0.95
 
float IC_VY = -2.5
 
str MESH_TRI = "mesh_triangle.msh"
 
str MESH_ELL = "mesh_hollow_ellipse.msh"
 
float NOTCH_DEPTH = 0.050
 
float NOTCH_HALF = 0.025
 
float NOTCH_W = 0.0015
 
float THICKNESS = 0.009
 
 IW
 
 IH
 
float IMPACT_V = 32.0
 
float IMPACTOR_MASS = 1.57
 
float RC_FACTOR = 0.95
 
float RHO = 8000.0
 
float E = 191.0e9
 
float K_BULK = 159.2e9
 
float DT = 2.5e-9
 
 QUICK
 
float R1 = 0.001
 
float PARTICLE_DIST = 0.001
 
float RADIUS = 0.003
 
float PULL_RATE = 0.005
 
str MESH_FILE = "mesh_cir_1_0.msh"
 
float SIDE = 0.01
 

Detailed Description

Attrition sim1: a rotating drum with an inward protrusion.

Circles, triangles and drums in two sizes tumble inside a cylinder that is
spun by a rotation displacement BC; the protrusion grinds them. This is the
deck behind ``attrition_test_sim1.gif`` in the top-level README.

The deck is assembled through :class:`peridem.Deck`. The wall is a complex
geometry, an outer circle with an inner circle removed and a protrusion
rectangle added. It is placed at its signed-volume centroid, computed by the
geometry object. The mesh axis is at the origin, so any other site translates
the mesh.

``gen_input.py`` next to this file writes the same decks as JSON for
``bin/PeriDEM``. ``python/tests/test_example_parity.py`` compares the two.
Attrition sim2: thin rotating container with an off-centre spin axis.

Circles, triangles, drums and hexagons in two sizes inside a thin-walled
cylinder with an inward bar. The container spins twice as fast as sim1 and
about a point offset from the origin, which throws the pack against the bar.

The deck is assembled through :class:`peridem.Deck`. Every parameter whose
default changes the result is set here. ``INPUT_DEFAULTS.md`` in this folder
records which those are.
Compression of a small circular-grain pack, set up in Python.

Parameters from Jha et al., J. Mech. Phys. Solids 151 (2021) 104376, section
4.3: material M1, lc = R/5, horizon = 3 lc, Rc = 0.95 h, C-bar = 100 and plate
velocity -0.06 m/s. The pack here is 4 by 3 grains and not the 502 of the
paper. The grain positions, the open channel that contains them and the moving
plate are derived from R and the pack size, and no deck file is read.

``test/test_data/peridem/jha2021_comp_n50/main.cpp`` builds the same deck in
C++. ``python/tests/test_example_parity.py`` compares the two decks key by
key.
Hollow ellipse dropped onto a short tip-up triangle, set up in Python.

``main.cpp`` in this folder builds the same deck in C++, with the same
geometry, materials and contact parameters.
``python/tests/test_example_parity.py`` compares the two decks key by key, so
that the comparison of the two runs is a comparison of the interfaces and not
of two problems.

Contact at the tip opens a crack. The ring separates into two pieces.
Silling 2003 Kalthoff-Winkler notched-plate impact, 2D and 3D, from Python.

A 200 x 100 mm maraging-steel plate with two open 1.5 mm notches is struck
edge-on by a rigid 1.57 kg cylinder at 32 m/s. Silling reports cracks running
from the notch tips at roughly 900 m/s and about 70 degrees to the notch.

This example uses the parts of the interface a pure JSON deck cannot reach:

* ``sim.setup()`` builds the model, then ``sim.break_bonds_in_slots(...)``
  seeds the pre-notch by cutting the peridynamic bonds that span each slot --
  the same operation the C++ driver does between ``init()`` and the time loop;
* ``sim.integrate()`` then runs the loop *without* re-initialising, or
  :func:`run_with_arrival_times` drives the loop step by step from Python to
  record when each node first becomes damaged, which gives the crack speed.

``Test_PeriDEM_notched_impact_inbuilt`` builds the same deck in C++.
``python/tests/test_example_parity.py`` compares the two decks key by key.
Two deformable circles: one fixed, one dropped onto it.

Two grains, one contact pair, gravity and an initial velocity. Both meshes are
built by Gmsh in the calling process and nothing is read from disk.

``test/test_data/peridem/twop_circ_inbuilt/main.cpp``, compiled with
TWOP_CONTACT_EXAMPLE as the target example_twop_circ_contact, builds the same
deck in C++.
Single circular particle: fixed SW patch, linear pull on the NE patch.

The deck is built in Python. ``input.json`` next to this file states the same
problem as a deck file. ``python/tests/test_example_parity.py`` runs the Python
deck through ``bin/PeriDEM`` and through the in-process interface and compares
the node fields.
Single rectangular particle pulled diagonally.

The south-west corner is fixed and the north-east corner is pulled at a
constant rate. The deck is built in Python and the mesh is generated in the
calling process, so no ``.msh`` file is read or written.

Function Documentation

◆ _nu()

float problem._nu ( float  E_,
float  K_ 
)
protected

Definition at line 80 of file problem.py.

80def _nu(E_: float, K_: float) -> float:
81 return 0.5 * (1.0 - E_ / (3.0 * K_))
82
83

Referenced by build_deck().

Here is the caller graph for this function:

◆ build_deck() [1/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "out/",
*float   final_time = FINAL_TIME,
int   num_steps = NUM_STEPS,
float | None   mesh_size = None,
float | None   horizon = None,
bool   damping_on = False,
float   eps = EPSILON,
bool   zero_ic = False,
str | os.PathLike[str] | None   mesh_dir = None,
bool   write_mesh = True,
bool   in_process_mesh = False 
)

Definition at line 58 of file problem.py.

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

References build_deck().

Here is the call graph for this function:

◆ build_deck() [2/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "runs/",
*float   final_time = FINAL_TIME,
int   num_steps = NUM_STEPS,
float   mesh_size = MESH_SIZE,
float | None   horizon = None,
str | os.PathLike[str] | None   mesh_dir = None,
tuple[str, str] | None   mesh_files = None,
bool   write_mesh = True,
bool   in_process_mesh = False 
)
Assemble the deck.

``in_process_mesh`` keeps Gmsh output in memory and writes no ``.msh``,
which is what the README demo uses. The parity runs instead point both the
Python and the C++ side at the same ``.msh`` files under ``mesh_dir``.

Definition at line 66 of file problem.py.

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

References build_deck().

Here is the call graph for this function:

◆ build_deck() [3/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "runs/",
*float   final_time = FINAL_TIME,
int   num_steps = NUM_STEPS,
int   output_interval = 1000,
str | os.PathLike[str] | None   mesh = None,
float | None   mesh_size = None,
list[str] | None   tags = None 
)
Assemble the deck.

Pass ``mesh_size`` to have Gmsh build the disc in memory (no ``.msh`` on
disk); otherwise the committed ``mesh_cir_1_0.msh`` is read, which is what
the parity run against ``bin/PeriDEM`` uses.

Definition at line 53 of file problem.py.

58 tags: list[str] | None = None) -> Deck:
59 """Assemble the deck.
60
61 Pass ``mesh_size`` to have Gmsh build the disc in memory (no ``.msh`` on
62 disk); otherwise the committed ``mesh_cir_1_0.msh`` is read, which is what
63 the parity run against ``bin/PeriDEM`` uses.
64 """
65 d = Deck(dim=2, t_final=final_time, n_steps=num_steps,
66 particle_sim_type="Single_Particle")
67 d.set_comment("Single-particle circle: fixed SW patch, linear pull on NE "
68 "(PDState), set up in Python")
69 d.set_output(output_path, tags=tags or TAGS, interval=output_interval,
70 debug=1, tag_pp="0", pvd_collection=True)
71
72 if mesh_size is not None:
73 mesh_spec = MeshSpec(size=mesh_size)
74 else:
75 mesh_spec = MeshSpec(file=Path(mesh) if mesh is not None
76 else HERE / MESH_FILE)
77 d.add_particle_type(Geometry("circle", [RADIUS, 0.0, 0.0, 0.0]), mesh_spec)
78 d.add_material(horizon=HORIZON, density=DENSITY, K=K,
79 G=to_G(E=peridem.to_E(K, NU), nu=NU), Gc=GC,
80 influence_fn_type=1)
81
82 # A square in the third quadrant is held and an equal square in the
83 # first quadrant is pulled.
84 hold = Geometry("rectangle", [-0.001, -0.001, 0.0, -0.0005, -0.0005, 0.0])
85 pull = Geometry("rectangle", [0.0005, 0.0005, 0.0, 0.001, 0.001, 0.0])
86 d.add_displacement_bc(region=hold, direction=[1, 2],
87 time_fn_type="constant", time_fn_params=[0.0],
88 spatial_fn_type="constant", zero_displacement=True)
89 d.add_displacement_bc(region=pull, direction=[1, 2],
90 time_fn_type="linear", time_fn_params=[PULL_RATE],
91 spatial_fn_type="constant")
92
93 d.set_test("test_peridynamics")
94 return d
95
96

References build_deck().

Here is the call graph for this function:

◆ build_deck() [4/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "runs/",
*float   final_time = FINAL_TIME,
int   num_steps = NUM_STEPS,
int   output_interval = 2000,
float   mesh_size = MESH_SIZE,
float   horizon = HORIZON,
list[str] | None   tags = None 
)

Definition at line 51 of file problem.py.

55 tags: list[str] | None = None) -> Deck:
56 d = Deck(dim=2, t_final=final_time, n_steps=num_steps,
57 particle_sim_type="Single_Particle")
58 d.set_comment("Single-particle rectangle: uniform in-process mesh; fixed "
59 "SW corner, linear pull on NE, set up in Python")
60 d.set_output(output_path, tags=tags or TAGS, interval=output_interval,
61 debug=1, tag_pp="1", pvd_collection=True)
62
63 # The uniform mesh is generated in memory and no file is read.
64 d.add_particle_type(Geometry("rectangle", [0.0, 0.0, 0.0, SIDE, SIDE, 0.0]),
65 MeshSpec(size=mesh_size, info="uniform"))
66 d.add_material(horizon=horizon, density=DENSITY, K=K,
67 G=to_G(E=peridem.to_E(K, NU), nu=NU), Gc=GC,
68 influence_fn_type=1)
69
70 corner = 0.2 * SIDE
71 hold = Geometry("rectangle", [0.0, 0.0, 0.0, corner, corner, 0.0])
72 pull = Geometry("rectangle",
73 [SIDE - corner, SIDE - corner, 0.0, SIDE, SIDE, 0.0])
74 d.add_displacement_bc(region=hold, direction=[1, 2],
75 time_fn_type="constant", time_fn_params=[0.0],
76 spatial_fn_type="constant", zero_displacement=True)
77 d.add_displacement_bc(region=pull, direction=[1, 2],
78 time_fn_type="linear", time_fn_params=[PULL_RATE],
79 spatial_fn_type="constant")
80
81 d.set_test("test_peridynamics")
82 return d
83
84

References build_deck().

Here is the call graph for this function:

◆ build_deck() [5/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "runs/",
*int   ncols = NCOLS,
int   nrows = NROWS,
float   final_time = FINAL_TIME,
int   num_steps = NUM_STEPS,
float   mesh_size = MESH_SIZE,
int   search_interval = SEARCH_INTERVAL,
str | os.PathLike[str] | None   mesh_dir = None,
bool   write_mesh = True,
bool   in_process_mesh = False 
)

Definition at line 91 of file problem.py.

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

References build_deck(), and pack_geometry().

Here is the call graph for this function:

◆ build_deck() [6/7]

Deck problem.build_deck ( str | os.PathLike[str]   output_path = "runs/out/",
*bool   dim3 = False,
bool   quick = False,
float | None   final_time = None,
float | None   dt = None,
int | None   num_steps = None,
str | os.PathLike[str] | None   mesh_dir = None,
float   v_impact = IMPACT_V 
)

Definition at line 95 of file problem.py.

100 v_impact: float = IMPACT_V) -> Deck:
101 cfg = dict(W=W, H=H, notch_half=NOTCH_HALF, mesh_size=MESH_SIZE, Iw=IW,
102 Ih=IH, rho=RHO, E=E, K_bulk=K_BULK, Gc=GC, dt=DT,
103 final_time=FINAL_TIME)
104 if quick:
105 cfg.update(QUICK)
106 cfg["notch_depth"] = 0.5 * cfg["H"]
107 cfg["notch_w"] = max(cfg["mesh_size"], 0.1 * cfg["notch_half"])
108 else:
109 cfg["notch_depth"] = NOTCH_DEPTH
110 cfg["notch_w"] = NOTCH_W
111
112 if final_time is not None:
113 cfg["final_time"] = final_time
114 if dt is not None:
115 cfg["dt"] = dt
116 n_steps = (int(round(cfg["final_time"] / cfg["dt"])) if num_steps is None
117 else num_steps)
118
119 w, h = cfg["W"], cfg["H"]
120 mesh_size = cfg["mesh_size"]
121 horizon = 3.0 * mesh_size
122 notch_half, notch_w, notch_depth = (cfg["notch_half"], cfg["notch_w"],
123 cfg["notch_depth"])
124 iw, ih = cfg["Iw"], cfg["Ih"]
125 gap = 1.5 * RC_FACTOR * mesh_size
126 rho, e_mod, k_bulk, gc = cfg["rho"], cfg["E"], cfg["K_bulk"], cfg["Gc"]
127 nu = _nu(e_mod, k_bulk)
128 g_mod = e_mod / (2.0 * (1.0 + nu))
129
130 d = Deck(dim=3 if dim3 else 2, t_final=cfg["final_time"], n_steps=n_steps,
131 # Element-node connectivity is only used for strain output and
132 # does not handle the hexahedra the 3D structured grid produces.
133 populate_element_node_connectivity=not dim3,
134 self_contact="none", bond_break="tension", wall_contact="meshed")
135
136 d.set_output(output_path, tags=TAGS, interval=max(1, n_steps // 10),
137 debug=1, perform_fe_out=False, dt_test_out=n_steps,
138 pvd_collection=False)
139 d.set_neighbor(update_criteria="simple_all", s_factor=5.0, update_interval=1,
140 near_bd_nodes_tol=0.5)
141
142 # --- bodies
143 if dim3:
144 plate = Geometry("cuboid", [-0.5 * w, -0.5 * h, -0.5 * THICKNESS,
145 0.5 * w, 0.5 * h, 0.5 * THICKNESS])
146 # Cylinder axis along the impact direction, flat face striking the edge.
147 impactor = Geometry("cylinder", [0.5 * iw, 0.0, -0.5 * ih, 0.0,
148 0.0, ih, 0.0])
149 z_lo, z_hi = -0.5 * THICKNESS - 1.0e-9, 0.5 * THICKNESS + 1.0e-9
150 else:
151 plate = Geometry("rectangle",
152 [-0.5 * w, -0.5 * h, 0.0, 0.5 * w, 0.5 * h, 0.0])
153 impactor = Geometry("rectangle",
154 [-0.5 * iw, -0.5 * ih, 0.0, 0.5 * iw, 0.5 * ih, 0.0])
155 z_lo, z_hi = -1.0e-9, 1.0e-9
156
157 voids = notch_void_boxes(h, notch_half, notch_w, notch_depth, z_lo, z_hi)
158 base = Path(mesh_dir) if mesh_dir is not None else HERE / "runs/inp"
159 # The plate sits on Silling's equally spaced structured grid with the notch
160 # slots carved out, so they are real gaps rather than cut material.
161 g_plate = d.add_particle_type(
162 plate, MeshSpec(file=base / "mesh_plate.msh", size=mesh_size,
163 info="uniform", voids=voids))
164 # In 2D the impactor is a rectangle, so it goes on the same grid; in 3D it
165 # is a cylinder, which a uniform grid cannot represent.
166 g_imp = d.add_particle_type(
167 impactor,
168 MeshSpec(file=base / "mesh_impactor.msh", size=mesh_size,
169 info="gmsh_builtin_mesh" if dim3 else "uniform"))
170
171 # The impactor has the plate material with Gc = 0. Its nodal forces are
172 # replaced by the rigid-body acceleration, so its bonds carry no load.
173 m_plate = d.add_material(material_type="PMBBond", horizon=horizon,
174 density=rho, K=k_bulk, G=g_mod, Gc=gc, E=e_mod,
175 influence_fn_type=0, influence_fn_params=[1.0])
176 m_imp = d.add_material(material_type="PDElasticBond", horizon=horizon,
177 density=rho, K=k_bulk, G=g_mod, Gc=0.0, E=e_mod,
178 influence_fn_type=0, influence_fn_params=[1.0])
179
180 # Contact force density is Kn * V_j * overlap, so Kn carries one power of
181 # the horizon per spatial dimension of the nodal weight: 5 in 3D, 4 in 2D.
182 kn = contact_stiffness(k_bulk, k_bulk, horizon,
183 horizon_power=5 if dim3 else 4)
184 for i, j in ((0, 0), (0, 1), (1, 1)):
185 d.add_contact_pair(i, j, contact_radius=RC_FACTOR * mesh_size,
186 Kn=kn, eps=1.0, damping_on=False, friction_on=False,
187 beta_n_factor=0.0, K=k_bulk)
188 d.set_contact_laws(damping_law="off", friction_law="coulomb_simple")
189
190 # The plate carries no load on its boundary. The displacement condition
191 # constrains the impactor to move along y.
192 d.add_displacement_bc(particles=[1], direction=[1],
193 time_fn_type="constant", time_fn_params=[0.0],
194 spatial_fn_type="constant", zero_displacement=True)
195 d.add_initial_velocity([0.0, -v_impact, 0.0], particles=[1])
196
197 # In two dimensions the mass is per unit thickness of the 9 mm plate.
198 d.add_rigid_particle(1, IMPACTOR_MASS if dim3 else IMPACTOR_MASS / THICKNESS)
199
200 d.place(0.0, 0.0, 0.0, geometry=g_plate, material=m_plate, contact=0)
201 d.place(0.0, 0.5 * h + 0.5 * ih + gap, 0.0, geometry=g_imp,
202 material=m_imp, contact=1)
203
204 # Used by seed_prenotch and crack_speed below.
205 d.extra_info = {"H": h, "notch_half": notch_half, "notch_w": notch_w,
206 "notch_depth": notch_depth, "mesh_size": mesh_size,
207 "horizon": horizon, "n_steps": n_steps,
208 "dt": cfg["dt"], "final_time": cfg["final_time"]}
209 return d
210
211

References _nu(), and notch_void_boxes().

Here is the call graph for this function:

◆ build_deck() [7/7]

Deck problem.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 at line 125 of file problem.py.

132 csv: str | os.PathLike[str] = CSV) -> Deck:
133 t_default, n_default, out_default = PRESETS[preset]
134 final_time = t_default if final_time is None else final_time
135 num_steps = n_default if num_steps is None else num_steps
136 output_interval = out_default if output_interval is None else output_interval
137
138 d = Deck(dim=2, t_final=final_time, n_steps=num_steps,
139 bond_break="tension", self_contact="none", wall_contact="meshed")
140 d.set_output(output_path, tags=TAGS, interval=output_interval, debug=1,
141 perform_fe_out=False,
142 dt_test_out=max(1, output_interval // 10), tag_pp="0",
143 pvd_collection=True)
144 d.set_gravity(0.0, -GRAVITY)
145 d.set_neighbor(update_criteria="simple_all", s_factor=10.0, update_interval=40,
146 near_bd_nodes_tol=0.5)
147
148 base = Path(mesh_dir)
149 for i, (name, params) in enumerate(GRAIN_SHAPES):
150 d.add_particle_type(Geometry(name, params),
151 MeshSpec(file=(base / MESH_FILES[i]).resolve()))
152 wall = Geometry("complex", WALL_PARAMS,
153 vec_type=["circle", "circle", "rectangle"],
154 vec_flag=["plus", "minus", "plus"])
155 g_wall = d.add_particle_type(wall,
156 MeshSpec(file=(base / MESH_FILES[6]).resolve()))
157
158 # Material 0 is the small grains, material 1 the large grains and drum.
159 m_small = d.add_material(horizon=HORIZON, density=DENSITY, K=1.0e4, G=6.0e3,
160 Gc=50.0, influence_fn_type=1)
161 m_large = d.add_material(horizon=HORIZON, density=DENSITY, K=1.0e5, G=6.0e4,
162 Gc=100.0, influence_fn_type=1)
163
164 # Contact groups: 0 small grains, 1 large grains, 2 the drum.
165 for (i, j), kn in KN.items():
166 d.add_contact_pair(
167 i, j, contact_radius_factor=0.95, Kn=kn,
168 K=peridem.harmonic_mean(K_MAT[i], K_MAT[j]),
169 damping_on=False, friction_on=False, eps=0.95, mu=0.5,
170 Kn_factor=KN_FACTOR,
171 # Damping is off, so Beta_n_Factor does not enter the force.
172 raw={"Beta_n_Factor": 100.0})
173 d.set_contact_laws(damping_law="off", friction_law="coulomb_simple",
174 correct_volume=False)
175
176 sites = read_sites(csv)
177 for s in sites:
178 zone = int(s["zone"])
179 large = zone >= 3
180 d.place(s["x"], s["y"], s["z"], geometry=zone,
181 material=m_large if large else m_small,
182 contact=1 if large else 0,
183 theta=s["theta"], scale=s["r"] / R_REF[zone])
184
185 wcx, wcy, wcz = wall.center
186 wall_id = len(sites)
187 d.place(wcx, wcy, wcz, geometry=g_wall, material=m_large, contact=2,
188 wall=True)
189
190 # The rotation centre is the last three time function parameters.
191 d.add_displacement_bc(particles=[wall_id], direction=[1, 2],
192 time_fn_type="rotation",
193 time_fn_params=[omega, 0.0, 0.0, 0.0],
194 spatial_fn_type="rotation")
195 return d
196
197

References read_sites().

Referenced by build_deck(), build_deck(), build_deck(), build_deck(), build_deck(), crack_speed(), main(), and validate_sites().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ crack_speed()

tuple[float, int, float] | None problem.crack_speed (   sim,
np.ndarray  arrival,
dict[str, float]  info 
)
Least-squares crack-tip speed from the arrival-time field.

Fits distance-from-the-right-notch-tip against arrival time over damaged
plate nodes below the tip (where the crack runs) and within half the plate
height of it. Returns (m/s, points used, correlation).

The returned correlation states how well the fit holds. If damage spreads
through the plate instead of advancing as a front, the fit has no meaning
and the correlation is low. The ``--quick`` settings produce that, on a
plate of 40 by 20 mm with E = 1.23 GPa.

Definition at line 264 of file problem.py.

265 info: dict[str, float]) -> tuple[float, int, float] | None:
266 """Least-squares crack-tip speed from the arrival-time field.
267
268 Fits distance-from-the-right-notch-tip against arrival time over damaged
269 plate nodes below the tip (where the crack runs) and within half the plate
270 height of it. Returns (m/s, points used, correlation).
271
272 The returned correlation states how well the fit holds. If damage spreads
273 through the plate instead of advancing as a front, the fit has no meaning
274 and the correlation is low. The ``--quick`` settings produce that, on a
275 plate of 40 by 20 mm with E = 1.23 GPa.
276 """
277 y_tip = 0.5 * info["H"] - info["notch_depth"]
278 tip = np.array([info["notch_half"], y_tip])
279 x = np.asarray(sim.reference)
280 seen = (arrival >= 0.0) & (np.asarray(sim.particle_id) == 0)
281 dist = np.linalg.norm(x[:, :2] - tip, axis=1)
282 # The crack runs into the plate, below the notch tip.
283 band = seen & (dist < 0.5 * info["H"]) & (x[:, 1] < y_tip)
284 n = int(band.sum())
285 if n < 20:
286 return None
287 t = arrival[band]
288 r_dist = dist[band]
289 if np.ptp(t) <= 0.0:
290 return None
291 slope, _ = np.polyfit(t, r_dist, 1)
292 r = float(np.corrcoef(t, r_dist)[0, 1])
293 return float(slope), n, r
294
295

References build_deck(), crack_speed(), run_with_arrival_times(), and seed_prenotch().

Referenced by crack_speed().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ main()

int problem.main ( list[str] | None   argv = None)

Definition at line 198 of file problem.py.

198def main(argv: list[str] | None = None) -> int:
199 import argparse
200
201 p = argparse.ArgumentParser(description=__doc__)
202 p.add_argument("-o", "--output", default=str(HERE / "runs_py/out"))
203 p.add_argument("--preset", choices=sorted(PRESETS), default="short")
204 p.add_argument("--final-time", type=float, default=None)
205 p.add_argument("--num-steps", type=int, default=None)
206 p.add_argument("--output-interval", type=int, default=None)
207 p.add_argument("--nthreads", type=int,
208 default=int(os.environ.get("NTHREADS", "4")))
209 p.add_argument("--snapshot", metavar="PNG", default=None)
210 p.add_argument("--write-deck", metavar="PATH", default=None)
211 args = p.parse_args(argv)
212
213 out = Path(args.output).resolve()
214 d = build_deck(str(out) + "/", preset=args.preset,
215 final_time=args.final_time, num_steps=args.num_steps,
216 output_interval=args.output_interval)
217 if args.write_deck:
218 print(d.write(args.write_deck))
219 return 0
220
221 out.mkdir(parents=True, exist_ok=True)
222 peridem.init(n_threads=args.nthreads)
223 sim = d.run(workdir=HERE)
224 if peridem.mpi_rank() != 0:
225 return 0
226 print(f"done: grains={sim.n_particles} walls={sim.n_walls} "
227 f"nodes={sim.n_nodes} t={sim.time:g}")
228 worst = max(sim.particles, key=lambda q: float(q.damage.max()), default=None)
229 if worst is not None:
230 print(f"most damaged grain: id={worst.id} "
231 f"max Z={float(worst.damage.max()):.4f} "
232 f"shape={worst.geometry_name}")
233 if args.snapshot:
234 print(peridem.snapshot(peridem.last_vtu(out), args.snapshot,
235 color="Damage_Z",
236 title="attrition sim1: damage"))
237 print(f"VTU under {out}")
238 return 0
239
240

References build_deck().

Here is the call graph for this function:

◆ notch_void_boxes()

list[list[float]] problem.notch_void_boxes ( float  h,
float  notch_half,
float  notch_w,
float  notch_depth,
float  z_lo,
float  z_hi 
)
The two notch slots as boxes, open at the top edge.

Definition at line 84 of file problem.py.

86 z_hi: float) -> list[list[float]]:
87 """The two notch slots as boxes, open at the top edge."""
88 hw = 0.5 * notch_w
89 y_tip = 0.5 * h - notch_depth
90 y_hi = 0.5 * h + 1.0e-9
91 return [[-notch_half - hw, y_tip, z_lo, -notch_half + hw, y_hi, z_hi],
92 [notch_half - hw, y_tip, z_lo, notch_half + hw, y_hi, z_hi]]
93
94

Referenced by build_deck().

Here is the caller graph for this function:

◆ pack_geometry()

dict[str, object] problem.pack_geometry ( int   ncols = NCOLS,
int   nrows = NROWS,
float   r = R,
float   mesh_size = MESH_SIZE 
)
Pack spacing and container dimensions, all derived from R and the mesh.

The gap between grains is slightly larger than the contact radius, so that
no pair is in contact at t = 0. Gravity and the plate bring pairs into
contact.

Definition at line 61 of file problem.py.

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

Referenced by build_deck().

Here is the caller graph for this function:

◆ read_sites()

list[dict[str, float]] problem.read_sites ( str | os.PathLike[str]   csv = CSV)
Packed grain sites: ``zone, x, y, z, r, theta`` per row.

Definition at line 105 of file problem.py.

105def read_sites(csv: str | os.PathLike[str] = CSV) -> list[dict[str, float]]:
106 """Packed grain sites: ``zone, x, y, z, r, theta`` per row."""
107 sites: list[dict[str, float]] = []
108 with Path(csv).open() as f:
109 next(f)
110 for line in f:
111 parts = [p.strip() for p in line.split(",")]
112 if len(parts) < 6:
113 continue
114 zone = int(float(parts[0]))
115 x, y, z, r, theta = (float(v) for v in parts[1:6])
116 if math.hypot(x, y) + r > R_IN - 1.0e-6:
117 raise ValueError(
118 f"initial pack overlaps the drum: zone={zone} at "
119 f"({x}, {y}) r={r} reaches past R_in={R_IN}")
120 sites.append({"zone": zone, "x": x, "y": y, "z": z, "r": r,
121 "theta": theta})
122 return sites
123
124

Referenced by build_deck(), and validate_sites().

Here is the caller graph for this function:

◆ run_with_arrival_times()

np.ndarray problem.run_with_arrival_times (   sim,
dict[str, float]  info,
*float   threshold = 0.30 
)
Drive the time loop from Python, recording first-damage time per node.

The arrival-time field is what gives the crack speed Silling reports.
Returns an array of length n_nodes, -1 where the node never damaged.

Definition at line 228 of file problem.py.

229 threshold: float = 0.30) -> np.ndarray:
230 """Drive the time loop from Python, recording first-damage time per node.
231
232 The arrival-time field is what gives the crack speed Silling reports.
233 Returns an array of length n_nodes, -1 where the node never damaged.
234 """
235 n_nodes = sim.n_nodes
236 arrival = np.full(n_nodes, -1.0)
237 plate = np.asarray(sim.particle_id) == 0
238 sample_every = max(1, int(info["n_steps"]) // 400)
239
240 def sample() -> None:
241 fresh = (arrival < 0.0) & plate & (sim.damage >= threshold)
242 arrival[fresh] = sim.time
243
244 sim.apply_initial_condition()
245 if sim.perform_output:
246 sim.write_output()
247 sim.set_current_dt(sim.dt)
248 sim.apply_displacement_bc()
249 sim.compute_forces()
250 sim.apply_rigid_body_constraint()
251 while sim.step_index < sim.n_steps:
252 sim.step()
253 if sim.should_output:
254 sim.write_output()
255 if sim.step_index % sample_every == 0:
256 sample()
257 sim.check_stop()
258 if sim.stopped:
259 break
260 sample()
261 return arrival
262
263

Referenced by crack_speed().

Here is the caller graph for this function:

◆ seed_prenotch()

int problem.seed_prenotch (   sim,
dict[str, float]  info 
)
Cut the peridynamic bonds that span the two notch slots.

The slots are already absent from the mesh, but the horizon is twice the
slot width, so bonds still reach across them. Must run after ``setup()``.

Definition at line 212 of file problem.py.

212def seed_prenotch(sim, info: dict[str, float]) -> int:
213 """Cut the peridynamic bonds that span the two notch slots.
214
215 The slots are already absent from the mesh, but the horizon is twice the
216 slot width, so bonds still reach across them. Must run after ``setup()``.
217 """
218 y_top = 0.5 * info["H"]
219 y_tip = y_top - info["notch_depth"]
220 n = sim.break_bonds_in_slots(
221 [-info["notch_half"], info["notch_half"]], info["notch_w"],
222 y_tip, y_top + 0.01 * info["H"], 0)
223 if n < 10:
224 raise RuntimeError(f"expected pre-notch bonds to cut, got {n}")
225 return n
226
227

Referenced by crack_speed().

Here is the caller graph for this function:

◆ validate_sites()

None problem.validate_sites ( list[dict[str, float]]  rows)
Reject a pack that starts inside the wall, the bar, or another grain.

Definition at line 129 of file problem.py.

129def validate_sites(rows: list[dict[str, float]]) -> None:
130 """Reject a pack that starts inside the wall, the bar, or another grain."""
131 bar = (R_IN - L_BAR, -0.5 * W_BAR, R_IN, 0.5 * W_BAR)
132 errors: list[str] = []
133 for i, a in enumerate(rows):
134 if a["zone"] not in R_REF:
135 errors.append(f"row {i}: unknown shape id {a['zone']}")
136 continue
137 if math.hypot(a["x"], a["y"]) + a["r"] > R_IN - 1.0e-9:
138 errors.append(f"row {i}: reaches past R_in")
139 if not (a["x"] + a["r"] < bar[0] or a["x"] - a["r"] > bar[2]
140 or a["y"] + a["r"] < bar[1] or a["y"] - a["r"] > bar[3]):
141 errors.append(f"row {i}: overlaps the protrusion")
142 for i in range(len(rows)):
143 for j in range(i + 1, len(rows)):
144 a, b = rows[i], rows[j]
145 if math.hypot(a["x"] - b["x"], a["y"] - b["y"]) < \
146 a["r"] + b["r"] - 1.0e-9:
147 errors.append(f"rows {i},{j} overlap")
148 if errors:
149 raise ValueError("initial pack invalid:\n " + "\n ".join(errors[:40]))
150
151

References build_deck(), and read_sites().

Here is the call graph for this function:

Variable Documentation

◆ A_IN

problem.A_IN

Definition at line 44 of file problem.py.

◆ A_OUT

problem.A_OUT

Definition at line 43 of file problem.py.

◆ B_IN

problem.B_IN

Definition at line 44 of file problem.py.

◆ B_OUT

problem.B_OUT

Definition at line 43 of file problem.py.

◆ BETA_N_FACTOR

float problem.BETA_N_FACTOR = 100.0

Definition at line 85 of file problem.py.

◆ CONTACT_RADIUS_FACTOR

float problem.CONTACT_RADIUS_FACTOR = 0.95

Definition at line 87 of file problem.py.

◆ CSV

str problem.CSV = HERE / "particle_locations_0.csv"

Definition at line 45 of file problem.py.

◆ DENSITY

float problem.DENSITY = 1200.0

Definition at line 48 of file problem.py.

◆ DT

float problem.DT = 2.5e-9

Definition at line 68 of file problem.py.

◆ E

float problem.E = 191.0e9

Definition at line 64 of file problem.py.

◆ ELL_THETA

float problem.ELL_THETA = 0.0

Definition at line 45 of file problem.py.

◆ EPSILON

float problem.EPSILON = 0.95

Definition at line 86 of file problem.py.

◆ FINAL_TIME

float problem.FINAL_TIME = 0.004

Definition at line 50 of file problem.py.

◆ FRICTION_COEFF

float problem.FRICTION_COEFF = 0.5

Definition at line 88 of file problem.py.

◆ GC

float problem.GC = 50.0

Definition at line 45 of file problem.py.

◆ GC_E

problem.GC_E

Definition at line 51 of file problem.py.

◆ GC_T

problem.GC_T

Definition at line 49 of file problem.py.

◆ GRAIN_SHAPES

list problem.GRAIN_SHAPES
Initial value:
1= [
2 ("circle", [R_SMALL, 0.0, 0.0, 0.0]),
3 ("triangle", [R_SMALL, 0.0, 0.0, 0.0]),
4 ("drum2d", [R_SMALL, 0.0004, 0.0, 0.0, 0.0]),
5 ("circle", [R_LARGE, 0.0, 0.0, 0.0]),
6 ("triangle", [R_LARGE, 0.0, 0.0, 0.0]),
7 ("drum2d", [R_LARGE, 0.0012, 0.0, 0.0, 0.0]),
8]

Definition at line 86 of file problem.py.

◆ GRAVITY

float problem.GRAVITY = 10.0

Definition at line 52 of file problem.py.

◆ H

problem.H = 0.0012

Definition at line 40 of file problem.py.

◆ HERE

problem.HERE = Path(__file__).resolve().parent

Definition at line 33 of file problem.py.

◆ HORIZON

float problem.HORIZON = 6.0e-4

Definition at line 47 of file problem.py.

◆ IC_VY

float problem.IC_VY = -2.5

Definition at line 55 of file problem.py.

◆ IH

problem.IH

Definition at line 55 of file problem.py.

◆ IMPACT_V

float problem.IMPACT_V = 32.0

Definition at line 56 of file problem.py.

◆ IMPACTOR_MASS

float problem.IMPACTOR_MASS = 1.57

Definition at line 57 of file problem.py.

◆ IW

problem.IW

Definition at line 55 of file problem.py.

◆ K

float problem.K = 2.16e7

Definition at line 43 of file problem.py.

◆ K_BULK

float problem.K_BULK = 159.2e9

Definition at line 65 of file problem.py.

◆ K_E

problem.K_E

Definition at line 51 of file problem.py.

◆ K_MAT

dict problem.K_MAT = {0: 1.0e4, 1: 1.0e5, 2: 1.0e5}

Definition at line 82 of file problem.py.

◆ K_T

problem.K_T

Definition at line 49 of file problem.py.

◆ KN

dict problem.KN
Initial value:
1= {
2 (0, 0): 7.368284e20,
3 (0, 1): 1.339688e21,
4 (0, 2): 1.339688e21,
5 (1, 1): 7.368284e21,
6 (1, 2): 7.368284e21,
7 (2, 2): 7.368284e21,
8}

Definition at line 74 of file problem.py.

◆ KN_FACTOR

float problem.KN_FACTOR = 1.0

Definition at line 83 of file problem.py.

◆ L_BAR

float problem.L_BAR = 0.005

Definition at line 47 of file problem.py.

◆ MESH_DIR

str problem.MESH_DIR = HERE / "meshes"

Definition at line 44 of file problem.py.

◆ MESH_ELL

str problem.MESH_ELL = "mesh_hollow_ellipse.msh"

Definition at line 63 of file problem.py.

◆ MESH_FILE

str problem.MESH_FILE = "mesh_cir_1_0.msh"

Definition at line 50 of file problem.py.

◆ MESH_FILES

tuple problem.MESH_FILES
Initial value:
1= [
2 "mesh_cir_small_0.msh",
3 "mesh_tri_small_0.msh",
4 "mesh_drum2d_small_0.msh",
5 "mesh_cir_large_0.msh",
6 "mesh_tri_large_0.msh",
7 "mesh_drum2d_large_0.msh",
8 "mesh_wall_0.msh",
9]

Definition at line 57 of file problem.py.

◆ MESH_SIZE

float problem.MESH_SIZE = R_SMALL / 5.0

Definition at line 43 of file problem.py.

◆ MESH_TRI

str problem.MESH_TRI = "mesh_triangle.msh"

Definition at line 62 of file problem.py.

◆ NCOLS

problem.NCOLS

Definition at line 49 of file problem.py.

◆ NOTCH_DEPTH

float problem.NOTCH_DEPTH = 0.050

Definition at line 49 of file problem.py.

◆ NOTCH_HALF

float problem.NOTCH_HALF = 0.025

Definition at line 50 of file problem.py.

◆ NOTCH_W

float problem.NOTCH_W = 0.0015

Definition at line 51 of file problem.py.

◆ NROWS

problem.NROWS

Definition at line 49 of file problem.py.

◆ NU

float problem.NU = 0.25

Definition at line 44 of file problem.py.

◆ NU_E

problem.NU_E

Definition at line 51 of file problem.py.

◆ NU_T

problem.NU_T

Definition at line 49 of file problem.py.

◆ NUM_STEPS

int problem.NUM_STEPS = 20000

Definition at line 51 of file problem.py.

◆ OMEGA

float problem.OMEGA = -20.0 * math.pi

Definition at line 51 of file problem.py.

◆ PARTICLE_DIST

float problem.PARTICLE_DIST = 0.001

Definition at line 39 of file problem.py.

◆ PRESETS

dict problem.PRESETS
Initial value:
1= {
2 "short": (0.01, 100_000, 2000),
3 "medium": (0.03, 300_000, 3000),
4 "paper": (0.1, 1_000_000, 2500),
5}

Definition at line 95 of file problem.py.

◆ PULL_RATE

float problem.PULL_RATE = 0.005

Definition at line 44 of file problem.py.

◆ QUICK

problem.QUICK
Initial value:
1= dict(W=0.040, H=0.020, notch_half=0.005, mesh_size=0.020 / 16.0,
2 Iw=0.008, Ih=0.004, rho=1200.0, E=1.23e9, K_bulk=2.0e9, Gc=424.0,
3 dt=1.0e-8, final_time=1.0e-4)

Definition at line 75 of file problem.py.

◆ R

float problem.R = 0.001

Definition at line 40 of file problem.py.

◆ R1

float problem.R1 = 0.001

Definition at line 38 of file problem.py.

◆ R_CONTACT_FACTOR

float problem.R_CONTACT_FACTOR = 0.95

Definition at line 53 of file problem.py.

◆ R_IN

float problem.R_IN = 0.02

Definition at line 49 of file problem.py.

◆ R_LARGE

problem.R_LARGE

Definition at line 50 of file problem.py.

◆ R_OUT

float problem.R_OUT = R_IN + 1.5 * MESH_SIZE

Definition at line 46 of file problem.py.

◆ R_REF

dict problem.R_REF = {0: R_SMALL, 1: R_SMALL, 2: R_SMALL, 3: R_LARGE, 4: R_LARGE, 5: R_LARGE}

Definition at line 55 of file problem.py.

◆ R_SMALL

problem.R_SMALL

Definition at line 50 of file problem.py.

◆ RADIUS

float problem.RADIUS = 0.003

Definition at line 35 of file problem.py.

◆ RC_FACTOR

float problem.RC_FACTOR = 0.95

Definition at line 60 of file problem.py.

◆ RHO

float problem.RHO = 8000.0

Definition at line 63 of file problem.py.

◆ RHO_E

problem.RHO_E

Definition at line 51 of file problem.py.

◆ RHO_T

problem.RHO_T

Definition at line 49 of file problem.py.

◆ ROT_CENTER

tuple problem.ROT_CENTER = (-0.2 * R_IN, 0.2 * R_IN, 0.0)

Definition at line 53 of file problem.py.

◆ SEARCH_FACTOR

float problem.SEARCH_FACTOR = 10.0

Definition at line 90 of file problem.py.

◆ SEARCH_INTERVAL

int problem.SEARCH_INTERVAL = 40

Definition at line 89 of file problem.py.

◆ SIDE

float problem.SIDE = 0.01

Definition at line 34 of file problem.py.

◆ TAGS

list problem.TAGS
Initial value:
1= ["Displacement", "Velocity", "Force", "Damage_Z", "Damage",
2 "Particle_ID", "Fixity", "Contact_Nodes"]

Definition at line 101 of file problem.py.

◆ THICKNESS

float problem.THICKNESS = 0.009

Definition at line 52 of file problem.py.

◆ TIP_GAP

float problem.TIP_GAP = 0.00008

Definition at line 46 of file problem.py.

◆ W

problem.W = 0.0008

Definition at line 39 of file problem.py.

◆ W_BAR

float problem.W_BAR = 1.5 * MESH_SIZE

Definition at line 48 of file problem.py.

◆ WALL_PARAMS

list problem.WALL_PARAMS
Initial value:
1= [0.021, 0.0, 0.0, 0.0,
2 0.02, 0.0, 0.0, 0.0,
3 0.014, -0.0015, 0.0, 0.02, 0.0015, 0.0]

Definition at line 68 of file problem.py.

◆ WALL_VY

float problem.WALL_VY = -0.06

Definition at line 46 of file problem.py.