|
3 | 3 |
|
4 | 4 | from deepmd.dpmodel.utils.neighbor_graph import ( |
5 | 5 | GraphLayout, |
| 6 | + angle_padding_fraction, |
6 | 7 | angle_to_edge_sum, |
7 | 8 | angle_to_node_sum, |
8 | 9 | attach_angles, |
9 | 10 | build_angle_index, |
10 | 11 | build_neighbor_graph, |
| 12 | + edge_force_virial, |
11 | 13 | pad_and_guard_angles, |
12 | 14 | ) |
13 | 15 |
|
@@ -358,3 +360,75 @@ def test_angle_aggregation_torch_namespace(): |
358 | 360 | # compare |
359 | 361 | np.testing.assert_allclose(np.asarray(e_t), e_np) |
360 | 362 | np.testing.assert_allclose(np.asarray(n_t), n_np) |
| 363 | + |
| 364 | + |
| 365 | +# --------------------------------------------------------------------------- |
| 366 | +# Task 6: angle-force invariance + angle_padding_fraction |
| 367 | +# --------------------------------------------------------------------------- |
| 368 | + |
| 369 | + |
| 370 | +def _small_graph_with_angles(a_rcut: float, layout: GraphLayout | None = None): |
| 371 | + """Return (graph_no_angles, graph_with_angles) for a 3-atom, 1-frame system.""" |
| 372 | + # 3 atoms: 0,1,2 in a line along x; shape (nf=1, nloc=3, 3) / (nf=1, nloc=3) |
| 373 | + coord = np.array([[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]]]) |
| 374 | + atype = np.array([[0, 1, 0]], dtype=np.int64) |
| 375 | + box = np.array([[[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]]) |
| 376 | + g = build_neighbor_graph(coord, atype, box, rcut=3.0) |
| 377 | + g_with = attach_angles(g, a_rcut, layout=layout) |
| 378 | + return g, g_with |
| 379 | + |
| 380 | + |
| 381 | +def test_edge_force_virial_ignores_angles(): |
| 382 | + """edge_force_virial output must be bit-identical with or without angles. |
| 383 | +
|
| 384 | + Angles add topology (angle_index/angle_mask) to the NeighborGraph but do |
| 385 | + NOT change edge_vec, edge_index, or edge_mask — the only inputs to |
| 386 | + edge_force_virial. This test proves that the angle fields are truly |
| 387 | + transparent to the force/virial assembly. |
| 388 | + """ |
| 389 | + g_bare, g_with_angles = _small_graph_with_angles(a_rcut=1.5) |
| 390 | + |
| 391 | + # Manufacture a fake per-edge gradient (same shape as edge_vec) |
| 392 | + rng = np.random.default_rng(42) |
| 393 | + n_edges = int(g_bare.edge_index.shape[1]) |
| 394 | + g_e = rng.standard_normal((n_edges, 3)) |
| 395 | + |
| 396 | + def run(graph): |
| 397 | + return edge_force_virial( |
| 398 | + g_e, |
| 399 | + graph.edge_vec, |
| 400 | + graph.edge_index, |
| 401 | + graph.edge_mask, |
| 402 | + graph.n_node, |
| 403 | + ) |
| 404 | + |
| 405 | + force_bare, av_bare, vir_bare = run(g_bare) |
| 406 | + force_with, av_with, vir_with = run(g_with_angles) |
| 407 | + |
| 408 | + # Exact equality: same inputs → same computation → identical bits |
| 409 | + np.testing.assert_array_equal(force_bare, force_with) |
| 410 | + np.testing.assert_array_equal(av_bare, av_with) |
| 411 | + np.testing.assert_array_equal(vir_bare, vir_with) |
| 412 | + |
| 413 | + |
| 414 | +def test_angle_padding_fraction(): |
| 415 | + """angle_padding_fraction returns 1 - A_real/A_max for a static layout. |
| 416 | +
|
| 417 | + We build with a fixed angle_capacity=A_max so the fraction is deterministic |
| 418 | + (not influenced by the dynamic min_angles guard of pad_and_guard_angles). |
| 419 | + """ |
| 420 | + A_max = 20 # static capacity, larger than any real angle count |
| 421 | + layout = GraphLayout(angle_capacity=A_max) |
| 422 | + g_bare, g_with = _small_graph_with_angles(a_rcut=1.5, layout=layout) |
| 423 | + |
| 424 | + # Confirm angles are present |
| 425 | + assert g_with.angle_mask is not None |
| 426 | + A_real = int(np.sum(g_with.angle_mask)) |
| 427 | + assert 0 < A_real <= A_max, f"Expected 0 < A_real <= {A_max}, got {A_real}" |
| 428 | + |
| 429 | + expected = 1.0 - A_real / A_max |
| 430 | + got = angle_padding_fraction(g_with) |
| 431 | + assert got == pytest.approx(expected), f"got {got}, expected {expected}" |
| 432 | + |
| 433 | + # No angles → fraction is 0.0 |
| 434 | + assert angle_padding_fraction(g_bare) == 0.0 |
0 commit comments