@@ -71,6 +71,9 @@ class EdgeFeatureCache(NamedTuple):
7171 Used for efficient batched rotation. None if not available.
7272 Dt_full
7373 Transpose of D_full with shape (E, D, D). None if not available.
74+ edge_quat
75+ Per-edge global-to-local quaternion actually used to build ``D_full`` and
76+ ``Dt_full`` with shape (E, 4). Includes the optional random local-Z roll.
7477 D_to_m_cache
7578 Lazy cache for projected D matrices keyed by a normalized
7679 ``"lmax:mmax"`` identifier.
@@ -103,6 +106,7 @@ class EdgeFeatureCache(NamedTuple):
103106 D_to_m_cache : dict [str , torch .Tensor ] | None = None
104107 Dt_from_m_cache : dict [str , torch .Tensor ] | None = None
105108 edge_src_gate : torch .Tensor | None = None
109+ edge_quat : torch .Tensor | None = None
106110
107111
108112def compute_edge_src_gate (
@@ -337,13 +341,13 @@ def build_edge_cache(
337341
338342 # === Step 6. Edge quaternion -> Wigner-D blocks ===
339343 with nvtx_range ("wigner_d" ):
340- D_full , Dt_full = _build_edge_wigner (
344+ D_full , Dt_full , edge_quat = _build_edge_wigner (
341345 edge_vec = edge_vec ,
342346 edge_len = edge_len ,
343347 eps = eps ,
344348 random_gamma = random_gamma ,
345349 wigner_calc = wigner_calc ,
346- ) # (E, D, D), (E, D, D)
350+ ) # (E, D, D), (E, D, D), (E, 4)
347351
348352 edge_type_feat = build_edge_type_feat (type_ebed , src , dst ) # (E, C)
349353
@@ -357,6 +361,7 @@ def build_edge_cache(
357361 edge_env = edge_env ,
358362 D_full = D_full ,
359363 Dt_full = Dt_full ,
364+ edge_quat = edge_quat ,
360365 deg_norm_floor = deg_norm_floor ,
361366 )
362367
@@ -460,13 +465,13 @@ def build_edge_cache_from_edges(
460465
461466 # === Step 4. Edge quaternion -> Wigner-D blocks ===
462467 with nvtx_range ("wigner_d" ):
463- D_full , Dt_full = _build_edge_wigner (
468+ D_full , Dt_full , edge_quat = _build_edge_wigner (
464469 edge_vec = edge_vec ,
465470 edge_len = edge_len ,
466471 eps = eps ,
467472 random_gamma = random_gamma ,
468473 wigner_calc = wigner_calc ,
469- ) # (E, D, D), (E, D, D)
474+ ) # (E, D, D), (E, D, D), (E, 4)
470475
471476 # === Step 5. Edge type features ===
472477 edge_type_feat = build_edge_type_feat (type_ebed , src , dst )
@@ -498,6 +503,7 @@ def build_edge_cache_from_edges(
498503 edge_env = edge_env ,
499504 D_full = D_full ,
500505 Dt_full = Dt_full ,
506+ edge_quat = edge_quat ,
501507 deg_norm_floor = deg_norm_floor ,
502508 edge_src_gate = edge_src_gate ,
503509 )
@@ -510,7 +516,7 @@ def _build_edge_wigner(
510516 eps : float ,
511517 random_gamma : bool ,
512518 wigner_calc : WignerCalculatorFn ,
513- ) -> tuple [torch .Tensor , torch .Tensor ]:
519+ ) -> tuple [torch .Tensor , torch .Tensor , torch . Tensor ]:
514520 """
515521 Build packed Wigner-D blocks from edge vectors.
516522
@@ -530,8 +536,9 @@ def _build_edge_wigner(
530536
531537 Returns
532538 -------
533- tuple[torch.Tensor, torch.Tensor]
534- Packed Wigner-D matrices ``(D_full, Dt_full)`` with shape ``(E, D, D)``.
539+ tuple[torch.Tensor, torch.Tensor, torch.Tensor]
540+ Packed Wigner-D matrices ``(D_full, Dt_full)`` with shape ``(E, D, D)``
541+ and the quaternion used to build them with shape ``(E, 4)``.
535542 """
536543 # === Step 1. Build edge-aligned quaternions ===
537544 edge_quat = build_edge_quaternion (
@@ -550,7 +557,8 @@ def _build_edge_wigner(
550557 edge_quat = quaternion_multiply (quaternion_z_rotation (gamma ), edge_quat )
551558
552559 # === Step 3. Convert quaternions to packed Wigner-D blocks ===
553- return wigner_calc (edge_quat )
560+ D_full , Dt_full = wigner_calc (edge_quat )
561+ return D_full , Dt_full , edge_quat
554562
555563
556564def _finalize_edge_cache (
@@ -564,6 +572,7 @@ def _finalize_edge_cache(
564572 edge_env : torch .Tensor ,
565573 D_full : torch .Tensor ,
566574 Dt_full : torch .Tensor ,
575+ edge_quat : torch .Tensor ,
567576 deg_norm_floor : float ,
568577 edge_src_gate : torch .Tensor | None = None ,
569578) -> EdgeFeatureCache :
@@ -590,6 +599,9 @@ def _finalize_edge_cache(
590599 Packed Wigner-D matrices with shape (E, D, D).
591600 Dt_full
592601 Transposed packed Wigner-D matrices with shape (E, D, D).
602+ edge_quat
603+ Global-to-local quaternions used to build the Wigner-D matrices with
604+ shape (E, 4).
593605 deg_norm_floor
594606 Floor added to the envelope-squared degree before the inverse-sqrt
595607 normalization. A tiny ``eps`` reproduces the legacy behavior; an
@@ -627,6 +639,7 @@ def _finalize_edge_cache(
627639 D_to_m_cache = {},
628640 Dt_from_m_cache = {},
629641 edge_src_gate = edge_src_gate ,
642+ edge_quat = edge_quat ,
630643 )
631644
632645
@@ -661,6 +674,7 @@ def _get_empty_edge_cache(
661674 """
662675 empty_long = torch .empty (0 , dtype = torch .long , device = device )
663676 empty_vec = torch .empty (0 , 3 , dtype = dtype , device = device )
677+ empty_quat = torch .empty (0 , 4 , dtype = dtype , device = device )
664678 empty_rbf = torch .empty (0 , n_radial , dtype = dtype , device = device )
665679 empty_type_feat = torch .empty (0 , n_channel , dtype = dtype , device = device )
666680 deg = torch .zeros (n_nodes , dtype = dtype , device = device )
@@ -679,6 +693,7 @@ def _get_empty_edge_cache(
679693 D_to_m_cache = {},
680694 Dt_from_m_cache = {},
681695 edge_src_gate = None ,
696+ edge_quat = empty_quat ,
682697 )
683698
684699
@@ -835,15 +850,19 @@ def edge_cache_to_dtype(
835850 _D_full = cache .D_full
836851 _Dt_full = cache .Dt_full
837852 _edge_src_gate = cache .edge_src_gate
853+ _edge_quat = cache .edge_quat
838854 D_full : torch .Tensor | None = None
839855 Dt_full : torch .Tensor | None = None
840856 edge_src_gate : torch .Tensor | None = None
857+ edge_quat : torch .Tensor | None = None
841858 if _D_full is not None :
842859 D_full = _D_full .to (dtype = dtype )
843860 if _Dt_full is not None :
844861 Dt_full = _Dt_full .to (dtype = dtype )
845862 if _edge_src_gate is not None :
846863 edge_src_gate = _edge_src_gate .to (dtype = dtype )
864+ if _edge_quat is not None :
865+ edge_quat = _edge_quat .to (dtype = dtype )
847866
848867 return EdgeFeatureCache (
849868 src = cache .src ,
@@ -859,4 +878,5 @@ def edge_cache_to_dtype(
859878 D_to_m_cache = None if cache .D_to_m_cache is None else {},
860879 Dt_from_m_cache = None if cache .Dt_from_m_cache is None else {},
861880 edge_src_gate = edge_src_gate ,
881+ edge_quat = edge_quat ,
862882 )
0 commit comments