2"""Build modular JSON for attrition sim2 — explicit keys, match main GIF deck.
5 examples/PeriDEM/attrition_tests/sim2_multi_particle_circ_tri_drum_hex_with_rotating_cylinder_with_protrusion_thin_container_and_change_rotation_rate
7Every field that has a dangerous modular default is set explicitly (see
8INPUT_DEFAULTS.md). No reliance on omitted-key defaults.
11from __future__
import annotations
15from pathlib
import Path
17HERE = Path(__file__).resolve().parent
19CSV = HERE /
"particle_locations_0.csv"
23MESH_SIZE = R_SMALL / 5.0
24HORIZON = 2.0 * MESH_SIZE
26R_OUT = R_IN + 1.5 * MESH_SIZE
28W_BAR = 1.5 * MESH_SIZE
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",
77K_MAT = {0: 1.0e4, 1: 1.0e5, 2: 1.0e5}
83CONTACT_RADIUS_FACTOR = 0.95
88FRICTION_LAW =
"coulomb_simple"
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)
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
117 a, b = min(i, j), max(i, j)
119 "Contact_Radius_Factor": CONTACT_RADIUS_FACTOR,
120 "Damping_On": DAMPING_ON,
121 "Friction_On": FRICTION_ON,
123 "K": 2.0 * K_MAT[a] * K_MAT[b] / (K_MAT[a] + K_MAT[b]),
125 "Friction_Coeff": FRICTION_COEFF,
126 "Kn_Factor": KN_FACTOR,
127 "Beta_n_Factor": BETA_N_FACTOR,
131def material(horizon: float, K: float, G: float, Gc: float) -> dict:
136 "Compute_From_Classical":
True,
137 "Is_Plain_Strain":
False,
141 "Influence_Function": {
"Type": 1},
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):
150 errors.append(f
"row {i}: bad zone {zi}")
152 if math.hypot(x, y) + r > R_IN - 1.0e-9:
154 f
"row {i} zone={zi}: past R_in (hyp+r={math.hypot(x, y) + r:.6e})"
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}")
165 raise SystemExit(
"IC invalid:\n " +
"\n ".join(errors[:40]))
172 output_interval: int,
176 with CSV.open()
as f:
179 parts = [p.strip()
for p
in line.split(
",")]
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))
195 "mat_id": 1
if large
else 0,
196 "contact_id": 1
if large
else 0,
202 wall_id = len(particles)
219 "Method":
"From_File",
220 "Random_Rotation": RANDOM_ROTATION,
221 "Data": {
"N": len(particles)},
223 for i, p
in enumerate(particles):
224 gen[
"Data"][str(i)] = p
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]},
232 "Parameters": [R_SMALL, R_SMALL * 0.4, 0.0, 0.0, 0.0],
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]},
239 "Parameters": [R_LARGE, R_LARGE * 0.4, 0.0, 0.0, 0.0],
241 "Set_8": {
"Type":
"hexagon",
"Parameters": [R_LARGE, 0.0, 0.0, 0.0]},
244 "Vec_type": [
"circle",
"circle",
"rectangle"],
245 "Vec_flag": [
"plus",
"minus",
"plus"],
246 "Parameters": WALL_PARAMS,
251 for i, name
in enumerate(MESH_FILES):
252 mesh[f
"Set_{i + 1}"] = {
"File": str((MESH / name).resolve())}
256 "Damping_Law": DAMPING_LAW,
257 "Friction_Law": FRICTION_LAW,
258 "Correct_Volume": CORRECT_VOLUME,
261 for j
in range(i, 3):
267 "Final_Time": final_time,
268 "Time_Steps": time_steps,
269 "Discretization_Type": {
270 "Spatial":
"finite_difference",
271 "Time":
"central_difference",
273 "Populate_ElementNodeConnectivity":
True,
274 "Quad_Approximation_Order": 2,
275 "Particle_Sim_Type":
"Multi_Particle",
276 "MPI_Strategy":
"auto",
278 "Bond_Break": BOND_BREAK,
279 "Self_Contact": SELF_CONTACT,
280 "Wall_Contact": WALL_CONTACT,
283 "Path": out_path
if out_path.endswith(
"/")
else out_path +
"/",
295 "Output_Interval": output_interval,
297 "Perform_FE_Out":
False,
298 "Compress_Type":
"zlib",
299 "File_Format":
"vtu",
300 "Test_Output_Interval": max(1, output_interval // 100),
302 "PVD_Collection":
True,
304 "Force_BC": {
"Gravity": [0.0, -10.0, 0.0]},
308 "Particle_List": [wall_id],
312 "Parameters": [OMEGA, ROT_CENTER[0], ROT_CENTER[1], ROT_CENTER[2]],
314 "Spatial_Function": {
"Type":
"rotation"},
315 "Zero_Displacement":
False,
318 "Particle": particle_geom,
322 "Set_1":
material(HORIZON, 1.0e4, 6.0e3, 50.0),
323 "Set_2":
material(HORIZON, 1.0e5, 6.0e4, 100.0),
327 "Update_Criteria":
"simple_all",
328 "Search_Factor": SEARCH_FACTOR,
329 "Search_Interval": SEARCH_INTERVAL,
330 "Near_Bd_Nodes_Tol": NEAR_BD_TOL,
332 "Particle_Generation": gen,
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}")
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]
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}"
356 print(
"Wall DOFs via Displacement_BC; see INPUT_DEFAULTS.md for contact gaps.")
359if __name__ ==
"__main__":