Add PdgRouter for particle-type-based expert gating
Routes on the pre-step PDG code, which — unlike ProcessRouter's process label — is already known at gate time (a conditioning input), so no supervision is needed and classify_loss falls back to the zero default. Generalizes EnergyRouter's soft-turn-on-then-Voronoi trick from a 1-D distance to a small learned PDG embedding space: its own embedding table maps each PDG code to a point, and n_experts learnable centers partition that space. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -344,6 +344,48 @@ class EnergyRouter(Router):
|
||||
return torch.softmax(-d2 / self.temperature, dim=-1)
|
||||
|
||||
|
||||
@register_router("pdg")
|
||||
class PdgRouter(Router):
|
||||
"""Soft turn-on gate over a learned PDG embedding.
|
||||
|
||||
Unlike ProcessRouter's process label, PDG code is already known at
|
||||
pre-step time (it's a conditioning input, `cond_cat[:, 0]`), so no
|
||||
supervision is needed — `classify_loss` falls back to the Router base
|
||||
class's zero-loss default, same as EnergyRouter. Because PDG is
|
||||
categorical rather than a scalar, this generalizes EnergyRouter's
|
||||
soft-turn-on-then-Voronoi trick from a 1-D distance to a distance in a
|
||||
small embedding space: its own embedding table (kept separate from the
|
||||
trunk's ConditionEncoder, same reasoning as ProcessRouter's own
|
||||
pdg/mat embeddings) maps each PDG code to a point, and `n_experts`
|
||||
learnable (or fixed) centers partition that space.
|
||||
`gate(pdg) = softmax_i(-||emb(pdg) - c_i||^2 / tau)`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
n_experts: int,
|
||||
pdg_vocab: int,
|
||||
emb_dim: int = 8,
|
||||
temperature: float = 0.5,
|
||||
learn_centers: bool = True,
|
||||
) -> None:
|
||||
super().__init__(n_experts)
|
||||
self.temperature = temperature
|
||||
self.pdg_emb = nn.Embedding(pdg_vocab, emb_dim)
|
||||
centers = torch.randn(n_experts, emb_dim) * 0.1
|
||||
if learn_centers:
|
||||
self.centers = nn.Parameter(centers)
|
||||
else:
|
||||
self.register_buffer("centers", centers)
|
||||
|
||||
def gate(self, cond_cont: torch.Tensor, cond_cat: torch.Tensor) -> torch.Tensor:
|
||||
e = self.pdg_emb(cond_cat[:, 0]) # (B, emb_dim)
|
||||
d2 = ((e.unsqueeze(1) - self.centers.unsqueeze(0)) ** 2).sum(
|
||||
-1
|
||||
) # (B, n_experts)
|
||||
return torch.softmax(-d2 / self.temperature, dim=-1)
|
||||
|
||||
|
||||
@register_router("process")
|
||||
class ProcessRouter(Router):
|
||||
"""Routes on the physics process expected to end the step.
|
||||
|
||||
Reference in New Issue
Block a user