@@ -29,7 +29,8 @@ class PDE_D(pyg.nn.MessagePassing):
2929 {"index" : 2 , "name" : "A" , "description" : "Brusselator param A (used by mesh model)" , "typical_range" : [0.5 , 5.0 ]},
3030 {"index" : 3 , "name" : "B" , "description" : "Brusselator param B (used by mesh model)" , "typical_range" : [1.0 , 10.0 ]},
3131 {"index" : 4 , "name" : "mu" , "description" : "Morphological parameter (used by mesh model)" , "typical_range" : [0.01 , 0.1 ]},
32- {"index" : 5 , "name" : "M1" , "description" : "Mobility coefficient for C1 gradients" , "typical_range" : [- 16 , 16 ]}
32+ {"index" : 5 , "name" : "M1" , "description" : "Mobility coefficient for C1 gradients" , "typical_range" : [- 16 , 16 ]},
33+ {"index" : 6 , "name" : "fdm_alpha" , "description" : "Field-dependent mobility strength (0=off, >0=faster at peaks, <0=slower at peaks)" , "typical_range" : [- 2.0 , 2.0 ]}
3334 ]
3435 },
3536 {
@@ -202,6 +203,37 @@ def __init__(self, aggr_type='mean', p=None, particle_params=None, bc_dpos=None,
202203 else :
203204 self .alignment_strength = 0.0
204205
206+ # Block 13 code change: Field-dependent mobility (FDM)
207+ # p[0, 6] controls fdm_alpha (field-dependent mobility strength):
208+ # 0.0 = constant mobility (backward compatible, default)
209+ # >0 = M_eff = M * (1 + fdm_alpha * clamp((C1-A)^2/A^2, max=4.0))
210+ # Particles move FASTER at field peaks/troughs (far from steady state)
211+ # and SLOWER near the Brusselator steady state (C1 ≈ A)
212+ # <0 = M_eff = M / (1 + |fdm_alpha| * clamp((C1-A)^2/A^2, max=4.0))
213+ # Particles move SLOWER at field peaks/troughs and FASTER near steady state
214+ # Literature: Hillen & Painter (2009) J Math Biol 58:183-217
215+ # "A user's guide to PDE models for chemotaxis" — concentration-dependent
216+ # chemotactic sensitivity chi(C) is standard in Keller-Segel family models.
217+ # Also: Painter & Hillen (2002) Can Appl Math Q 10:501-543 — volume-filling
218+ # chemotaxis where cells move slower in crowded (high-concentration) regions.
219+ # Rationale: The 7/10 ceiling (96 iterations, Blocks 1-12) is a COUPLING
220+ # bottleneck: particles respond uniformly to gradients. FDM creates nonlinear
221+ # coupling where accumulation dynamics depend on local field state, enabling
222+ # multi-scale organization (tight clusters at peaks, dispersed at boundaries).
223+ # Effect: With positive alpha, particles accumulate more strongly at Turing peaks
224+ # (where C1 >> A) because their mobility is amplified there. This creates
225+ # positive feedback: peaks attract particles faster → particles consume C1 →
226+ # pattern reorganizes. With negative alpha, particles are more mobile near
227+ # steady state and immobilized at peaks → smeared distributions.
228+ if p .shape [1 ] > 6 :
229+ self .fdm_alpha = p [0 , 6 ]
230+ else :
231+ self .fdm_alpha = 0.0
232+
233+ # Brusselator parameter A for FDM normalization
234+ # Stored from mesh params row 0, index 2
235+ self .A_ref = p [0 , 2 ]
236+
205237 # Report configuration
206238 print (f"initialized PDE_D with parameters:" )
207239 print (f"mobility: M₁={ self .M1 .item ()} , M₂={ self .M2 .item ()} " )
@@ -255,6 +287,12 @@ def __init__(self, aggr_type='mean', p=None, particle_params=None, bc_dpos=None,
255287 print (f"velocity alignment (Vicsek): strength={ align_val :.3f} (f_align = alpha*(v_j-v_i)*w(d), Vicsek 1995)" )
256288 else :
257289 print (f"velocity alignment: off (strength=0)" )
290+ if hasattr (self , 'fdm_alpha' ):
291+ fdm_val = self .fdm_alpha .item () if hasattr (self .fdm_alpha , 'item' ) else self .fdm_alpha
292+ if fdm_val != 0 :
293+ print (f"field-dependent mobility (FDM): alpha={ fdm_val :.3f} (M_eff = M*(1+alpha*clamp((C1-A)^2/A^2,max=4)), Hillen & Painter 2009)" )
294+ else :
295+ print (f"field-dependent mobility: off (alpha=0)" )
258296 if particle_params is not None :
259297 print (f"multi-type support: { particle_params .shape [0 ]} particle types" )
260298 print (f"per-type params: [M1, M2, consumption, production, ar_p1, ar_p2, ar_p3, ar_p4]" )
@@ -451,6 +489,28 @@ def message(self, edge_index_i, edge_index_j, x_i, x_j, mode=None, parameters_i=
451489 v_perp [:, 1 ] = velocity_raw [:, 0 ] # +vx
452490 velocity_raw = velocity_raw + self .chirality * v_perp
453491
492+ # Block 13: Field-dependent mobility (FDM)
493+ # Hillen & Painter (2009): concentration-dependent chemotactic sensitivity
494+ # M_eff depends on local C1 value relative to Brusselator steady state A.
495+ # Positive alpha: particles move faster at peaks/troughs (far from A)
496+ # Negative alpha: particles move slower at peaks/troughs (immobilized at features)
497+ # Uses (C1 - A)^2 / A^2 as dimensionless deviation, clamped for stability.
498+ if hasattr (self , 'fdm_alpha' ) and self .fdm_alpha != 0 :
499+ C1_local = fields_i [:, 0 :1 ] # Local C1 at particle position
500+ A_ref = self .A_ref
501+ # Dimensionless squared deviation from steady state
502+ deviation_sq = (C1_local - A_ref ) ** 2 / (A_ref ** 2 + 1e-6 )
503+ deviation_sq = torch .clamp (deviation_sq , max = 4.0 )
504+
505+ if self .fdm_alpha > 0 :
506+ # Positive: amplify mobility at peaks/troughs
507+ fdm_factor = 1.0 + self .fdm_alpha * deviation_sq
508+ else :
509+ # Negative: suppress mobility at peaks/troughs
510+ fdm_factor = 1.0 / (1.0 + torch .abs (self .fdm_alpha ) * deviation_sq )
511+
512+ velocity_raw = velocity_raw * fdm_factor
513+
454514 velocities = velocity_raw
455515
456516 return velocities
0 commit comments