RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
edge_classes.py
Go to the documentation of this file.
1"""
2core/edge_classes.py — The ten abstract reaction-edge classes (Layer 1).
3
4Host-independent. Implements the elementary edge-class catalogue of
5Ghoniem (2026), *A Generalized Graph-Based Cluster Dynamics Framework*,
6Section 2.2.
7
8Every edge of a reaction admissibility graph (RAG) falls into exactly one
9of ten classes, identified by the pattern of polarity and population at
10its endpoints and by the form of its stoichiometric tuple. This module
11defines the class taxonomy and, for each class, the *abstract
12stoichiometric contract* — how many precursor vertices it has, whether it
13crosses the polarity layers, and how it shifts size / population /
14composition. The concrete, size-resolved rate kernels are supplied by a
15host material (Layer 2); the abstract core only fixes the contract that
16every host edge of a given class must obey.
17
18The graph-walker assembles the master-equation right-hand side
19
20 dc_a/dt = G_a + sum_r S_ar J_r(c) - D_a c_a
21
22by iterating edges; each edge contributes its signed flux S_ar J_r to the
23affected vertices. Because the contract below fixes the sign pattern of
24S_ar per class, conservation of signed lattice-defect content is a
25structural property of the graph, not something re-derived per host.
26"""
27from __future__ import annotations
28
29from dataclasses import dataclass
30from enum import Enum
31
32
33class EdgeClass(Enum):
34 """The ten elementary reaction-edge classes (paper Section 2.2)."""
35
36 GROWTH = "growth" # 1. chi-monomer absorption, n -> n+1
37 SHRINKAGE = "shrinkage" # 2. (-chi)-monomer impingement, n -> n-1
38 DISSOCIATION = "dissociation" # 3. thermal chi-monomer emission, n -> n-1
39 RECOMBINATION = "recombination" # 4. cross-polarity monomer annihilation
40 ANNIHILATION = "annihilation" # 5. cross-polarity binary hyperedge
41 INTER_POPULATION = "inter_population" # 6. p -> p', size and composition fixed
42 SOLUTE_TRAPPING = "solute_trapping" # 7. c -> c +/- e_s (trap / detrap)
43 COALESCENCE = "coalescence" # 8. same-polarity binary growth
44 SOURCE = "source" # 9. cascade injection, no precursor
45 SINK = "sink" # 10. absorption at a fixed sink
46
47 def __str__(self) -> str:
48 return self.value
49
50
51@dataclass(frozen=True)
53 """The abstract stoichiometric contract obeyed by every edge of a class.
54
55 Attributes
56 ----------
57 n_precursor_clusters : int
58 Number of distinct *cluster* vertices consumed (0 for SOURCE).
59 kinetic_order : int
60 Order of the reaction flux J_r in concentration: 0 for a pure
61 source, 1 for a unary (first-order) transition, 2 for a binary
62 collision. SOLUTE_TRAPPING spans both directions — trapping is
63 binary (cavity x gas reservoir), detrapping is unary — and is
64 recorded here as order 2 for the forward (trapping) edge.
65 consumes_monomer : bool
66 True if the reaction consumes a same-polarity point-defect monomer
67 from its pool (GROWTH) — distinct from a tracked cluster vertex.
68 cross_polarity : bool
69 True if the edge couples the chi = -1 and chi = +1 layers.
70 size_shift : int | None
71 Change in size n of the (surviving) cluster: +1, -1, 0, or None
72 when it is not fixed by the class (ANNIHILATION, COALESCENCE,
73 SOURCE).
74 changes_population : bool
75 True if the edge moves the cluster between populations.
76 changes_composition : bool
77 True if the edge changes the trapped-solute composition vector.
78 creates_vertex : bool
79 True if the edge has no precursor cluster vertex (SOURCE).
80 destroys_to_sink : bool
81 True if the edge removes content to an untracked fixed sink (SINK).
82 description : str
83 One-line statement of the elementary transformation.
84 """
85
86 edge_class: EdgeClass
87 n_precursor_clusters: int
88 kinetic_order: int
89 consumes_monomer: bool
90 cross_polarity: bool
91 size_shift: "int | None"
92 changes_population: bool
93 changes_composition: bool
94 creates_vertex: bool
95 destroys_to_sink: bool
96 description: str
97
98
99# ── The fixed, host-independent contract for each of the ten classes ──────────
100EDGE_CLASS_SPEC: "dict[EdgeClass, EdgeClassSpec]" = {
101 EdgeClass.GROWTH: EdgeClassSpec(
102 EdgeClass.GROWTH, 1, 2, True, False, +1, False, False, False, False,
103 "(chi,n,p,c) -> (chi,n+1,p,c) by absorption of a chi-monomer"),
104 EdgeClass.SHRINKAGE: EdgeClassSpec(
105 EdgeClass.SHRINKAGE, 1, 2, False, True, -1, False, False, False, False,
106 "(chi,n,p,c) -> (chi,n-1,p,c) by impingement of a (-chi)-monomer"),
107 EdgeClass.DISSOCIATION: EdgeClassSpec(
108 EdgeClass.DISSOCIATION, 1, 1, False, False, -1, False, False, False, False,
109 "(chi,n,p,c) -> (chi,n-1,p,c) by thermal emission of a chi-monomer "
110 "into the monomer pool; kernel involves a binding energy"),
111 EdgeClass.RECOMBINATION: EdgeClassSpec(
112 EdgeClass.RECOMBINATION, 1, 2, False, True, -1, False, False, False, False,
113 "(chi,n,p,c) + (-chi,1) -> (chi,n-1,p,c); the (-chi)-monomer is "
114 "annihilated rather than returned to any pool"),
115 EdgeClass.ANNIHILATION: EdgeClassSpec(
116 EdgeClass.ANNIHILATION, 2, 2, True, True, None, False, False, False, False,
117 "(chi,n,p,c) + (-chi,n') -> (chi',|n-n'|,p_surv,c_surv); the "
118 "opposite-polarity analogue of coalescence (binary hyperedge)"),
119 EdgeClass.INTER_POPULATION: EdgeClassSpec(
120 EdgeClass.INTER_POPULATION, 1, 1, False, False, 0, True, False, False, False,
121 "(chi,n,p,c) -> (chi,n,p',c) with p' != p: segregation, sweeping, "
122 "or capture by a moving sink; changes only the population coordinate"),
123 EdgeClass.SOLUTE_TRAPPING: EdgeClassSpec(
124 EdgeClass.SOLUTE_TRAPPING, 1, 2, False, False, 0, False, True, False, False,
125 "(chi,n,p,c) <-> (chi,n,p,c +/- e_s): trapping (binary, cluster x gas "
126 "reservoir) or detrapping (unary); changes only the composition vector"),
127 EdgeClass.COALESCENCE: EdgeClassSpec(
128 EdgeClass.COALESCENCE, 2, 2, False, False, None, False, False, False, False,
129 "(chi,n,p,c) + (chi,n',p,c') -> (chi,n+n',p,c+c'): same-polarity "
130 "binary growth (hyperedge with a quadratic rate kernel). An optional "
131 "product_population redirects the product to a different same-polarity "
132 "population (cross-population coalescence, e.g. <111>+<111> junction -> "
133 "<100>); the default (no product_population) keeps the product in the "
134 "reactant population, so changes_population is False for the base case."),
135 EdgeClass.SOURCE: EdgeClassSpec(
136 EdgeClass.SOURCE, 0, 0, False, False, None, False, False, True, False,
137 "(empty) -> (chi,n,p,c) at a rate set by the displacement-cascade "
138 "or transmutation spectrum; the only edge with no precursor vertex"),
139 EdgeClass.SINK: EdgeClassSpec(
140 EdgeClass.SINK, 1, 1, False, False, None, False, False, False, True,
141 "(chi,n,p,c) -> (sink) at a fixed sink (free surface, grain "
142 "boundary, dislocation); first-order loss, absorbs both polarities"),
143}
144
145# Sanity: the catalogue is complete and consistent.
146assert set(EDGE_CLASS_SPEC) == set(EdgeClass), \
147 "EDGE_CLASS_SPEC must cover every EdgeClass exactly once"
148for _ec, _spec in EDGE_CLASS_SPEC.items():
149 assert _spec.edge_class is _ec, f"EDGE_CLASS_SPEC key/value mismatch for {_ec}"
150
151
152def spec_of(edge_class: EdgeClass) -> EdgeClassSpec:
153 """Return the abstract stoichiometric contract for ``edge_class``."""
154 return EDGE_CLASS_SPEC[edge_class]