File size: 7,254 Bytes
0a16898 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | // 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 <torch/torch.h>
#include <cstdint>
#include <cmath>
#include <vector>
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 <arm_neon.h>
#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; y<H; y++){
const float* pr = p + (size_t)y*W;
const float* pu = (y>0) ? p + (size_t)(y-1)*W : nullptr;
const float* pd = (y<H-1) ? p + (size_t)(y+1)*W : nullptr;
const float* cEr = cE + (size_t)y*W;
const float* cSr = cS + (size_t)y*W;
const float* cSu = (y>0) ? cS + (size_t)(y-1)*W : nullptr;
float* o = Ap + (size_t)y*W;
int x = 0;
#if defined(__ARM_NEON)
if (y>0 && y<H-1){ // interior rows: all 4 edges exist
{ float pc=pr[0], s=cEr[0]*(pc-pr[1])+cSr[0]*(pc-pd[0])+cSu[0]*(pc-pu[0]); o[0]=s; }
for (x=1; x+4<=W-1; x+=4){ // vectorise interior cols [1, W-2]
float32x4_t pc=vld1q_f32(pr+x);
float32x4_t ce=vld1q_f32(cEr+x), cw=vld1q_f32(cEr+x-1);
float32x4_t cs=vld1q_f32(cSr+x), cn=vld1q_f32(cSu+x);
float32x4_t s=vmulq_f32(ce, vsubq_f32(pc, vld1q_f32(pr+x+1)));
s=vmlaq_f32(s, cw, vsubq_f32(pc, vld1q_f32(pr+x-1)));
s=vmlaq_f32(s, cs, vsubq_f32(pc, vld1q_f32(pd+x)));
s=vmlaq_f32(s, cn, vsubq_f32(pc, vld1q_f32(pu+x)));
vst1q_f32(o+x, s);
}
for (; x<W; x++){ // scalar tail (incl. col W-1)
float pc=pr[x], s=cEr[x-1]*(pc-pr[x-1])+cSr[x]*(pc-pd[x])+cSu[x]*(pc-pu[x]);
if (x<W-1) s+=cEr[x]*(pc-pr[x+1]);
o[x]=s;
}
continue;
}
#endif
for (x=0; x<W; x++){ // boundary rows / scalar fallback
float pc=pr[x], s=0.0f;
if (x<W-1) s+=cEr[x]*(pc-pr[x+1]);
if (x>0) s+=cEr[x-1]*(pc-pr[x-1]);
if (y<H-1) s+=cSr[x]*(pc-pd[x]);
if (y>0) 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 (; i<N; i++) s+=(double)a[i]*b[i]; return s;
#else
double s=0; for (int i=0;i<N;i++) s += (double)a[i]*(double)b[i]; return s;
#endif
}
// p += a*d ; r -= a*Ad ; return sum r^2
double axpy_rr(float* p, float* r, const float* d, const float* Ad, float a, int N){
#if defined(__ARM_NEON)
float32x4_t va=vdupq_n_f32(a), acc=vdupq_n_f32(0); int i=0;
for (; i+4<=N; i+=4){
float32x4_t di=vld1q_f32(d+i), adi=vld1q_f32(Ad+i);
float32x4_t pi=vmlaq_f32(vld1q_f32(p+i), va, di); vst1q_f32(p+i, pi);
float32x4_t ri=vmlsq_f32(vld1q_f32(r+i), va, adi); vst1q_f32(r+i, ri);
acc=vmlaq_f32(acc, ri, ri);
}
double s=hadd(acc);
for (; i<N; i++){ p[i]+=a*d[i]; r[i]-=a*Ad[i]; s+=(double)r[i]*r[i]; }
return s;
#else
double s=0; for (int i=0;i<N;i++){ p[i]+=a*d[i]; r[i]-=a*Ad[i]; s+=(double)r[i]*r[i]; } return s;
#endif
}
void precond(float* z, const float* r, const float* diag, int N){ // z = r/diag (0 where diag~0)
#if defined(__ARM_NEON)
float32x4_t eps=vdupq_n_f32(1e-20f); int i=0;
for (; i+4<=N; i+=4){
float32x4_t dg=vld1q_f32(diag+i);
float32x4_t zz=vdivq_f32(vld1q_f32(r+i), dg);
vst1q_f32(z+i, vbslq_f32(vcgtq_f32(dg,eps), zz, vdupq_n_f32(0)));
}
for (; i<N; i++) z[i]= diag[i]>1e-20f ? r[i]/diag[i] : 0.f;
#else
for (int i=0;i<N;i++) z[i]= diag[i]>1e-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<N; i++) d[i]=z[i]+beta*d[i];
#else
for (int i=0;i<N;i++) d[i]=z[i]+beta*d[i];
#endif
}
} // namespace
// Solve L p = b with node `ground` pinned, by Jacobi-preconditioned CG; p is
// warm-started and overwritten with the solution.
void flow_cg(torch::Tensor cE, torch::Tensor cS, torch::Tensor b,
torch::Tensor p, int64_t ground, int64_t max_iters, double tol){
TORCH_CHECK(p.dtype()==torch::kFloat32 && p.is_contiguous(), "p must be f32 contiguous");
int H=(int)p.size(0), W=(int)p.size(1), N=H*W, g=(int)ground;
const float* cEp=cE.data_ptr<float>(); const float* cSp=cS.data_ptr<float>();
const float* bp=b.data_ptr<float>();
float* pp=p.data_ptr<float>();
std::vector<float> diag(N,0.f), r(N), z(N), d(N), Ad(N);
for (int y=0;y<H;y++) for (int x=0;x<W;x++){ // diag = sum of incident conductivities
int i=y*W+x; float s=0.f;
if (x<W-1) s+=cEp[i]; if (x>0) s+=cEp[i-1];
if (y<H-1) s+=cSp[i]; if (y>0) s+=cSp[i-W];
diag[i]=s;
}
diag[g]+=GROUND;
matvec(Ad.data(), pp, cEp, cSp, H, W, g);
for (int i=0;i<N;i++) r[i]=bp[i]-Ad[i];
precond(z.data(), r.data(), diag.data(), N);
for (int i=0;i<N;i++) d[i]=z[i];
double rz = ddot(r.data(), z.data(), N), tol2 = tol*tol;
for (int it=0; it<max_iters; it++){
matvec(Ad.data(), d.data(), cEp, cSp, H, W, g);
double dAd = ddot(d.data(), Ad.data(), N);
if (dAd<=1e-30) break;
float alpha=(float)(rz/dAd);
double rr = axpy_rr(pp, r.data(), d.data(), Ad.data(), alpha, N);
if (rr < tol2) break;
precond(z.data(), r.data(), diag.data(), N);
double rzn = ddot(r.data(), z.data(), N);
float beta=(float)(rzn/rz);
xpby(d.data(), z.data(), beta, N);
rz=rzn;
}
}
|