爆発しなくなった、ざっぱ〜んのソース

とりあえず貼り付けときますね。
「時間感覚」のところと「粒子が近づきすぎてないか」のところが追加した処理。

import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class Particle {
    enum Type{
        WATER(0, new Color(0, 0, 255, 96)), OUTWALL(1, Color.BLACK), INWALL(2, Color.GREEN);

        int index;
        Color color;
        Type(int index, Color c){
            color = c;
            this.index = index;
        }
    }
    
    static class Elem{
        double x;
        double y;
        double vx;
        double vy;
        double p;
        Type type;
        boolean surface = false;

        public Elem(double x, double y, Type type) {
            this.x = x;
            this.y = y;
            this.type = type;
        }
        Color getColor(){
            return surface ? new Color(255, 255, 255, 96) : type.color;
        }
    }
    
    static Elem[] elements = new Elem[1500];
    static int elementCount;
    static JLabel label;
    static Image img = new BufferedImage(500, 400, BufferedImage.TYPE_INT_RGB);
    
    static final double parcSize = 8e-3;//1.5e-2;//粒子サイズ
    static final double minlen = 0.5;//粒子の最低距離(粒子サイズに対する)
    static final double re = 2.1;//近傍粒子の範囲
    static final double relap = 4.0;//ラプラシアン用の近傍粒子の範囲
    static double deltat = 0.0001;//時間の単位(粒子の最高速度によって調整される)
    static final double grav = 9.8;//重力加速度(m/s^2)
    static final double dim = 2;//次元数
    static final double nyu = 1.0038E-6;//動粘性係数(水 1.0038E-6 m^2/s)
    static final double rho = 1.0e3;//粒子の比重(kg/m^3)
    static final double deltatmax = 1.0e-4;//時間の単位の最大値(本来は1e-3)

    static double n0;//基準粒子密度
    static double lambda;//ラプラシアン用の係数
    static final double beta = 0.97;//自由表面条件
    
    public static void main(String[] args){
        //左右の外壁
        double maxy = 0;
        double oldparc = 1.5e-2;
        double ysize = 0.5 * parcSize / oldparc;
        double xsize = 0.8 * parcSize / oldparc;
        for(double y = 0; y < ysize; y += parcSize){
            elements[elementCount++] = new Elem(0, y, Type.INWALL);
            elements[elementCount++] = new Elem(-parcSize, y, Type.OUTWALL);
            elements[elementCount++] = new Elem(-parcSize * 2, y, Type.OUTWALL);
            elements[elementCount++] = new Elem(xsize, y, Type.INWALL);
            elements[elementCount++] = new Elem(xsize+parcSize, y, Type.OUTWALL);
            elements[elementCount++] = new Elem(xsize+parcSize * 2, y, Type.OUTWALL);
            maxy = y;
        }
        //下の壁
        for(double x = -parcSize * 2; x < xsize + parcSize * 2; x += parcSize){
            elements[elementCount++] = new Elem(x, maxy + parcSize, 
                    (x < 0 || x > xsize) ? Type.OUTWALL : Type.INWALL);
            elements[elementCount++] = new Elem(x, maxy + parcSize * 2, Type.OUTWALL);
            elements[elementCount++] = new Elem(x, maxy + parcSize * 3, Type.OUTWALL);
        }
        
        //水柱
        int sample = 0;
        double waterx = .3 * parcSize / oldparc;
        double watercxmin = .2 * parcSize / oldparc;
        double watercxmax = .25 * parcSize / oldparc;
        double watercymin = .1 * parcSize / oldparc;
        double watercymax = .15 * parcSize / oldparc;
        for(double x = parcSize; x < waterx; x+= parcSize){
            for(double y = parcSize * 3; y < ysize; y += parcSize){//.5まで
                if(y > watercxmin && y < watercxmax && x > watercymin && x < watercymax){
                    //内部になる点を一点記憶しておく
                    sample = elementCount;
                }
                elements[elementCount++] = new Elem(x, y, Type.WATER);
            }
        }

        //基本の粒子密度n0とラプラシアン用の係数rambda
        n0 = 0;
        double a = 0;
        double b = 0;
        for(int i = 0; i < elementCount; ++i){
            if(i == sample) continue;

            double d = Math.sqrt(
                    (elements[sample].x - elements[i].x) * (elements[sample].x - elements[i].x) +
                    (elements[sample].y - elements[i].y) * (elements[sample].y - elements[i].y));
            double w = (d < re * parcSize) ? parcSize * re / d - 1 : 0;            
            n0 += w;
            a += (elements[sample].x - elements[i].x) * (elements[sample].x - elements[i].x) +
                    (elements[sample].y - elements[i].y) * (elements[sample].y - elements[i].y) * w;
        }
        lambda = a / n0;

        //ウィンドウの表示
        JFrame f = new JFrame("粒子法");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(500, 450);
        label = new JLabel(new ImageIcon(img));
        draw();
        f.add(label);
        f.setVisible(true);

        //処理スレッド
        new Thread(){
            @Override
            public void run() {
                try{
                    for(;;){
                        calc();
                        draw();
                    }
                }catch(InterruptedException e){}
            }
        }.start();
    }
    
    static void calc() throws InterruptedException{
        final double[][] w = new double[elementCount][elementCount];
        
        //粒子密度を計算しておく
        for(int i = 0; i < elementCount; ++i){
            for(int j = 0; j < i; ++j){
                double d = Math.sqrt(
                        (elements[i].x - elements[j].x) * (elements[i].x - elements[j].x) +
                        (elements[i].y - elements[j].y) * (elements[i].y - elements[j].y));
                double wd = (d < relap * parcSize) ? parcSize * relap / d - 1 : 0;
                w[i][j] = wd;
                w[j][i] = wd;
            }
        }
        
        //時間間隔
        double maxvelo = 0;
        double velolim = 0.2 * parcSize / (deltatmax * 0.02);
        double svelolim = velolim * velolim;
        for(int i = 0; i < elementCount; ++i){
            if(elements[i].type != Type.WATER) continue;
            double v = elements[i].vx * elements[i].vx + elements[i].vy * elements[i].vy;
            if(v > velolim){
                //速すぎる場合、遅くしちゃう
                double sv = Math.sqrt(v);
                elements[i].vx *= svelolim / sv;
                elements[i].vy *= svelolim / sv;
                v = velolim;
            }
            if(v > maxvelo) maxvelo = v;
        }
        maxvelo = Math.sqrt(maxvelo);
        double dtlim = deltat * 1.1;
        if(maxvelo == 0){
            deltat = dtlim;
        }else{
            deltat = 0.2 * parcSize / maxvelo;
        }
        if(deltat > dtlim) deltat = dtlim;
        if(deltat > deltatmax) deltat = deltatmax;
        
        //仮の速度・位置を計算
        final double oldx[] = new double[elementCount];
        final double oldy[] = new double[elementCount];
        for(int i = 0; i < elementCount; ++i){
            oldx[i] = elements[i].vx;
            oldy[i] = elements[i].vy;
        }
        for(int i = 0; i < elementCount; ++i){
            if(elements[i].type != Type.WATER){
                continue;
            }
            //速度のラプラシアンを計算
            double laplasux = 0;
            double laplasuy = 0;
            for(int j = 0; j < elementCount; ++j){
                if(j == i) continue;
                if(w[i][j] == 0) continue;
                laplasux += (oldx[j] - elements[i].vx) * w[i][j];
                laplasuy += (oldy[j] - elements[i].vy) * w[i][j];
            }
            laplasux *= 2 * dim / n0 / lambda;
            laplasuy *= 2 * dim / n0 / lambda;

            //仮の位置・速度を計算
            //
            elements[i].vx += + deltat * (nyu * laplasux);
            elements[i].vy += deltat * (nyu * laplasuy + grav);
            elements[i].x += elements[i].vx * deltat;
            elements[i].y += elements[i].vy * deltat;
        }

        //粒子が近づきすぎてないか
        double r2lim = parcSize * parcSize * minlen * minlen;
        for(int i = 0; i < elementCount; ++i){
            for(int j = 0; j < i; ++j){
                if(w[i][j] == 0) continue;
                double x = elements[j].x - elements[i].x;
                double y = elements[j].y - elements[i].y;
                double rr = x * x + y * y;
                if(rr < r2lim){
                    double r = Math.sqrt(rr);
                    //double m2 = rho + rho;
                    double vgx = (elements[i].vx + elements[j].vx) / 2;//本来は速度を粘度で按分
                    double vgy = (elements[i].vy + elements[j].vy) / 2;
                    double vrx = rho * (elements[i].vx - vgx);
                    double vry = rho * (elements[i].vy - vgy);
                    double vabs = (vrx * x + vry * y) / r;
                    
                    if(vabs < 0) continue;
                    
                    final double colrat = 0.15;
                    double vrat = 1 + colrat;
                    double vmx = vrat * vabs * x / r;
                    double vmy = vrat * vabs * y / r;
                    if(elements[i].type == Type.WATER){
                        elements[i].vx -= vmx / rho;
                        elements[i].vy -= vmy / rho;
                        elements[i].x -= deltat * vmx / rho;
                        elements[i].y -= deltat * vmy / rho;
                    }
                    if(elements[j].type == Type.WATER){
                        elements[j].vx += vmx / rho;
                        elements[j].vy += vmy / rho;
                        elements[j].x += deltat * vmx / rho;
                        elements[j].y += deltat * vmy / rho;
                        
                    }
                }
            }
        }
        
        //仮の粒子密度を計算
        final double[][] wasta = new double[elementCount][elementCount];
        final double[][] rsqu = new double[elementCount][elementCount];
        final int[] neighborcount = new int[elementCount];
        final int[][] neighbor = new int[elementCount][elementCount];
        for(int i = 0; i < elementCount; ++i){
            for(int j = 0; j < i; ++j){
                rsqu[i][j] = rsqu[j][i] =
                        (elements[i].x - elements[j].x) * (elements[i].x - elements[j].x) +
                        (elements[i].y - elements[j].y) * (elements[i].y - elements[j].y);
                double d = Math.sqrt(rsqu[i][j]);
                double rp = re * parcSize;
                if(d < rp){
                    double wd = rp / d - 1;
                    wasta[i][j] = wd;
                    wasta[j][i] = wd;
                    neighbor[i][neighborcount[i]++] = j;
                    neighbor[j][neighborcount[j]++] = i;
                }
            }
        }
        
        //粒子密度から自由表面になるところを検出
        final double[] nasta = new double[elementCount];
        for(int i = 0; i < elementCount; ++i){
            for(int j = 0; j < neighborcount[i]; ++j){
                nasta[i] += wasta[i][neighbor[i][j]];
            }
            if(elements[i].type == Type.WATER){
                elements[i].surface = nasta[i] < n0 * beta;
            }
        }
        
        //よくわからんけど。
        final int[] check = new int[elementCount];
        for(int i = 0; i < elementCount; ++i){
            if(elements[i].type != Type.WATER){
                check[i] = 2;
            }else if(elements[i].surface){
                check[i] = 1;
            }else{
                check[i] = 0;
            }
        }
        for(boolean cont = true; !cont;){
            cont = false;
            for(int i = 0; i < elementCount; ++i){
                if(check[i] == 1){
                    for(int j = 0; j < neighborcount[i]; ++j){
                        if(check[neighbor[i][j]] == 0) check[neighbor[i][j]] = 1;
                    }
                    check[i] = 2;
                    cont = true;
                }
            }
        }
        
        //連立方程式の係数行列を作成
        final double[] rk = new double[elementCount];
        final double[][] coef = new double[elementCount][elementCount];
        final double[] val = new double[elementCount];
        //exec = Executors.newFixedThreadPool(threadLimit);
        for(int i = 0; i < elementCount; ++i){
            if(elements[i].surface){
                val[i] = 0;
            }else{
                val[i] = -rho / deltat / deltat * (nasta[i] - n0) * lambda / 2 / dim;
            }
            for(int j = 0; j < neighborcount[i]; ++j){
                coef[i][j + 1] = wasta[i][neighbor[i][j]];
                coef[i][0] -= wasta[i][neighbor[i][j]];
            }
            coef[i][0] -= 1.0e-7 / deltat / deltat * rho * n0;//よくわからないけどソースから
            if(check[i] == 0) coef[i][0] *= 2;
        }
        
        //連立方程式を解く(とりあえずガウスザイデル法。CG法がいいらしい)
        for(int n = 0; n < 1000; ++n){
            double div = 0;
            for(int i = 0; i < elementCount; ++i){
                double sum = 0;
                for(int j = 0; j < neighborcount[i]; ++j){
                    sum += coef[i][j + 1] * rk[neighbor[i][j]];
                }
                
                double old = rk[i];
                rk[i] = (val[i] - sum) / coef[i][0];
                div += Math.abs(rk[i] - old);
            }
            //収束判定
            if(div < 0.0001) {
                break;
            }
        }
        
        
        for(int i = 0; i < elementCount; ++i){
            //圧力が負の場合は0に
            if(rk[i] < 0) rk[i] = 0;
        }
        
        //速度・場所を再計算
        for(int i = 0; i < elementCount; ++i){
            oldx[i] = elements[i].x;
            oldy[i] = elements[i].y;
        }
        for(int i = 0; i < elementCount; ++i){
            final int i = idx;
            if(elements[i].type != Type.WATER) continue;//場所がかわるのは水だけ
            double udashx = 0;
            double udashy = 0;
            //圧力の最小値を得る
            double hatp = rk[i];
            for(int j = 0; j < neighborcount[i]; ++j){
                if(hatp > rk[neighbor[i][j]]) hatp = rk[neighbor[i][j]];
            }
            //速度の修正量を計算
            for(int j = 0; j < neighborcount[i]; ++j){
                int n = neighbor[i][j];
                udashx += (rk[n] - hatp) / rsqu[i][n] * (oldx[n] - elements[i].x) * wasta[i][n];
                udashy += (rk[n] - hatp) / rsqu[i][n] * (oldy[n] - elements[i].y) * wasta[i][n];
            }
            udashx = -udashx * deltat / rho * dim / n0;
            udashy = -udashy * deltat / rho * dim / n0;
            //速度の再計算
            elements[i].vx += udashx;
            elements[i].vy += udashy;
            
            //場所を再計算
            elements[i].x += deltat * udashx;
            elements[i].y += deltat * udashy;
        }
        
        time += deltat;
    }
    
    static void draw(){
        Graphics2D g = (Graphics2D) img.getGraphics();
        g.setColor(new Color(128, 212, 255));
        g.fillRect(0, 0, 500, 400);
        int size = 8;
        double s = size / parcSize;
        for(int i = 0; i < elementCount; ++i){
            Elem el = elements[i];
            g.setColor(el.getColor());
            g.fillOval((int)(el.x * s) + 30, (int)(el.y * s) + 20, size * 3/2, size * 3/2);
        }
        g.setColor(Color.BLACK);
        g.drawString(String.format("%2.4f", time), 10, 15);
        g.dispose();
        label.repaint();
    }
    
}