95def build_deck(output_path: str | os.PathLike[str] =
"runs/out/", *,
96 dim3: bool =
False, quick: bool =
False,
97 final_time: float |
None =
None, dt: float |
None =
None,
98 num_steps: int |
None =
None,
99 mesh_dir: str | os.PathLike[str] |
None =
None,
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)
106 cfg[
"notch_depth"] = 0.5 * cfg[
"H"]
107 cfg[
"notch_w"] = max(cfg[
"mesh_size"], 0.1 * cfg[
"notch_half"])
109 cfg[
"notch_depth"] = NOTCH_DEPTH
110 cfg[
"notch_w"] = NOTCH_W
112 if final_time
is not None:
113 cfg[
"final_time"] = final_time
116 n_steps = (int(round(cfg[
"final_time"] / cfg[
"dt"]))
if num_steps
is None
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"],
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))
130 d = Deck(dim=3
if dim3
else 2, t_final=cfg[
"final_time"], n_steps=n_steps,
133 populate_element_node_connectivity=
not dim3,
134 self_contact=
"none", bond_break=
"tension", wall_contact=
"meshed")
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)
144 plate = Geometry(
"cuboid", [-0.5 * w, -0.5 * h, -0.5 * THICKNESS,
145 0.5 * w, 0.5 * h, 0.5 * THICKNESS])
147 impactor = Geometry(
"cylinder", [0.5 * iw, 0.0, -0.5 * ih, 0.0,
149 z_lo, z_hi = -0.5 * THICKNESS - 1.0e-9, 0.5 * THICKNESS + 1.0e-9
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
158 base = Path(mesh_dir)
if mesh_dir
is not None else HERE /
"runs/inp"
161 g_plate = d.add_particle_type(
162 plate, MeshSpec(file=base /
"mesh_plate.msh", size=mesh_size,
163 info=
"uniform", voids=voids))
166 g_imp = d.add_particle_type(
168 MeshSpec(file=base /
"mesh_impactor.msh", size=mesh_size,
169 info=
"gmsh_builtin_mesh" if dim3
else "uniform"))
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])
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")
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])
198 d.add_rigid_particle(1, IMPACTOR_MASS
if dim3
else IMPACTOR_MASS / THICKNESS)
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)
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"]}
229 threshold: float = 0.30) -> np.ndarray:
230 """Drive the time loop from Python, recording first-damage time per node.
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.
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)
240 def sample() -> None:
241 fresh = (arrival < 0.0) & plate & (sim.damage >= threshold)
242 arrival[fresh] = sim.time
244 sim.apply_initial_condition()
245 if sim.perform_output:
247 sim.set_current_dt(sim.dt)
248 sim.apply_displacement_bc()
250 sim.apply_rigid_body_constraint()
251 while sim.step_index < sim.n_steps:
253 if sim.should_output:
255 if sim.step_index % sample_every == 0: