// physarum-flow: the Tero adaptive-conductivity Physarum model, whose compute // core is a weighted-grid-Laplacian solve. Given per-edge conductivities on a // masked grid and a balanced source/sink injection, flow_cg solves L(cE,cS) p = b // for the node pressures p by Jacobi-preconditioned conjugate gradients. One node // is grounded with a diagonal penalty so the Laplacian is positive-definite (no // singular nullspace to project out); the gauge choice does not affect edge flux. // The caller iterates: solve -> flux Q = c*grad(p) -> reinforce conductive edges, // which converges onto the shortest path (single source/sink) or an efficient // network (multiple terminals). The 5-point weighted mat-vec is in gather form // (each node reads its neighbours, no scatter) and vectorises with NEON; CG on a // modest grid is latency-bound and sequential, so it is not threaded. #include #include #include #include namespace { const float GROUND = 1e3f; // diagonal penalty pinning the ground node's gauge // (kept modest: too large floors the f32 CG residual) #if defined(__ARM_NEON) #include #endif // Ap = (L + GROUND e_g e_g^T) p. cE[y*W+x] is the edge (y,x)-(y,x+1); cS the edge // (y,x)-(y+1,x); both zero on non-open edges, so wall nodes yield 0. void matvec(float* Ap, const float* p, const float* cE, const float* cS, int H, int W, int g){ for (int y=0; y0) ? p + (size_t)(y-1)*W : nullptr; const float* pd = (y0) ? cS + (size_t)(y-1)*W : nullptr; float* o = Ap + (size_t)y*W; int x = 0; #if defined(__ARM_NEON) if (y>0 && y0) s+=cEr[x-1]*(pc-pr[x-1]); if (y0) s+=cSu[x]*(pc-pu[x]); o[x]=s; } } Ap[g] += GROUND * p[g]; } #if defined(__ARM_NEON) inline double hadd(float32x4_t v){ float32x2_t s=vadd_f32(vget_low_f32(v),vget_high_f32(v)); return (double)vget_lane_f32(vpadd_f32(s,s),0); } #endif double ddot(const float* a, const float* b, int N){ #if defined(__ARM_NEON) float32x4_t acc=vdupq_n_f32(0); int i=0; for (; i+4<=N; i+=4) acc=vmlaq_f32(acc, vld1q_f32(a+i), vld1q_f32(b+i)); double s=hadd(acc); for (; i1e-20f ? r[i]/diag[i] : 0.f; #else for (int i=0;i1e-20f ? r[i]/diag[i] : 0.f; #endif } void xpby(float* d, const float* z, float beta, int N){ // d = z + beta*d #if defined(__ARM_NEON) float32x4_t vb=vdupq_n_f32(beta); int i=0; for (; i+4<=N; i+=4) vst1q_f32(d+i, vmlaq_f32(vld1q_f32(z+i), vb, vld1q_f32(d+i))); for (; i(); const float* cSp=cS.data_ptr(); const float* bp=b.data_ptr(); float* pp=p.data_ptr(); std::vector diag(N,0.f), r(N), z(N), d(N), Ad(N); for (int y=0;y0) s+=cEp[i-1]; if (y0) s+=cSp[i-W]; diag[i]=s; } diag[g]+=GROUND; matvec(Ad.data(), pp, cEp, cSp, H, W, g); for (int i=0;i