PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
gen_input.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Build modular JSON for attrition sim2 — explicit keys, match main GIF deck.
3
4Reference:
5 examples/PeriDEM/attrition_tests/sim2_multi_particle_circ_tri_drum_hex_with_rotating_cylinder_with_protrusion_thin_container_and_change_rotation_rate
6
7Every field that has a dangerous modular default is set explicitly (see
8INPUT_DEFAULTS.md). No reliance on omitted-key defaults.
9"""
10
11from __future__ import annotations
12
13import json
14import math
15from pathlib import Path
16
17HERE = Path(__file__).resolve().parent
18MESH = HERE / "meshes"
19CSV = HERE / "particle_locations_0.csv"
20
21R_SMALL = 0.001
22R_LARGE = 0.003
23MESH_SIZE = R_SMALL / 5.0
24HORIZON = 2.0 * MESH_SIZE
25R_IN = 0.02
26R_OUT = R_IN + 1.5 * MESH_SIZE
27L_BAR = 0.005
28W_BAR = 1.5 * MESH_SIZE
29R_REF = {
30 0: R_SMALL,
31 1: R_SMALL,
32 2: R_SMALL,
33 3: R_SMALL,
34 4: R_LARGE,
35 5: R_LARGE,
36 6: R_LARGE,
37 7: R_LARGE,
38}
39
40MESH_FILES = [
41 "mesh_cir_small_0.msh",
42 "mesh_tri_small_0.msh",
43 "mesh_drum2d_small_0.msh",
44 "mesh_hex_small_0.msh",
45 "mesh_cir_large_0.msh",
46 "mesh_tri_large_0.msh",
47 "mesh_drum2d_large_0.msh",
48 "mesh_hex_large_0.msh",
49 "mesh_wall_0.msh",
50]
51
52WALL_PARAMS = [
53 R_OUT,
54 0.0,
55 0.0,
56 0.0,
57 R_IN,
58 0.0,
59 0.0,
60 0.0,
61 R_IN - L_BAR,
62 -0.5 * W_BAR,
63 0.0,
64 R_IN,
65 0.5 * W_BAR,
66 0.0,
67]
68
69KN = {
70 (0, 0): 5.595291e21,
71 (0, 1): 1.017326e22,
72 (0, 2): 1.017326e22,
73 (1, 1): 5.595291e22,
74 (1, 2): 5.595291e22,
75 (2, 2): 5.595291e22,
76}
77K_MAT = {0: 1.0e4, 1: 1.0e5, 2: 1.0e5}
78
79# Explicit — never omit (modular defaults differ from main)
80KN_FACTOR = 1.0
81BETA_N_FACTOR = 100.0
82EPSILON = 0.95
83CONTACT_RADIUS_FACTOR = 0.95
84FRICTION_COEFF = 0.5
85DAMPING_ON = False
86FRICTION_ON = False
87DAMPING_LAW = "off"
88FRICTION_LAW = "coulomb_simple"
89CORRECT_VOLUME = False # main DEM contact used full Vj
90SEARCH_INTERVAL = 40
91SEARCH_FACTOR = 10.0
92NEAR_BD_TOL = 0.5
93BOND_BREAK = "tension"
94# Main applies broken-bond self-contact inside PD; modular name:
95SELF_CONTACT = "broken_bond_kn"
96WALL_CONTACT = "meshed"
97RANDOM_ROTATION = False
98OMEGA = -40.0 * math.pi
99ROT_CENTER = (-0.2 * R_IN, 0.2 * R_IN, 0.0)
100
101
102def wall_geom_center(params: list[float]) -> tuple[float, float, float]:
103 r_out, cx0, cy0, cz0 = params[0], params[1], params[2], params[3]
104 r_in, cx1, cy1, cz1 = params[4], params[5], params[6], params[7]
105 x0, y0, z0, x1, y1, z1 = params[8:14]
106 a_out = math.pi * r_out * r_out
107 a_in = math.pi * r_in * r_in
108 a_rect = abs(x1 - x0) * abs(y1 - y0)
109 vol = a_out - a_in + a_rect
110 cx = (a_out * cx0 - a_in * cx1 + a_rect * 0.5 * (x0 + x1)) / vol
111 cy = (a_out * cy0 - a_in * cy1 + a_rect * 0.5 * (y0 + y1)) / vol
112 cz = (a_out * cz0 - a_in * cz1 + a_rect * 0.5 * (z0 + z1)) / vol
113 return cx, cy, cz
114
115
116def contact_pair(i: int, j: int) -> dict:
117 a, b = min(i, j), max(i, j)
118 return {
119 "Contact_Radius_Factor": CONTACT_RADIUS_FACTOR,
120 "Damping_On": DAMPING_ON,
121 "Friction_On": FRICTION_ON,
122 "Kn": KN[(a, b)],
123 "K": 2.0 * K_MAT[a] * K_MAT[b] / (K_MAT[a] + K_MAT[b]),
124 "Epsilon": EPSILON,
125 "Friction_Coeff": FRICTION_COEFF,
126 "Kn_Factor": KN_FACTOR,
127 "Beta_n_Factor": BETA_N_FACTOR,
128 }
129
130
131def material(horizon: float, K: float, G: float, Gc: float) -> dict:
132 return {
133 "Type": "PDState",
134 "Horizon": horizon,
135 "Density": 1200.0,
136 "Compute_From_Classical": True,
137 "Is_Plain_Strain": False,
138 "K": K,
139 "G": G,
140 "Gc": Gc,
141 "Influence_Function": {"Type": 1},
142 }
143
144
145def validate_ic(rows: list[tuple]) -> None:
146 bar = (R_IN - L_BAR, -0.5 * W_BAR, R_IN, 0.5 * W_BAR)
147 errors: list[str] = []
148 for i, (zi, x, y, z, r, o) in enumerate(rows):
149 if zi not in R_REF:
150 errors.append(f"row {i}: bad zone {zi}")
151 continue
152 if math.hypot(x, y) + r > R_IN - 1.0e-9:
153 errors.append(
154 f"row {i} zone={zi}: past R_in (hyp+r={math.hypot(x, y) + r:.6e})"
155 )
156 if not (x + r < bar[0] or x - r > bar[2] or y + r < bar[1] or y - r > bar[3]):
157 errors.append(f"row {i} zone={zi}: overlaps protrusion AABB")
158 for i in range(len(rows)):
159 for j in range(i + 1, len(rows)):
160 dx = rows[i][1] - rows[j][1]
161 dy = rows[i][2] - rows[j][2]
162 if math.hypot(dx, dy) < rows[i][4] + rows[j][4] - 1.0e-9:
163 errors.append(f"overlap rows {i},{j}")
164 if errors:
165 raise SystemExit("IC invalid:\n " + "\n ".join(errors[:40]))
166
167
169 final_time: float,
170 time_steps: int,
171 out_path: str,
172 output_interval: int,
173) -> dict:
174 particles = []
175 rows = []
176 with CSV.open() as f:
177 next(f)
178 for line in f:
179 parts = [p.strip() for p in line.split(",")]
180 if len(parts) < 6:
181 continue
182 zi = int(float(parts[0]))
183 x, y, z = float(parts[1]), float(parts[2]), float(parts[3])
184 r, o = float(parts[4]), float(parts[5])
185 rows.append((zi, x, y, z, r, o))
186 large = zi >= 4
187 particles.append(
188 {
189 "x": x,
190 "y": y,
191 "z": z,
192 "theta": o,
193 "s": r / R_REF[zi],
194 "geom_id": zi,
195 "mat_id": 1 if large else 0,
196 "contact_id": 1 if large else 0,
197 }
198 )
199
200 validate_ic(rows)
201
202 wall_id = len(particles)
203 wcx, wcy, wcz = wall_geom_center(WALL_PARAMS)
204 particles.append(
205 {
206 "x": wcx,
207 "y": wcy,
208 "z": wcz,
209 "theta": 0.0,
210 "s": 1.0,
211 "geom_id": 8,
212 "mat_id": 1,
213 "contact_id": 2,
214 "is_wall": True, # no PD on wall; DOFs locked by Displacement_BC
215 }
216 )
217
218 gen = {
219 "Method": "From_File",
220 "Random_Rotation": RANDOM_ROTATION,
221 "Data": {"N": len(particles)},
222 }
223 for i, p in enumerate(particles):
224 gen["Data"][str(i)] = p
225
226 particle_geom = {
227 "Sets": 9,
228 "Set_1": {"Type": "circle", "Parameters": [R_SMALL, 0.0, 0.0, 0.0]},
229 "Set_2": {"Type": "triangle", "Parameters": [R_SMALL, 0.0, 0.0, 0.0]},
230 "Set_3": {
231 "Type": "drum2d",
232 "Parameters": [R_SMALL, R_SMALL * 0.4, 0.0, 0.0, 0.0],
233 },
234 "Set_4": {"Type": "hexagon", "Parameters": [R_SMALL, 0.0, 0.0, 0.0]},
235 "Set_5": {"Type": "circle", "Parameters": [R_LARGE, 0.0, 0.0, 0.0]},
236 "Set_6": {"Type": "triangle", "Parameters": [R_LARGE, 0.0, 0.0, 0.0]},
237 "Set_7": {
238 "Type": "drum2d",
239 "Parameters": [R_LARGE, R_LARGE * 0.4, 0.0, 0.0, 0.0],
240 },
241 "Set_8": {"Type": "hexagon", "Parameters": [R_LARGE, 0.0, 0.0, 0.0]},
242 "Set_9": {
243 "Type": "complex",
244 "Vec_type": ["circle", "circle", "rectangle"],
245 "Vec_flag": ["plus", "minus", "plus"],
246 "Parameters": WALL_PARAMS,
247 },
248 }
249
250 mesh = {"Sets": 9}
251 for i, name in enumerate(MESH_FILES):
252 mesh[f"Set_{i + 1}"] = {"File": str((MESH / name).resolve())}
253
254 contact = {
255 "Sets": 3,
256 "Damping_Law": DAMPING_LAW,
257 "Friction_Law": FRICTION_LAW,
258 "Correct_Volume": CORRECT_VOLUME,
259 }
260 for i in range(3):
261 for j in range(i, 3):
262 contact[f"Set_{i + 1}_{j + 1}"] = contact_pair(i, j)
263
264 return {
265 "Model": {
266 "Dimension": 2,
267 "Final_Time": final_time,
268 "Time_Steps": time_steps,
269 "Discretization_Type": {
270 "Spatial": "finite_difference",
271 "Time": "central_difference",
272 },
273 "Populate_ElementNodeConnectivity": True,
274 "Quad_Approximation_Order": 2,
275 "Particle_Sim_Type": "Multi_Particle",
276 "MPI_Strategy": "auto",
277 "Seed": 0,
278 "Bond_Break": BOND_BREAK,
279 "Self_Contact": SELF_CONTACT,
280 "Wall_Contact": WALL_CONTACT,
281 },
282 "Output": {
283 "Path": out_path if out_path.endswith("/") else out_path + "/",
284 "Perform_Out": True,
285 "Tags": [
286 "Displacement",
287 "Velocity",
288 "Force",
289 "Damage_Z",
290 "Damage",
291 "Particle_ID",
292 "Fixity",
293 "Contact_Nodes",
294 ],
295 "Output_Interval": output_interval,
296 "Debug": 3,
297 "Perform_FE_Out": False,
298 "Compress_Type": "zlib",
299 "File_Format": "vtu",
300 "Test_Output_Interval": max(1, output_interval // 100),
301 "Tag_PP": "0",
302 "PVD_Collection": True,
303 },
304 "Force_BC": {"Gravity": [0.0, -10.0, 0.0]},
305 "Displacement_BC": {
306 "Sets": 1,
307 "Set_1": {
308 "Particle_List": [wall_id],
309 "Direction": [1, 2],
310 "Time_Function": {
311 "Type": "rotation",
312 "Parameters": [OMEGA, ROT_CENTER[0], ROT_CENTER[1], ROT_CENTER[2]],
313 },
314 "Spatial_Function": {"Type": "rotation"},
315 "Zero_Displacement": False,
316 },
317 },
318 "Particle": particle_geom,
319 "Mesh": mesh,
320 "Material": {
321 "Sets": 2,
322 "Set_1": material(HORIZON, 1.0e4, 6.0e3, 50.0),
323 "Set_2": material(HORIZON, 1.0e5, 6.0e4, 100.0),
324 },
325 "Contact": contact,
326 "Neighbor": {
327 "Update_Criteria": "simple_all",
328 "Search_Factor": SEARCH_FACTOR,
329 "Search_Interval": SEARCH_INTERVAL,
330 "Near_Bd_Nodes_Tol": NEAR_BD_TOL,
331 },
332 "Particle_Generation": gen,
333 }
334
335
336def main() -> None:
337 for mesh in MESH_FILES:
338 if not (MESH / mesh).is_file():
339 raise SystemExit(f"missing mesh {MESH / mesh}")
340 if not CSV.is_file():
341 raise SystemExit(f"missing {CSV}")
342
343 short = build(0.01, 100000, "runs/out", 2000)
344 medium = build(0.03, 300000, "runs/out", 3000)
345 paper = build(0.1, 1000000, "runs/out", 2500)
346 (HERE / "input_short.json").write_text(json.dumps(short, indent=2) + "\n")
347 (HERE / "input_medium.json").write_text(json.dumps(medium, indent=2) + "\n")
348 (HERE / "input.json").write_text(json.dumps(paper, indent=2) + "\n")
349 n = short["Particle_Generation"]["Data"]["N"]
350 wid = short["Displacement_BC"]["Set_1"]["Particle_List"][0]
351 print(
352 f"wrote decks N={n} wall_id={wid} Self_Contact={SELF_CONTACT} "
353 f"DampLaw={DAMPING_LAW} DampOn={DAMPING_ON} SearchInt={SEARCH_INTERVAL} "
354 f"KnF={KN_FACTOR} BetaN={BETA_N_FACTOR} Bond={BOND_BREAK}"
355 )
356 print("Wall DOFs via Displacement_BC; see INPUT_DEFAULTS.md for contact gaps.")
357
358
359if __name__ == "__main__":
360 main()
dict build(float final_time, int time_steps, str out_path, int output_interval, float omega=-20.0 *math.pi)
Definition gen_input.py:114
None main()
Definition gen_input.py:269
tuple[float, float, float] wall_geom_center(list[float] params)
Definition gen_input.py:65
None validate_ic(list[tuple] rows)
Definition gen_input.py:145
dict contact_pair(int i, int j)
Definition gen_input.py:80