@@ -30,7 +30,8 @@ class PDE_D(pyg.nn.MessagePassing):
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 ]},
3232 {"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 ]}
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 ]},
34+ {"index" : 7 , "name" : "gdp_gamma" , "description" : "Gradient-dependent production strength (0=off, >0=particles at steep gradients consume/produce more)" , "typical_range" : [0.0 , 2.0 ]}
3435 ]
3536 },
3637 {
@@ -234,6 +235,34 @@ def __init__(self, aggr_type='mean', p=None, particle_params=None, bc_dpos=None,
234235 # Stored from mesh params row 0, index 2
235236 self .A_ref = p [0 , 2 ]
236237
238+ # Block 14 code change: Gradient-dependent production (GDP)
239+ # p[0, 7] controls gdp_gamma (gradient-dependent production/consumption strength):
240+ # 0.0 = constant production/consumption rates (backward compatible, default)
241+ # >0 = rate_eff = rate * (1 + gdp_gamma * clamp(|grad_C1_local|, max=1.0))
242+ # Particles at pattern BOUNDARIES (steep gradients) consume/produce MORE
243+ # than particles at pattern CENTERS (flat gradients, near peaks/troughs).
244+ # Literature: Meinhardt (1982) "Models of Biological Pattern Formation" — gradient-
245+ # dependent production rates create edge-selective feedback in morphogen systems.
246+ # Also: Kondo & Miura (2010) Science 329:1616 — reaction-diffusion patterns are
247+ # stabilized when reaction rates depend on spatial context (gradient information).
248+ # Rationale: The 7/10 ceiling (104 iterations, Blocks 1-13) persists because
249+ # particle back-reaction (consumption/production) is spatially uniform. GDP makes
250+ # particles at pattern boundaries produce/consume more, creating edge-selective
251+ # feedback that sharpens morphological features. Combined with FDM (mobility
252+ # control) and multi-type opposing (segregation), GDP adds a third dimension
253+ # of spatial control: WHERE particles have their strongest effect on fields.
254+ # Effect: Particles at steep gradient boundaries become production/consumption
255+ # hotspots, reinforcing boundary regions. Particles at flat peaks/troughs have
256+ # weaker back-reaction. This creates differential feedback: boundaries are
257+ # reinforced, centers are relatively unaffected → sharper, more complex features.
258+ if p .shape [1 ] > 7 :
259+ self .gdp_gamma = p [0 , 7 ]
260+ else :
261+ self .gdp_gamma = 0.0
262+
263+ # Storage for local gradient magnitude, computed during 'fp' pass for use in 'pf' pass
264+ self ._local_grad_mag = None
265+
237266 # Report configuration
238267 print (f"initialized PDE_D with parameters:" )
239268 print (f"mobility: M₁={ self .M1 .item ()} , M₂={ self .M2 .item ()} " )
@@ -293,6 +322,12 @@ def __init__(self, aggr_type='mean', p=None, particle_params=None, bc_dpos=None,
293322 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)" )
294323 else :
295324 print (f"field-dependent mobility: off (alpha=0)" )
325+ if hasattr (self , 'gdp_gamma' ):
326+ gdp_val = self .gdp_gamma .item () if hasattr (self .gdp_gamma , 'item' ) else self .gdp_gamma
327+ if gdp_val > 0 :
328+ print (f"gradient-dependent production (GDP): gamma={ gdp_val :.3f} (rate_eff = rate*(1+gamma*clamp(|gradC1|,max=1)), Meinhardt 1982)" )
329+ else :
330+ print (f"gradient-dependent production: off (gamma=0)" )
296331 if particle_params is not None :
297332 print (f"multi-type support: { particle_params .shape [0 ]} particle types" )
298333 print (f"per-type params: [M1, M2, consumption, production, ar_p1, ar_p2, ar_p3, ar_p4]" )
@@ -540,6 +575,23 @@ def message(self, edge_index_i, edge_index_j, x_i, x_j, mode=None, parameters_i=
540575 consumption = consumption * mm_factor
541576 production = production * mm_factor
542577
578+ # Block 14: Gradient-dependent production (GDP)
579+ # Meinhardt (1982): gradient-dependent rates create edge-selective feedback
580+ # When gdp_gamma > 0, scale consumption/production by local gradient magnitude:
581+ # rate_eff = rate * (1 + gdp_gamma * clamp(|dC1/dr|, max=1.0))
582+ # Particles at pattern boundaries (steep gradients) have amplified rates.
583+ # Particles at pattern centers (flat, near peak/trough) have baseline rates.
584+ # Uses the C1 field difference along the pf edge as gradient proxy.
585+ if hasattr (self , 'gdp_gamma' ) and self .gdp_gamma > 0 :
586+ # Compute local gradient magnitude from field difference along this edge
587+ C1_i = x_i [:, 6 ] # C1 at particle position
588+ C1_j = x_j [:, 6 ] # C1 at mesh node
589+ dC1_dr = torch .abs (C1_j - C1_i ) / (dist_safe * 32.0 ) # Scaled gradient estimate
590+ grad_mag_clamped = torch .clamp (dC1_dr , max = 1.0 )
591+ gdp_factor = 1.0 + self .gdp_gamma * grad_mag_clamped
592+ consumption = consumption * gdp_factor
593+ production = production * gdp_factor
594+
543595 # Create field updates [C₁, C₂]
544596 field_updates = torch .zeros ((pos_i .size (0 ), 2 ), device = pos_i .device )
545597 field_updates [:, 0 ] = - consumption * weights
0 commit comments