算法31-基于事件模拟互相碰撞的粒子

各位读者盆友,上午好!我们终于接近算法系列的尾声拉。这里介绍看起来很酷炫的一种算法实践:模拟粒子碰撞。

其实,这方面有非常广阔的使用天地,曾经在关于宇宙的记录片中就看到科学家们研究星球变化、宇宙演化就用计算你模拟的,很酷炫,真的是超酷炫!背后的技术也非常难。

先看结果,酷炫吧:

算法31-基于事件模拟互相碰撞的粒子_第1张图片

本博客代码示例均来自:算法 Algorithmes Forth Edition
[美] Robert Sedgewick Kevin Wayne 著 谢路云译

文章目录

  • 一、粒子模型
  • 二、离子模拟的事件类
  • 三、模拟粒子互相碰撞
  • 四、命令行运行
  • 五、源码

一、粒子模型

package com.cmh.algorithm.background;

import edu.princeton.cs.algs4.StdDraw;
import edu.princeton.cs.algs4.StdRandom;

import java.awt.*;

/**
 * 基于事件驱动模拟的粒子碰撞
 * 粒子对象
 * Author:起舞的日子
 * Date:  2020/5/1 00:51
 */
public class Particle {
    private static final double INFINITY = Double.POSITIVE_INFINITY;

    /**
     * position(位置)
     */
    private double rx, ry;

    /**
     * velocity(速度)
     */
    private double vx, vy;

    /**
     * number of collisions so far
     * 碰撞的次数
     */
    private int count;

    /**
     * 半径
     */
    private final double radius;

    /**
     * 质量
     */
    private final double mass;

    private final Color color;

    public Particle(double rx, double ry, double vx, double vy, double radius, double mass, Color color) {
        this.vx = vx;
        this.vy = vy;
        this.rx = rx;
        this.ry = ry;
        this.radius = radius;
        this.mass = mass;
        this.color = color;
    }

    public Particle() {
        rx = StdRandom.uniform(0.0, 1.0);
        ry = StdRandom.uniform(0.0, 1.0);
        vx = StdRandom.uniform(-0.005, 0.005);
        vy = StdRandom.uniform(-0.005, 0.005);
        radius = 0.02;
        mass = 0.5;
        color = Color.BLACK;
    }

    public void move(double dt) {
        rx += vx * dt;
        ry += vy * dt;
    }

    public void draw() {
        StdDraw.setPenColor(color);
        StdDraw.filledCircle(rx, ry, radius);
    }

    public int count() {
        return count;
    }

    /**
     * 距离该粒子和粒子b碰撞所需的时间
     */
    public double timeToHit(Particle that) {
        if (this == that) {
            return INFINITY;
        }
        double dx = that.rx - this.rx;
        double dy = that.ry - this.ry;
        double dvx = that.vx - this.vx;
        double dvy = that.vy - this.vy;
        double dvdr = dx * dvx + dy * dvy;
        if (dvdr > 0) {
            return INFINITY;
        }
        double dvdv = dvx * dvx + dvy * dvy;
        if (dvdv == 0) {
            return INFINITY;
        }
        double drdr = dx * dx + dy * dy;
        double sigma = this.radius + that.radius;
        double d = (dvdr * dvdr) - dvdv * (drdr - sigma * sigma);
        if (d < 0) {
            return INFINITY;
        }
        return -(dvdr + Math.sqrt(d)) / dvdv;
    }

    /**
     * 距离该粒子和垂直墙体碰撞所需的时间
     */
    public double timeToHitVerticalWall() {
        if (vx > 0) {
            return (1.0 - rx - radius) / vx;
        } else if (vx < 0) {
            return (radius - rx) / vx;
        } else {
            return INFINITY;
        }
    }

    /**
     * 距离该粒子和水平墙体碰撞所需的时间
     */
    public double timeToHitHorizontalWall() {
        if (vy > 0) {
            return (1.0 - ry - radius) / vy;
        } else if (vy < 0) {
            return (radius - ry) / vy;
        } else {
            return INFINITY;
        }
    }

    /**
     * 碰撞后该粒子的速度
     */
    public void bounceOff(Particle that) {
        double dx = that.rx - this.rx;
        double dy = that.ry - this.ry;
        double dvx = that.vx - this.vx;
        double dvy = that.vy - this.vy;
        double dvdr = dx * dvx + dy * dvy;
        double dist = this.radius + that.radius;

        double magnitude = 2 * this.mass * that.mass * dvdr / ((this.mass + that.mass) * dist);

        double fx = magnitude * dx / dist;
        double fy = magnitude * dy / dist;

        this.vx += fx / this.mass;
        this.vy += fy / this.mass;
        that.vx -= fx / that.mass;
        that.vy -= fy / that.mass;

        this.count++;
        that.count++;

    }

    /**
     * 碰撞垂直墙体后该粒子的速度
     */
    public void bounceOffVerticalWall() {
        vx = -vx;
        count++;
    }

    /**
     * 碰撞水平墙体后该粒子的速度
     */
    public void bounceOffHorizontalWall() {
        vy = -vy;
        count++;
    }

    /**
     * 动能
     */
    public double kineticEnergy() {
        return 0.5 * mass * (vx * vx + vy * vy);
    }

}

二、离子模拟的事件类

 private class Event implements Comparable<Event> {
        private final double time;

        private final Particle a, b;

        private final int countA, countB;

        public Event(double t, Particle a, Particle b) {
            //创造一个发生在时间t且a和b相关的新事件
            this.time = t;
            this.a = a;
            this.b = b;
            if (a != null) {
                countA = a.count();
            } else {
                countA = -1;
            }
            if (b != null) {
                countB = b.count();
            } else {
                countB = -1;
            }
        }

        public int compareTo(Event that) {
            if (this.time < that.time) {
                return -1;
            } else if (this.time > that.time) {
                return +1;
            } else {
                return 0;
            }
        }

        public boolean isValid() {
            if (a != null && a.count() != countA) {
                return false;
            }
            if (b != null && b.count() != countB) {
                return false;
            }
            return true;
        }
    }

三、模拟粒子互相碰撞

package com.cmh.algorithm.background;

import edu.princeton.cs.algs4.MinPQ;
import edu.princeton.cs.algs4.StdDraw;

/**
 * 基于事件模拟互相碰撞的粒子(框架)
 * Author:起舞的日子
 * Date:  2020/5/1 03:49
 */
public class CollisionSystem {
    /**
     * 优先队列
     */
    private MinPQ<Event> pq;

    /**
     * 模拟时钟
     */
    private double t = 0.0;

    /**
     * 粒子数组
     */
    private Particle[] particles;

    public CollisionSystem(Particle[] particles) {
        this.particles = particles;
    }

    /**
     * 预测其他粒子的碰撞事件
     */
    private void predictCollisions(Particle a, double limit) {
        if (a == null) {
            return;
        }
        for (int i = 0; i < particles.length; i++) {
            //将与particles[i]发生碰撞的事件插入pq中
            double dt = a.timeToHit(particles[i]);
            if (t + dt < limit) {
                pq.insert(new Event(t + dt, a, particles[i]));
            }
        }
        double dtX = a.timeToHitVerticalWall();
        if (t + dtX <= limit) {
            pq.insert(new Event(t + dtX, a, null));
        }
        double dtY = a.timeToHitHorizontalWall();
        if (t + dtY <= limit) {
            pq.insert(new Event(t + dtY, null, a));
        }
    }

    public void redraw(double limit, double Hz) {
        //重绘事件:重新画出所有的粒子
        StdDraw.clear();
        for (int i = 0; i < particles.length; i++) {
            particles[i].draw();
        }
        StdDraw.pause(100);
        if (t < limit) {
            pq.insert(new Event(t + 1.0 / Hz, null, null));
        }
    }

    /**
     * 基于事件模拟互相碰撞的粒子(主循环)
     */
    public void simulate(double limit, double Hz) {
        pq = new MinPQ<>();
        for (int i = 0; i < particles.length; i++) {
            predictCollisions(particles[i], limit);
        }
        //添加重绘事件
        pq.insert(new Event(0, null, null));
        while (!pq.isEmpty()) {
            //处理一个事件
            Event event = pq.delMin();
            if (!event.isValid()) {
                continue;
            }
            for (int i = 0; i < particles.length; i++) {
                //更新粒子的位置
                particles[i].move(event.time - t);
            }
            t = event.time;
            Particle a = event.a, b = event.b;
            if (a != null && b != null) {
                a.bounceOff(b);
            } else if (a != null && b == null) {
                a.bounceOffVerticalWall();
            } else if (a == null && b != null) {
                b.bounceOffHorizontalWall();
            } else if (a == null && b == null) {
                redraw(limit, Hz);
            }
            predictCollisions(a, limit);
            predictCollisions(b, limit);
        }
    }

    private class Event implements Comparable<Event> {
        private final double time;

        private final Particle a, b;

        private final int countA, countB;

        public Event(double t, Particle a, Particle b) {
            //创造一个发生在时间t且a和b相关的新事件
            this.time = t;
            this.a = a;
            this.b = b;
            if (a != null) {
                countA = a.count();
            } else {
                countA = -1;
            }
            if (b != null) {
                countB = b.count();
            } else {
                countB = -1;
            }
        }

        public int compareTo(Event that) {
            if (this.time < that.time) {
                return -1;
            } else if (this.time > that.time) {
                return +1;
            } else {
                return 0;
            }
        }

        public boolean isValid() {
            if (a != null && a.count() != countA) {
                return false;
            }
            if (b != null && b.count() != countB) {
                return false;
            }
            return true;
        }
    }

    public static void main(String[] args) {
        StdDraw.pause(0);
        int N = Integer.parseInt(args[0]);
        Particle[] particles = new Particle[N];
        for (int i = 0; i < N; i++) {
            particles[i] = new Particle();
        }
        CollisionSystem system = new CollisionSystem(particles);
        system.simulate(10000, 0.5);
    }
}

四、命令行运行

(develop)]$ javac -cp ~/Downloads/algs4.jar:  ./com/cmh/algorithm/background/CollisionSystem.java


(develop)]$java -cp ~/Downloads/algs4.jar: com/cmh/algorithm/background/CollisionSystem 100

五、源码

https://github.com/cmhhcm/my2020.git

你可能感兴趣的:(数据结构与算法)