From 4c03c0822e14ca7b14994ce4116f274627398582 Mon Sep 17 00:00:00 2001 From: xingye Date: Sun, 9 Feb 2020 08:44:21 +0800 Subject: [PATCH 1/4] =?UTF-8?q?'=E5=A2=9E=E5=8A=A0=E6=B2=BB=E6=84=88?= =?UTF-8?q?=E5=8A=9F=E8=83=BD'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .classpath | 6 + .gitignore | 4 + .project | 17 +++ README.md | 6 + src/Bed.java | 27 +++++ src/City.java | 33 +++++ src/Constants.java | 35 ++++++ src/Hospital.java | 102 ++++++++++++++++ src/Main.java | 59 +++++++++ src/MathUtil.java | 38 ++++++ src/MoveTarget.java | 42 +++++++ src/MyPanel.java | 130 ++++++++++++++++++++ src/Person.java | 285 ++++++++++++++++++++++++++++++++++++++++++++ src/PersonPool.java | 59 +++++++++ src/Point.java | 44 +++++++ src/Virus.java | 10 ++ 16 files changed, 897 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 README.md create mode 100644 src/Bed.java create mode 100644 src/City.java create mode 100644 src/Constants.java create mode 100644 src/Hospital.java create mode 100644 src/Main.java create mode 100644 src/MathUtil.java create mode 100644 src/MoveTarget.java create mode 100644 src/MyPanel.java create mode 100644 src/Person.java create mode 100644 src/PersonPool.java create mode 100644 src/Point.java create mode 100644 src/Virus.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..0cbf9cd --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef675f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.iml +out/ +*.class diff --git a/.project b/.project new file mode 100644 index 0000000..799132a --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + VirusBroadcast + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..adcff1e --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# VirusBroadcast +一个基于java的模拟仿真程序,由于启动的时候时间仓促,数据不足,所以模型和推演过程过于简单,如果有好的想法或者能提供相关数据支持的朋友请提issues。 +如果您也是一名java程序员,可以直接修改并给我提交pr,我之前已经启动每日疫情数据的每日抓取工作,希望在疫情结束后有机会通过这些精准的的数据做一个复盘。 + +2020.2.6: +病毒变异过程是一个不断适应的过程,可以尝试简单的DNN对病毒进行建模,已经开始着手实施。 diff --git a/src/Bed.java b/src/Bed.java new file mode 100644 index 0000000..28d1478 --- /dev/null +++ b/src/Bed.java @@ -0,0 +1,27 @@ +/** + * 床位 + * + * + * @ClassName: Bed + * @Description: 床位 + * @author: Bruce Young + * @date: 2020年02月02日 21:00 + */ +public class Bed extends Point { + public Bed(int x, int y) { + super(x, y); + } + + /** + * 是否占用了该床位 + */ + private boolean isEmpty = true; + + public boolean isEmpty() { + return isEmpty; + } + + public void setEmpty(boolean empty) { + isEmpty = empty; + } +} diff --git a/src/City.java b/src/City.java new file mode 100644 index 0000000..9ff5198 --- /dev/null +++ b/src/City.java @@ -0,0 +1,33 @@ +/** + * 城市描述对象 + * + * @ClassName: City + * @Description: 城市描述对象 + * @author: Bruce Young + * @date: 2020年02月02日 17:48 + */ +public class City { + private int centerX; + private int centerY; + + public City(int centerX, int centerY) { + this.centerX = centerX; + this.centerY = centerY; + } + + public int getCenterX() { + return centerX; + } + + public void setCenterX(int centerX) { + this.centerX = centerX; + } + + public int getCenterY() { + return centerY; + } + + public void setCenterY(int centerY) { + this.centerY = centerY; + } +} diff --git a/src/Constants.java b/src/Constants.java new file mode 100644 index 0000000..1a722cb --- /dev/null +++ b/src/Constants.java @@ -0,0 +1,35 @@ +/** + * 模拟参数 + * + * @ClassName: Constants + * @Description: 模拟参数 + * @author: Bruce Young + * @date: 2020年02月02日 21:40 + */ +public class Constants { + + public static int ORIGINAL_COUNT = 50;//初始感染数量 + public static float BROAD_RATE = 0.8f;//传播率 + public static float SHADOW_TIME = 140;//潜伏时间,14天为140 + public static int HOSPITAL_RECEIVE_TIME = 10;//医院收治响应时间 + public static int BED_COUNT = 1000;//医院床位 + /** + * 流动意向平均值,建议调整范围:[-0.99,0.99] + *

+ * -0.99 人群流动最慢速率,甚至完全控制疫情传播 + * 0.99为人群流动最快速率, 可导致全城感染 + */ + public static float u = 0.99f; + public static int CITY_PERSON_SIZE = 5000;//城市总人口数量 + public static float FATALITY_RATE = 0.05f;//fatality_rate病死率,根据2月6日数据估算(病死数/确诊数)为0.02 + public static int DIE_TIME = 100;//死亡时间均值,30天,从发病(确诊)时开始计时 + public static double DIE_VARIANCE = 1;//死亡时间方差 + public static int CURE_TIME = 150;//治愈时间均值 + public static double CURE_VARIANCE = 20;//治愈时间方差 + /** + * 城市大小即窗口边界,限制不允许出城 + */ + public static final int CITY_WIDTH = 700; + public static final int CITY_HEIGHT = 800; + +} diff --git a/src/Hospital.java b/src/Hospital.java new file mode 100644 index 0000000..629789a --- /dev/null +++ b/src/Hospital.java @@ -0,0 +1,102 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 医院 + *

+ * 床位容量 + * + * @ClassName: Hospital + * @Description: 医院,包含床位容量 + * @author: Bruce Young + * @date: 2020年02月02日 20:58 + */ +public class Hospital extends Point { + public static final int HOSPITAL_X = 720; + public static final int HOSPITAL_Y = 80; + private int width; + private int height = 600; + + public int getWidth() { + return width; + } + + + public int getHeight() { + return height; + } + + + private static Hospital hospital = new Hospital(); + + public static Hospital getInstance() { + return hospital; + } + + private Point point = new Point(HOSPITAL_X, HOSPITAL_Y);//第一个床位所在坐标,用于给其他床位定绝对坐标 + private List beds = new ArrayList<>(); + + /** + * 获取所有床位 + * + * @return + */ + public List getBeds() { + return beds; + } + + private Hospital() { + //医院矩形所在坐标 + super(HOSPITAL_X, HOSPITAL_Y + 10); + //根据床位数量调整医院矩形的大小 + if (Constants.BED_COUNT == 0) { + width = 0; + height = 0; + } + //根据医院床位数量计算医院宽度 + //因为高度定了只能装载100个床位 + int column = Constants.BED_COUNT / 100; + width = column * 6; + //根据第一个床位坐标初始化其他床位的坐标 + for (int i = 0; i < column; i++) { + + for (int j = 10; j <= 606; j += 6) { + + Bed bed = new Bed(point.getX() + i * 6, point.getY() + j); + beds.add(bed); + if (beds.size() >= Constants.BED_COUNT) {//确定医院床位承载数量 + break; + } + } + + } + } + + /** + * 使用床位 + * + * @return + */ + public Bed pickBed() { + for (Bed bed : beds) { + if (bed.isEmpty()) { + return bed; + } + } + return null; + } + + /** + * 死亡或痊愈出院空出床位 + * + * @param bed + * @return + */ + public Bed returnBed(Bed bed) { + if (bed != null) { + bed.setEmpty(true); + } + return bed; + } +} diff --git a/src/Main.java b/src/Main.java new file mode 100644 index 0000000..e1c79cb --- /dev/null +++ b/src/Main.java @@ -0,0 +1,59 @@ +import javax.swing.*; + +import java.util.List; +import java.util.Random; + +/** + * 模拟程序主入口 + * + * @author + * @comment GinRyan + */ +public class Main { + + public static void main(String[] args) { + initHospital(); + initPanel(); + initInfected(); + } + + /** + * 初始化画布 + */ + private static void initPanel() { + MyPanel p = new MyPanel(); + Thread panelThread = new Thread(p); + JFrame frame = new JFrame(); + frame.add(p); + frame.setSize(Constants.CITY_WIDTH + hospitalWidth + 300, Constants.CITY_HEIGHT); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + frame.setTitle("瘟疫传播模拟"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + panelThread.start();//开启画布线程,即世界线程,接着看代码的下一站可以转MyPanel.java + } + + private static int hospitalWidth; + + /** + * 初始化医院参数 + */ + private static void initHospital() { + hospitalWidth = Hospital.getInstance().getWidth(); + } + + /** + * 初始化初始感染者 + */ + private static void initInfected() { + List people = PersonPool.getInstance().getPersonList();//获取所有的市民 + for (int i = 0; i < Constants.ORIGINAL_COUNT; i++) { + Person person; + do { + person = people.get(new Random().nextInt(people.size() - 1));//随机挑选一个市民 + } while (person.isInfected());//如果该市民已经被感染,重新挑选 + person.beInfected();//让这个幸运的市民成为感染者 + } + } + +} diff --git a/src/MathUtil.java b/src/MathUtil.java new file mode 100644 index 0000000..ba592e4 --- /dev/null +++ b/src/MathUtil.java @@ -0,0 +1,38 @@ +import java.util.Random; + +/** + * 数学算法工具类 + * + * @ClassName: MathUtil + * @Description: 数学算法工具类 + * @author: Bruce Young + * @date: 2020年02月06日 11:27 + */ +public class MathUtil { + /** + * 仅仅使用一个随机数生成器 + */ + private static final Random randomGen = new Random(); + + /** + * 标准正态分布化 + *

+ * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 + *

+ * 设X随机变量为服从正态分布,sigma是影响分布形态的系数 u值决定正态分布均值 + *

+ *

+ * 推导: + * StdX = (X-u)/sigma + * X = sigma * StdX + u + * + * @param sigma 正态标准差sigma值 + * @param u 正态均值参数mu + * @return + */ + public static double stdGaussian(double sigma, double u) { + double X = randomGen.nextGaussian(); + return sigma * X + u; + } + +} diff --git a/src/MoveTarget.java b/src/MoveTarget.java new file mode 100644 index 0000000..73c6ca3 --- /dev/null +++ b/src/MoveTarget.java @@ -0,0 +1,42 @@ +/** + * 位移目标对象 + * + * @ClassName: MoveTarget + * @Description: 位移目标对象 + * @author: Bruce Young + * @date: 2020年02月02日 17:47 + */ +public class MoveTarget { + private int x; + private int y; + private boolean arrived=false;//是否到达目标点 + + public MoveTarget(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } + + public boolean isArrived() { + return arrived; + } + + public void setArrived(boolean arrived) { + this.arrived = arrived; + } +} diff --git a/src/MyPanel.java b/src/MyPanel.java new file mode 100644 index 0000000..450983d --- /dev/null +++ b/src/MyPanel.java @@ -0,0 +1,130 @@ +import javax.swing.*; +import java.awt.*; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +/** + * 主面板。 + * + * @ClassName: MyPanel + * @Description: 主面板 + * @author: Bruce Young + * @date: 2020年02月02日 17:03 + */ +public class MyPanel extends JPanel implements Runnable { + + + + public MyPanel() { + super(); + this.setBackground(new Color(0x444444)); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + g.setColor(new Color(0x00ff00));//设置医院边界颜色 + //绘制医院边界 + g.drawRect(Hospital.getInstance().getX(), Hospital.getInstance().getY(), + Hospital.getInstance().getWidth(), Hospital.getInstance().getHeight()); + g.setFont(new Font("微软雅黑", Font.BOLD, 16)); + g.setColor(new Color(0x00ff00)); + g.drawString("医院", Hospital.getInstance().getX() + Hospital.getInstance().getWidth() / 4, Hospital.getInstance().getY() - 16); + //绘制代表人类的圆点 + List people = PersonPool.getInstance().getPersonList(); + if (people == null) { + return; + } + for (Person person : people) { + switch (person.getState()) { + case Person.State.NORMAL: { + //健康人 + g.setColor(new Color(0xdddddd)); + break; + } + case Person.State.SHADOW: { + //潜伏期感染者 + g.setColor(new Color(0xffee00)); + break; + } + + case Person.State.CONFIRMED: { + //确诊患者 + g.setColor(new Color(0xff0000)); + break; + } + case Person.State.FREEZE: { + //已隔离者 + g.setColor(new Color(0x48FFFC)); + break; + } + case Person.State.DEATH: { + //死亡患者 + + g.setColor(new Color(0x000000)); + break; + } + case Person.State.CURED: { + //治愈患者 + g.setColor(new Color(0x00ff00)); + } + } + person.update();//对各种状态的市民进行不同的处理 + g.fillOval(person.getX(), person.getY(), 3, 3); + + } + + int captionStartOffsetX = 700 + Hospital.getInstance().getWidth() + 40; + int captionStartOffsetY = 40; + int captionSize = 24; + + //显示数据信息 + g.setColor(Color.WHITE); + g.drawString("城市总人数:" + Constants.CITY_PERSON_SIZE, captionStartOffsetX, captionStartOffsetY); + g.setColor(new Color(0xdddddd)); + g.drawString("健康者人数:" + PersonPool.getInstance().getPeopleSize(Person.State.NORMAL), captionStartOffsetX, captionStartOffsetY + captionSize); + g.setColor(new Color(0xffee00)); + g.drawString("潜伏期人数:" + PersonPool.getInstance().getPeopleSize(Person.State.SHADOW), captionStartOffsetX, captionStartOffsetY + 2 * captionSize); + g.setColor(new Color(0xff0000)); + g.drawString("发病者人数:" + PersonPool.getInstance().getPeopleSize(Person.State.CONFIRMED), captionStartOffsetX, captionStartOffsetY + 3 * captionSize); + g.setColor(new Color(0x48FFFC)); + g.drawString("已隔离人数:" + PersonPool.getInstance().getPeopleSize(Person.State.FREEZE), captionStartOffsetX, captionStartOffsetY + 4 * captionSize); + g.setColor(new Color(0x00ff00)); + g.drawString("空余病床:" + Math.max(Constants.BED_COUNT - PersonPool.getInstance().getPeopleSize(Person.State.FREEZE), 0), captionStartOffsetX, captionStartOffsetY + 5 * captionSize); + g.setColor(new Color(0xE39476)); + //暂定急需病床数量为 NEED = 确诊发病者数量 - 已隔离住院数量 + // + int needBeds = PersonPool.getInstance().getPeopleSize(Person.State.CONFIRMED) + - PersonPool.getInstance().getPeopleSize(Person.State.FREEZE); + + g.drawString("急需病床:" + (needBeds > 0 ? needBeds : 0), captionStartOffsetX, captionStartOffsetY + 6 * captionSize); + g.setColor(new Color(0x000000)); + g.drawString("病死人数:" + PersonPool.getInstance().getPeopleSize(Person.State.DEATH), captionStartOffsetX, captionStartOffsetY + 7 * captionSize); + g.setColor(new Color(0x00ff00)); + g.drawString("治愈人数:" + PersonPool.getInstance().getPeopleSize(Person.State.CURED), captionStartOffsetX, captionStartOffsetY + 8 * captionSize); + g.setColor(new Color(0xffffff)); + g.drawString("世界时间(天):" + (int) (worldTime / 10.0), captionStartOffsetX, captionStartOffsetY + 9 * captionSize); + + } + + + public static int worldTime = 0;//世界时间 + + public Timer timer = new Timer(); + + class MyTimerTask extends TimerTask { + @Override + public void run() { + MyPanel.this.repaint(); + worldTime++; + } + } + + @Override + public void run() { + timer.schedule(new MyTimerTask(), 0, 100);//启动世界计时器,时间开始流动(突然脑补DIO台词:時は停た) + } + + +} diff --git a/src/Person.java b/src/Person.java new file mode 100644 index 0000000..0909d43 --- /dev/null +++ b/src/Person.java @@ -0,0 +1,285 @@ +import java.util.List; +import java.util.Random; + +/** + * 能够随机运动的民众 + * + * @ClassName: Person + * @Description: 能够随机运动的民众 + * @author: Bruce Young + * @date: 2020年02月02日 17:05 + */ + +public class Person extends Point { + private City city; + + private MoveTarget moveTarget; + /** + * 人群流动意愿影响系数:正态分布方差sigma + */ + int sig = 1; + + /** + * 正态分布N(mu,sigma)随机位移目标位置 + */ + + double targetXU;//x方向的均值mu + double targetYU;//y方向的均值mu + double targetSig = 50;//方差sigma + + /** + * 市民的状态 + *

+ * 市民状态应该需要细分,虽然有的状态暂未纳入模拟,但是细分状态应该保留 + */ + public interface State { + int NORMAL = 0;//正常人,未感染的健康人 + int SUSPECTED = NORMAL + 1;//有暴露感染风险 + int SHADOW = SUSPECTED + 1;//潜伏期 + int CONFIRMED = SHADOW + 1;//发病且已确诊为感染病人 + int FREEZE = CONFIRMED + 1;//隔离治疗,禁止位移 + + //已治愈出院的人转为NORMAL即可,否则会与作者通过数值大小判断状态的代码冲突 + int DEATH = FREEZE + 1;//病死者 + int CURED = DEATH + 1;//治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定 + } + + public Person(City city, int x, int y) { + super(x, y); + this.city = city; + //对市民的初始位置进行N(x,100)的正态分布随机 + targetXU = MathUtil.stdGaussian(100, x); + targetYU = MathUtil.stdGaussian(100, y); + + } + + /** + * 流动意愿标准化 + *

+ * 根据标准正态分布生成随机人口流动意愿 + *

+ * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 + *

+ * 设X随机变量为服从正态分布,sigma是影响分布形态的系数,从而影响整体人群流动意愿分布 + * u值决定正态分布的中轴是让更多人群偏向希望流动或者希望懒惰。 + *

+ * value的推导: + * StdX = (X-u)/sigma + * X = sigma * StdX + u + * + * @return + */ + public boolean wantMove() { + return MathUtil.stdGaussian(sig, Constants.u) > 0; + } + + private int state = State.NORMAL; + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + int infectedTime = 0;//感染时刻 + int confirmedTime = 0;//确诊时刻 + int dieMoment = 0;//死亡时刻,为0代表未确定,-1代表不会病死 + int cureMoment = 0;//治愈时刻 + + + public boolean isInfected() { + return state >= State.SHADOW; + } + + public void beInfected() { + state = State.SHADOW; + infectedTime = MyPanel.worldTime; + } + + /** + * 计算两点之间的直线距离 + * + * @param person + * @return + */ + public double distance(Person person) { + return Math.sqrt(Math.pow(getX() - person.getX(), 2) + Math.pow(getY() - person.getY(), 2)); + } + + /** + * 住院 + */ + private void freezy() { + state = State.FREEZE; + } + + /** + * 不同状态下的单个人实例运动行为 + */ + private void action() { + + if (state == State.FREEZE || state == State.DEATH) { + return;//如果处于隔离或者死亡状态,则无法行动 + } + if (!wantMove()) { + return; + } + //存在流动意愿的,将进行流动,流动位移仍然遵循标准正态分布 + if (moveTarget == null || moveTarget.isArrived()) { + //在想要移动并且没有目标时,将自身移动目标设置为随机生成的符合正态分布的目标点 + //产生N(a,b)的数:Math.sqrt(b)*random.nextGaussian()+a + double targetX = MathUtil.stdGaussian(targetSig, targetXU); + double targetY = MathUtil.stdGaussian(targetSig, targetYU); + moveTarget = new MoveTarget((int) targetX, (int) targetY); + + } + + //计算运动位移 + int dX = moveTarget.getX() - getX(); + int dY = moveTarget.getY() - getY(); + + double length = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));//与目标点的距离 + + if (length < 1) { + //判断是否到达目标点 + moveTarget.setArrived(true); + return; + } + + int udX = (int) (dX / length);//x轴dX为位移量,符号为沿x轴前进方向, 即udX为X方向表示量 + if (udX == 0 && dX != 0) { + if (dX > 0) { + udX = 1; + } else { + udX = -1; + } + } + + + int udY = (int) (dY / length);//y轴dY为位移量,符号为沿x轴前进方向,即udY为Y方向表示量 + //FIXED: 修正一处错误 + if (udY == 0 && dY != 0) { + if (dY > 0) { + udY = 1; + } else { + udY = -1; + } + } + + //横向运动边界 + if (getX() > Constants.CITY_WIDTH || getX() < 0) { + moveTarget = null; + if (udX > 0) { + udX = -udX; + } + } + //纵向运动边界 + if (getY() > Constants.CITY_HEIGHT || getY() < 0) { + moveTarget = null; + if (udY > 0) { + udY = -udY; + } + } + moveTo(udX, udY); + + } + + public Bed useBed; + + private float SAFE_DIST = 2f;//安全距离 + + /** + * 对各种状态的人进行不同的处理,更新发布市民健康状态 + */ + public void update() { + //@TODO找时间改为状态机 + + if (state == State.DEATH) { + return;//如果已经隔离或者死亡了,就不需要处理了 + } + + //处理已经确诊的感染者(即患者) + if (state == State.CONFIRMED && dieMoment == 0) { + + int destiny = new Random().nextInt(10000) + 1;//幸运数字,[1,10000]随机数 + if (1 <= destiny && destiny <= (int) (Constants.FATALITY_RATE * 10000)) { + + //如果幸运数字落在死亡区间 + int dieTime = (int) MathUtil.stdGaussian(Constants.DIE_VARIANCE, Constants.DIE_TIME); + dieMoment = confirmedTime + dieTime;//发病后确定死亡时刻 + } else { + dieMoment = -1;//逃过了死神的魔爪 + + } + } + //治愈 + if (state == State.FREEZE && cureMoment == 0) { + int cureTime = (int) MathUtil.stdGaussian(Constants.CURE_VARIANCE, Constants.CURE_TIME); + if (cureTime > dieMoment - MyPanel.worldTime && dieMoment > 0) { + cureMoment = -1; //来不及治好,死亡时间在治愈时间之前 + } else { + cureMoment = MyPanel.worldTime + cureTime; + } + } + + if (state == State.FREEZE && cureMoment > 0 && MyPanel.worldTime >= cureMoment) { + state = State.CURED; + Hospital.getInstance().returnBed(useBed); + } + + if (state == State.CONFIRMED + && MyPanel.worldTime - confirmedTime >= Constants.HOSPITAL_RECEIVE_TIME) { + //如果患者已经确诊,且(世界时刻-确诊时刻)大于医院响应时间,即医院准备好病床了,可以抬走了 + Bed bed = Hospital.getInstance().pickBed();//查找空床位 + if (bed == null) { + + //没有床位了,报告需求床位数 + + } else { + //安置病人 + useBed = bed; + state = State.FREEZE; + setX(bed.getX()); + setY(bed.getY()); + bed.setEmpty(false); + } + } + + //处理病死者 + if ((state == State.CONFIRMED || state == State.FREEZE) && MyPanel.worldTime >= dieMoment && dieMoment > 0) { + state = State.DEATH;//患者死亡 + // 随机丢弃尸体,没有火葬场和墓地,暂时这样处理 + super.setX((int) MathUtil.stdGaussian(100, city.getCenterX())); + super.setY((int) MathUtil.stdGaussian(100, city.getCenterY())); + Hospital.getInstance().returnBed(useBed);//归还床位 + } + + //增加一个正态分布用于潜伏期内随机发病时间 + double stdRnShadowtime = MathUtil.stdGaussian(25, Constants.SHADOW_TIME / 2); + //处理发病的潜伏期感染者 + if (MyPanel.worldTime - infectedTime > stdRnShadowtime && state == State.SHADOW) { + state = State.CONFIRMED;//潜伏者发病 + confirmedTime = MyPanel.worldTime;//刷新时间 + } + //处理未隔离者的移动问题 + action(); + //处理健康人被感染的问题 + List people = PersonPool.getInstance().personList; + if (state >= State.SHADOW) { + return; + } + //通过一个随机幸运值和安全距离决定感染其他人 + for (Person person : people) { + if (person.getState() == State.NORMAL) { + continue; + } + float random = new Random().nextFloat(); + if (random < Constants.BROAD_RATE && distance(person) < SAFE_DIST) { + this.beInfected(); + break; + } + } + } +} diff --git a/src/PersonPool.java b/src/PersonPool.java new file mode 100644 index 0000000..87a672c --- /dev/null +++ b/src/PersonPool.java @@ -0,0 +1,59 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * 区域人群对象池 + * + * @ClassName: PersonPool + * @Description: 区域人群对象池,该地区假设为一个近似封闭的环境,拥有几乎不变的民众数量 + * @author: Bruce Young + * @date: 2020年02月02日 17:21 + */ +public class PersonPool { + private static PersonPool personPool = new PersonPool(); + + public static PersonPool getInstance() { + return personPool; + } + + List personList = new ArrayList(); + + public List getPersonList() { + return personList; + } + + + /** + * @param state 市民类型 Person.State的值,若为-1则返回当前总数目 + * @return 获取指定人群数量 + */ + public int getPeopleSize(int state) { + if (state == -1) { + return personList.size(); + } + int i = 0; + for (Person person : personList) { + if (person.getState() == state) { + i++; + } + } + return i; + } + + + private PersonPool() { + City city = new City(400, 400);//设置城市中心为坐标(400,400) + //添加城市居民 + for (int i = 0; i < Constants.CITY_PERSON_SIZE; i++) { + Random random = new Random(); + //产生N(a,b)的数:Math.sqrt(b)*random.nextGaussian()+a + int x = (int) (100 * random.nextGaussian() + city.getCenterX()); + int y = (int) (100 * random.nextGaussian() + city.getCenterY()); + if (x > 700) { + x = 700; + } + personList.add(new Person(city, x, y)); + } + } +} diff --git a/src/Point.java b/src/Point.java new file mode 100644 index 0000000..747b6ad --- /dev/null +++ b/src/Point.java @@ -0,0 +1,44 @@ +/** + * 位置坐标基类 + * + * @ClassName: Point + * @Description: 位置坐标基类 + * @author: Bruce Young + * @date: 2020年02月02日 20:59 + */ +public class Point { + private int x; + private int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * 位移 + * + * @param x + * @param y + */ + public void moveTo(int x, int y) { + this.x += x; + this.y += y; + } + + public int getX() { + return x; + } + + public void setX(int x) { + this.x = x; + } + + public int getY() { + return y; + } + + public void setY(int y) { + this.y = y; + } +} diff --git a/src/Virus.java b/src/Virus.java new file mode 100644 index 0000000..acda218 --- /dev/null +++ b/src/Virus.java @@ -0,0 +1,10 @@ +/** + * 病毒对象 + * + * @ClassName: Virus + * @Description: 病毒对象 + * @author: Bruce Young + * @date: 2020年02月02日 17:04 + */ +public class Virus { +} From 35c757e3aa9bd05f6f422f8f483c4c7febe59e08 Mon Sep 17 00:00:00 2001 From: PlanetMoon Date: Sun, 9 Feb 2020 09:55:40 +0800 Subject: [PATCH 2/4] update with Cure --- .classpath | 6 - .gitignore | 4 - .project | 17 --- README.md | 6 - VirusBroadcast | 1 + src/Bed.java | 27 ----- src/City.java | 33 ----- src/Constants.java | 35 ------ src/Hospital.java | 102 ---------------- src/Main.java | 59 --------- src/MathUtil.java | 38 ------ src/MoveTarget.java | 42 ------- src/MyPanel.java | 130 -------------------- src/Person.java | 285 -------------------------------------------- src/PersonPool.java | 59 --------- src/Point.java | 44 ------- src/Virus.java | 10 -- 17 files changed, 1 insertion(+), 897 deletions(-) delete mode 100644 .classpath delete mode 100644 .gitignore delete mode 100644 .project delete mode 100644 README.md create mode 160000 VirusBroadcast delete mode 100644 src/Bed.java delete mode 100644 src/City.java delete mode 100644 src/Constants.java delete mode 100644 src/Hospital.java delete mode 100644 src/Main.java delete mode 100644 src/MathUtil.java delete mode 100644 src/MoveTarget.java delete mode 100644 src/MyPanel.java delete mode 100644 src/Person.java delete mode 100644 src/PersonPool.java delete mode 100644 src/Point.java delete mode 100644 src/Virus.java diff --git a/.classpath b/.classpath deleted file mode 100644 index 0cbf9cd..0000000 --- a/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ef675f0..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.idea/ -*.iml -out/ -*.class diff --git a/.project b/.project deleted file mode 100644 index 799132a..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - VirusBroadcast - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/README.md b/README.md deleted file mode 100644 index adcff1e..0000000 --- a/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# VirusBroadcast -一个基于java的模拟仿真程序,由于启动的时候时间仓促,数据不足,所以模型和推演过程过于简单,如果有好的想法或者能提供相关数据支持的朋友请提issues。 -如果您也是一名java程序员,可以直接修改并给我提交pr,我之前已经启动每日疫情数据的每日抓取工作,希望在疫情结束后有机会通过这些精准的的数据做一个复盘。 - -2020.2.6: -病毒变异过程是一个不断适应的过程,可以尝试简单的DNN对病毒进行建模,已经开始着手实施。 diff --git a/VirusBroadcast b/VirusBroadcast new file mode 160000 index 0000000..b6badc7 --- /dev/null +++ b/VirusBroadcast @@ -0,0 +1 @@ +Subproject commit b6badc75be0ef996a68810b722356bc3423ea8b9 diff --git a/src/Bed.java b/src/Bed.java deleted file mode 100644 index 28d1478..0000000 --- a/src/Bed.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 床位 - * - * - * @ClassName: Bed - * @Description: 床位 - * @author: Bruce Young - * @date: 2020年02月02日 21:00 - */ -public class Bed extends Point { - public Bed(int x, int y) { - super(x, y); - } - - /** - * 是否占用了该床位 - */ - private boolean isEmpty = true; - - public boolean isEmpty() { - return isEmpty; - } - - public void setEmpty(boolean empty) { - isEmpty = empty; - } -} diff --git a/src/City.java b/src/City.java deleted file mode 100644 index 9ff5198..0000000 --- a/src/City.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 城市描述对象 - * - * @ClassName: City - * @Description: 城市描述对象 - * @author: Bruce Young - * @date: 2020年02月02日 17:48 - */ -public class City { - private int centerX; - private int centerY; - - public City(int centerX, int centerY) { - this.centerX = centerX; - this.centerY = centerY; - } - - public int getCenterX() { - return centerX; - } - - public void setCenterX(int centerX) { - this.centerX = centerX; - } - - public int getCenterY() { - return centerY; - } - - public void setCenterY(int centerY) { - this.centerY = centerY; - } -} diff --git a/src/Constants.java b/src/Constants.java deleted file mode 100644 index 1a722cb..0000000 --- a/src/Constants.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 模拟参数 - * - * @ClassName: Constants - * @Description: 模拟参数 - * @author: Bruce Young - * @date: 2020年02月02日 21:40 - */ -public class Constants { - - public static int ORIGINAL_COUNT = 50;//初始感染数量 - public static float BROAD_RATE = 0.8f;//传播率 - public static float SHADOW_TIME = 140;//潜伏时间,14天为140 - public static int HOSPITAL_RECEIVE_TIME = 10;//医院收治响应时间 - public static int BED_COUNT = 1000;//医院床位 - /** - * 流动意向平均值,建议调整范围:[-0.99,0.99] - *

- * -0.99 人群流动最慢速率,甚至完全控制疫情传播 - * 0.99为人群流动最快速率, 可导致全城感染 - */ - public static float u = 0.99f; - public static int CITY_PERSON_SIZE = 5000;//城市总人口数量 - public static float FATALITY_RATE = 0.05f;//fatality_rate病死率,根据2月6日数据估算(病死数/确诊数)为0.02 - public static int DIE_TIME = 100;//死亡时间均值,30天,从发病(确诊)时开始计时 - public static double DIE_VARIANCE = 1;//死亡时间方差 - public static int CURE_TIME = 150;//治愈时间均值 - public static double CURE_VARIANCE = 20;//治愈时间方差 - /** - * 城市大小即窗口边界,限制不允许出城 - */ - public static final int CITY_WIDTH = 700; - public static final int CITY_HEIGHT = 800; - -} diff --git a/src/Hospital.java b/src/Hospital.java deleted file mode 100644 index 629789a..0000000 --- a/src/Hospital.java +++ /dev/null @@ -1,102 +0,0 @@ -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * 医院 - *

- * 床位容量 - * - * @ClassName: Hospital - * @Description: 医院,包含床位容量 - * @author: Bruce Young - * @date: 2020年02月02日 20:58 - */ -public class Hospital extends Point { - public static final int HOSPITAL_X = 720; - public static final int HOSPITAL_Y = 80; - private int width; - private int height = 600; - - public int getWidth() { - return width; - } - - - public int getHeight() { - return height; - } - - - private static Hospital hospital = new Hospital(); - - public static Hospital getInstance() { - return hospital; - } - - private Point point = new Point(HOSPITAL_X, HOSPITAL_Y);//第一个床位所在坐标,用于给其他床位定绝对坐标 - private List beds = new ArrayList<>(); - - /** - * 获取所有床位 - * - * @return - */ - public List getBeds() { - return beds; - } - - private Hospital() { - //医院矩形所在坐标 - super(HOSPITAL_X, HOSPITAL_Y + 10); - //根据床位数量调整医院矩形的大小 - if (Constants.BED_COUNT == 0) { - width = 0; - height = 0; - } - //根据医院床位数量计算医院宽度 - //因为高度定了只能装载100个床位 - int column = Constants.BED_COUNT / 100; - width = column * 6; - //根据第一个床位坐标初始化其他床位的坐标 - for (int i = 0; i < column; i++) { - - for (int j = 10; j <= 606; j += 6) { - - Bed bed = new Bed(point.getX() + i * 6, point.getY() + j); - beds.add(bed); - if (beds.size() >= Constants.BED_COUNT) {//确定医院床位承载数量 - break; - } - } - - } - } - - /** - * 使用床位 - * - * @return - */ - public Bed pickBed() { - for (Bed bed : beds) { - if (bed.isEmpty()) { - return bed; - } - } - return null; - } - - /** - * 死亡或痊愈出院空出床位 - * - * @param bed - * @return - */ - public Bed returnBed(Bed bed) { - if (bed != null) { - bed.setEmpty(true); - } - return bed; - } -} diff --git a/src/Main.java b/src/Main.java deleted file mode 100644 index e1c79cb..0000000 --- a/src/Main.java +++ /dev/null @@ -1,59 +0,0 @@ -import javax.swing.*; - -import java.util.List; -import java.util.Random; - -/** - * 模拟程序主入口 - * - * @author - * @comment GinRyan - */ -public class Main { - - public static void main(String[] args) { - initHospital(); - initPanel(); - initInfected(); - } - - /** - * 初始化画布 - */ - private static void initPanel() { - MyPanel p = new MyPanel(); - Thread panelThread = new Thread(p); - JFrame frame = new JFrame(); - frame.add(p); - frame.setSize(Constants.CITY_WIDTH + hospitalWidth + 300, Constants.CITY_HEIGHT); - frame.setLocationRelativeTo(null); - frame.setVisible(true); - frame.setTitle("瘟疫传播模拟"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - panelThread.start();//开启画布线程,即世界线程,接着看代码的下一站可以转MyPanel.java - } - - private static int hospitalWidth; - - /** - * 初始化医院参数 - */ - private static void initHospital() { - hospitalWidth = Hospital.getInstance().getWidth(); - } - - /** - * 初始化初始感染者 - */ - private static void initInfected() { - List people = PersonPool.getInstance().getPersonList();//获取所有的市民 - for (int i = 0; i < Constants.ORIGINAL_COUNT; i++) { - Person person; - do { - person = people.get(new Random().nextInt(people.size() - 1));//随机挑选一个市民 - } while (person.isInfected());//如果该市民已经被感染,重新挑选 - person.beInfected();//让这个幸运的市民成为感染者 - } - } - -} diff --git a/src/MathUtil.java b/src/MathUtil.java deleted file mode 100644 index ba592e4..0000000 --- a/src/MathUtil.java +++ /dev/null @@ -1,38 +0,0 @@ -import java.util.Random; - -/** - * 数学算法工具类 - * - * @ClassName: MathUtil - * @Description: 数学算法工具类 - * @author: Bruce Young - * @date: 2020年02月06日 11:27 - */ -public class MathUtil { - /** - * 仅仅使用一个随机数生成器 - */ - private static final Random randomGen = new Random(); - - /** - * 标准正态分布化 - *

- * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 - *

- * 设X随机变量为服从正态分布,sigma是影响分布形态的系数 u值决定正态分布均值 - *

- *

- * 推导: - * StdX = (X-u)/sigma - * X = sigma * StdX + u - * - * @param sigma 正态标准差sigma值 - * @param u 正态均值参数mu - * @return - */ - public static double stdGaussian(double sigma, double u) { - double X = randomGen.nextGaussian(); - return sigma * X + u; - } - -} diff --git a/src/MoveTarget.java b/src/MoveTarget.java deleted file mode 100644 index 73c6ca3..0000000 --- a/src/MoveTarget.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * 位移目标对象 - * - * @ClassName: MoveTarget - * @Description: 位移目标对象 - * @author: Bruce Young - * @date: 2020年02月02日 17:47 - */ -public class MoveTarget { - private int x; - private int y; - private boolean arrived=false;//是否到达目标点 - - public MoveTarget(int x, int y) { - this.x = x; - this.y = y; - } - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } - - public int getY() { - return y; - } - - public void setY(int y) { - this.y = y; - } - - public boolean isArrived() { - return arrived; - } - - public void setArrived(boolean arrived) { - this.arrived = arrived; - } -} diff --git a/src/MyPanel.java b/src/MyPanel.java deleted file mode 100644 index 450983d..0000000 --- a/src/MyPanel.java +++ /dev/null @@ -1,130 +0,0 @@ -import javax.swing.*; -import java.awt.*; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -/** - * 主面板。 - * - * @ClassName: MyPanel - * @Description: 主面板 - * @author: Bruce Young - * @date: 2020年02月02日 17:03 - */ -public class MyPanel extends JPanel implements Runnable { - - - - public MyPanel() { - super(); - this.setBackground(new Color(0x444444)); - } - - @Override - public void paint(Graphics g) { - super.paint(g); - g.setColor(new Color(0x00ff00));//设置医院边界颜色 - //绘制医院边界 - g.drawRect(Hospital.getInstance().getX(), Hospital.getInstance().getY(), - Hospital.getInstance().getWidth(), Hospital.getInstance().getHeight()); - g.setFont(new Font("微软雅黑", Font.BOLD, 16)); - g.setColor(new Color(0x00ff00)); - g.drawString("医院", Hospital.getInstance().getX() + Hospital.getInstance().getWidth() / 4, Hospital.getInstance().getY() - 16); - //绘制代表人类的圆点 - List people = PersonPool.getInstance().getPersonList(); - if (people == null) { - return; - } - for (Person person : people) { - switch (person.getState()) { - case Person.State.NORMAL: { - //健康人 - g.setColor(new Color(0xdddddd)); - break; - } - case Person.State.SHADOW: { - //潜伏期感染者 - g.setColor(new Color(0xffee00)); - break; - } - - case Person.State.CONFIRMED: { - //确诊患者 - g.setColor(new Color(0xff0000)); - break; - } - case Person.State.FREEZE: { - //已隔离者 - g.setColor(new Color(0x48FFFC)); - break; - } - case Person.State.DEATH: { - //死亡患者 - - g.setColor(new Color(0x000000)); - break; - } - case Person.State.CURED: { - //治愈患者 - g.setColor(new Color(0x00ff00)); - } - } - person.update();//对各种状态的市民进行不同的处理 - g.fillOval(person.getX(), person.getY(), 3, 3); - - } - - int captionStartOffsetX = 700 + Hospital.getInstance().getWidth() + 40; - int captionStartOffsetY = 40; - int captionSize = 24; - - //显示数据信息 - g.setColor(Color.WHITE); - g.drawString("城市总人数:" + Constants.CITY_PERSON_SIZE, captionStartOffsetX, captionStartOffsetY); - g.setColor(new Color(0xdddddd)); - g.drawString("健康者人数:" + PersonPool.getInstance().getPeopleSize(Person.State.NORMAL), captionStartOffsetX, captionStartOffsetY + captionSize); - g.setColor(new Color(0xffee00)); - g.drawString("潜伏期人数:" + PersonPool.getInstance().getPeopleSize(Person.State.SHADOW), captionStartOffsetX, captionStartOffsetY + 2 * captionSize); - g.setColor(new Color(0xff0000)); - g.drawString("发病者人数:" + PersonPool.getInstance().getPeopleSize(Person.State.CONFIRMED), captionStartOffsetX, captionStartOffsetY + 3 * captionSize); - g.setColor(new Color(0x48FFFC)); - g.drawString("已隔离人数:" + PersonPool.getInstance().getPeopleSize(Person.State.FREEZE), captionStartOffsetX, captionStartOffsetY + 4 * captionSize); - g.setColor(new Color(0x00ff00)); - g.drawString("空余病床:" + Math.max(Constants.BED_COUNT - PersonPool.getInstance().getPeopleSize(Person.State.FREEZE), 0), captionStartOffsetX, captionStartOffsetY + 5 * captionSize); - g.setColor(new Color(0xE39476)); - //暂定急需病床数量为 NEED = 确诊发病者数量 - 已隔离住院数量 - // - int needBeds = PersonPool.getInstance().getPeopleSize(Person.State.CONFIRMED) - - PersonPool.getInstance().getPeopleSize(Person.State.FREEZE); - - g.drawString("急需病床:" + (needBeds > 0 ? needBeds : 0), captionStartOffsetX, captionStartOffsetY + 6 * captionSize); - g.setColor(new Color(0x000000)); - g.drawString("病死人数:" + PersonPool.getInstance().getPeopleSize(Person.State.DEATH), captionStartOffsetX, captionStartOffsetY + 7 * captionSize); - g.setColor(new Color(0x00ff00)); - g.drawString("治愈人数:" + PersonPool.getInstance().getPeopleSize(Person.State.CURED), captionStartOffsetX, captionStartOffsetY + 8 * captionSize); - g.setColor(new Color(0xffffff)); - g.drawString("世界时间(天):" + (int) (worldTime / 10.0), captionStartOffsetX, captionStartOffsetY + 9 * captionSize); - - } - - - public static int worldTime = 0;//世界时间 - - public Timer timer = new Timer(); - - class MyTimerTask extends TimerTask { - @Override - public void run() { - MyPanel.this.repaint(); - worldTime++; - } - } - - @Override - public void run() { - timer.schedule(new MyTimerTask(), 0, 100);//启动世界计时器,时间开始流动(突然脑补DIO台词:時は停た) - } - - -} diff --git a/src/Person.java b/src/Person.java deleted file mode 100644 index 0909d43..0000000 --- a/src/Person.java +++ /dev/null @@ -1,285 +0,0 @@ -import java.util.List; -import java.util.Random; - -/** - * 能够随机运动的民众 - * - * @ClassName: Person - * @Description: 能够随机运动的民众 - * @author: Bruce Young - * @date: 2020年02月02日 17:05 - */ - -public class Person extends Point { - private City city; - - private MoveTarget moveTarget; - /** - * 人群流动意愿影响系数:正态分布方差sigma - */ - int sig = 1; - - /** - * 正态分布N(mu,sigma)随机位移目标位置 - */ - - double targetXU;//x方向的均值mu - double targetYU;//y方向的均值mu - double targetSig = 50;//方差sigma - - /** - * 市民的状态 - *

- * 市民状态应该需要细分,虽然有的状态暂未纳入模拟,但是细分状态应该保留 - */ - public interface State { - int NORMAL = 0;//正常人,未感染的健康人 - int SUSPECTED = NORMAL + 1;//有暴露感染风险 - int SHADOW = SUSPECTED + 1;//潜伏期 - int CONFIRMED = SHADOW + 1;//发病且已确诊为感染病人 - int FREEZE = CONFIRMED + 1;//隔离治疗,禁止位移 - - //已治愈出院的人转为NORMAL即可,否则会与作者通过数值大小判断状态的代码冲突 - int DEATH = FREEZE + 1;//病死者 - int CURED = DEATH + 1;//治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定 - } - - public Person(City city, int x, int y) { - super(x, y); - this.city = city; - //对市民的初始位置进行N(x,100)的正态分布随机 - targetXU = MathUtil.stdGaussian(100, x); - targetYU = MathUtil.stdGaussian(100, y); - - } - - /** - * 流动意愿标准化 - *

- * 根据标准正态分布生成随机人口流动意愿 - *

- * 流动意愿标准化后判断是在0的左边还是右边从而决定是否流动。 - *

- * 设X随机变量为服从正态分布,sigma是影响分布形态的系数,从而影响整体人群流动意愿分布 - * u值决定正态分布的中轴是让更多人群偏向希望流动或者希望懒惰。 - *

- * value的推导: - * StdX = (X-u)/sigma - * X = sigma * StdX + u - * - * @return - */ - public boolean wantMove() { - return MathUtil.stdGaussian(sig, Constants.u) > 0; - } - - private int state = State.NORMAL; - - public int getState() { - return state; - } - - public void setState(int state) { - this.state = state; - } - - int infectedTime = 0;//感染时刻 - int confirmedTime = 0;//确诊时刻 - int dieMoment = 0;//死亡时刻,为0代表未确定,-1代表不会病死 - int cureMoment = 0;//治愈时刻 - - - public boolean isInfected() { - return state >= State.SHADOW; - } - - public void beInfected() { - state = State.SHADOW; - infectedTime = MyPanel.worldTime; - } - - /** - * 计算两点之间的直线距离 - * - * @param person - * @return - */ - public double distance(Person person) { - return Math.sqrt(Math.pow(getX() - person.getX(), 2) + Math.pow(getY() - person.getY(), 2)); - } - - /** - * 住院 - */ - private void freezy() { - state = State.FREEZE; - } - - /** - * 不同状态下的单个人实例运动行为 - */ - private void action() { - - if (state == State.FREEZE || state == State.DEATH) { - return;//如果处于隔离或者死亡状态,则无法行动 - } - if (!wantMove()) { - return; - } - //存在流动意愿的,将进行流动,流动位移仍然遵循标准正态分布 - if (moveTarget == null || moveTarget.isArrived()) { - //在想要移动并且没有目标时,将自身移动目标设置为随机生成的符合正态分布的目标点 - //产生N(a,b)的数:Math.sqrt(b)*random.nextGaussian()+a - double targetX = MathUtil.stdGaussian(targetSig, targetXU); - double targetY = MathUtil.stdGaussian(targetSig, targetYU); - moveTarget = new MoveTarget((int) targetX, (int) targetY); - - } - - //计算运动位移 - int dX = moveTarget.getX() - getX(); - int dY = moveTarget.getY() - getY(); - - double length = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));//与目标点的距离 - - if (length < 1) { - //判断是否到达目标点 - moveTarget.setArrived(true); - return; - } - - int udX = (int) (dX / length);//x轴dX为位移量,符号为沿x轴前进方向, 即udX为X方向表示量 - if (udX == 0 && dX != 0) { - if (dX > 0) { - udX = 1; - } else { - udX = -1; - } - } - - - int udY = (int) (dY / length);//y轴dY为位移量,符号为沿x轴前进方向,即udY为Y方向表示量 - //FIXED: 修正一处错误 - if (udY == 0 && dY != 0) { - if (dY > 0) { - udY = 1; - } else { - udY = -1; - } - } - - //横向运动边界 - if (getX() > Constants.CITY_WIDTH || getX() < 0) { - moveTarget = null; - if (udX > 0) { - udX = -udX; - } - } - //纵向运动边界 - if (getY() > Constants.CITY_HEIGHT || getY() < 0) { - moveTarget = null; - if (udY > 0) { - udY = -udY; - } - } - moveTo(udX, udY); - - } - - public Bed useBed; - - private float SAFE_DIST = 2f;//安全距离 - - /** - * 对各种状态的人进行不同的处理,更新发布市民健康状态 - */ - public void update() { - //@TODO找时间改为状态机 - - if (state == State.DEATH) { - return;//如果已经隔离或者死亡了,就不需要处理了 - } - - //处理已经确诊的感染者(即患者) - if (state == State.CONFIRMED && dieMoment == 0) { - - int destiny = new Random().nextInt(10000) + 1;//幸运数字,[1,10000]随机数 - if (1 <= destiny && destiny <= (int) (Constants.FATALITY_RATE * 10000)) { - - //如果幸运数字落在死亡区间 - int dieTime = (int) MathUtil.stdGaussian(Constants.DIE_VARIANCE, Constants.DIE_TIME); - dieMoment = confirmedTime + dieTime;//发病后确定死亡时刻 - } else { - dieMoment = -1;//逃过了死神的魔爪 - - } - } - //治愈 - if (state == State.FREEZE && cureMoment == 0) { - int cureTime = (int) MathUtil.stdGaussian(Constants.CURE_VARIANCE, Constants.CURE_TIME); - if (cureTime > dieMoment - MyPanel.worldTime && dieMoment > 0) { - cureMoment = -1; //来不及治好,死亡时间在治愈时间之前 - } else { - cureMoment = MyPanel.worldTime + cureTime; - } - } - - if (state == State.FREEZE && cureMoment > 0 && MyPanel.worldTime >= cureMoment) { - state = State.CURED; - Hospital.getInstance().returnBed(useBed); - } - - if (state == State.CONFIRMED - && MyPanel.worldTime - confirmedTime >= Constants.HOSPITAL_RECEIVE_TIME) { - //如果患者已经确诊,且(世界时刻-确诊时刻)大于医院响应时间,即医院准备好病床了,可以抬走了 - Bed bed = Hospital.getInstance().pickBed();//查找空床位 - if (bed == null) { - - //没有床位了,报告需求床位数 - - } else { - //安置病人 - useBed = bed; - state = State.FREEZE; - setX(bed.getX()); - setY(bed.getY()); - bed.setEmpty(false); - } - } - - //处理病死者 - if ((state == State.CONFIRMED || state == State.FREEZE) && MyPanel.worldTime >= dieMoment && dieMoment > 0) { - state = State.DEATH;//患者死亡 - // 随机丢弃尸体,没有火葬场和墓地,暂时这样处理 - super.setX((int) MathUtil.stdGaussian(100, city.getCenterX())); - super.setY((int) MathUtil.stdGaussian(100, city.getCenterY())); - Hospital.getInstance().returnBed(useBed);//归还床位 - } - - //增加一个正态分布用于潜伏期内随机发病时间 - double stdRnShadowtime = MathUtil.stdGaussian(25, Constants.SHADOW_TIME / 2); - //处理发病的潜伏期感染者 - if (MyPanel.worldTime - infectedTime > stdRnShadowtime && state == State.SHADOW) { - state = State.CONFIRMED;//潜伏者发病 - confirmedTime = MyPanel.worldTime;//刷新时间 - } - //处理未隔离者的移动问题 - action(); - //处理健康人被感染的问题 - List people = PersonPool.getInstance().personList; - if (state >= State.SHADOW) { - return; - } - //通过一个随机幸运值和安全距离决定感染其他人 - for (Person person : people) { - if (person.getState() == State.NORMAL) { - continue; - } - float random = new Random().nextFloat(); - if (random < Constants.BROAD_RATE && distance(person) < SAFE_DIST) { - this.beInfected(); - break; - } - } - } -} diff --git a/src/PersonPool.java b/src/PersonPool.java deleted file mode 100644 index 87a672c..0000000 --- a/src/PersonPool.java +++ /dev/null @@ -1,59 +0,0 @@ -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * 区域人群对象池 - * - * @ClassName: PersonPool - * @Description: 区域人群对象池,该地区假设为一个近似封闭的环境,拥有几乎不变的民众数量 - * @author: Bruce Young - * @date: 2020年02月02日 17:21 - */ -public class PersonPool { - private static PersonPool personPool = new PersonPool(); - - public static PersonPool getInstance() { - return personPool; - } - - List personList = new ArrayList(); - - public List getPersonList() { - return personList; - } - - - /** - * @param state 市民类型 Person.State的值,若为-1则返回当前总数目 - * @return 获取指定人群数量 - */ - public int getPeopleSize(int state) { - if (state == -1) { - return personList.size(); - } - int i = 0; - for (Person person : personList) { - if (person.getState() == state) { - i++; - } - } - return i; - } - - - private PersonPool() { - City city = new City(400, 400);//设置城市中心为坐标(400,400) - //添加城市居民 - for (int i = 0; i < Constants.CITY_PERSON_SIZE; i++) { - Random random = new Random(); - //产生N(a,b)的数:Math.sqrt(b)*random.nextGaussian()+a - int x = (int) (100 * random.nextGaussian() + city.getCenterX()); - int y = (int) (100 * random.nextGaussian() + city.getCenterY()); - if (x > 700) { - x = 700; - } - personList.add(new Person(city, x, y)); - } - } -} diff --git a/src/Point.java b/src/Point.java deleted file mode 100644 index 747b6ad..0000000 --- a/src/Point.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 位置坐标基类 - * - * @ClassName: Point - * @Description: 位置坐标基类 - * @author: Bruce Young - * @date: 2020年02月02日 20:59 - */ -public class Point { - private int x; - private int y; - - public Point(int x, int y) { - this.x = x; - this.y = y; - } - - /** - * 位移 - * - * @param x - * @param y - */ - public void moveTo(int x, int y) { - this.x += x; - this.y += y; - } - - public int getX() { - return x; - } - - public void setX(int x) { - this.x = x; - } - - public int getY() { - return y; - } - - public void setY(int y) { - this.y = y; - } -} diff --git a/src/Virus.java b/src/Virus.java deleted file mode 100644 index acda218..0000000 --- a/src/Virus.java +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 病毒对象 - * - * @ClassName: Virus - * @Description: 病毒对象 - * @author: Bruce Young - * @date: 2020年02月02日 17:04 - */ -public class Virus { -} From b977027f2fd49240ee258d2a1281cffb20945300 Mon Sep 17 00:00:00 2001 From: PlanetMoon Date: Sun, 9 Feb 2020 10:13:54 +0800 Subject: [PATCH 3/4] update with Cure --- README.md | 17 +++++++++++------ VirusBroadcast | 1 - src/Constants.java | 4 +++- src/MyPanel.java | 10 ++++++++-- src/Person.java | 22 +++++++++++++++++++--- 5 files changed, 41 insertions(+), 13 deletions(-) delete mode 160000 VirusBroadcast diff --git a/README.md b/README.md index adcff1e..ea40726 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# VirusBroadcast -一个基于java的模拟仿真程序,由于启动的时候时间仓促,数据不足,所以模型和推演过程过于简单,如果有好的想法或者能提供相关数据支持的朋友请提issues。 -如果您也是一名java程序员,可以直接修改并给我提交pr,我之前已经启动每日疫情数据的每日抓取工作,希望在疫情结束后有机会通过这些精准的的数据做一个复盘。 - -2020.2.6: -病毒变异过程是一个不断适应的过程,可以尝试简单的DNN对病毒进行建模,已经开始着手实施。 +# Virus Broadcast +本项目fork自一位优秀的Java工程师,同时也是一位B站UP主。 +本分支修改如下: +* 修正原项目中入院隔离人员不再死亡的bug +* 增加治愈的功能 +># VirusBroadcast +>一个基于java的模拟仿真程序,由于启动的时候时间仓促,数据不足,所以模型和推演过程过于简单,如果有好的想法或者能提供相关数据支持的朋友请提issues。 +>如果您也是一名java程序员,可以直接修改并给我提交pr,我之前已经启动每日疫情数据的每日抓取工作,希望在疫情结束后有机会通过这些精准的的数据做一个复盘。 +> +>2020.2.6: +>病毒变异过程是一个不断适应的过程,可以尝试简单的DNN对病毒进行建模,已经开始着手实施。 diff --git a/VirusBroadcast b/VirusBroadcast deleted file mode 160000 index b6badc7..0000000 --- a/VirusBroadcast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6badc75be0ef996a68810b722356bc3423ea8b9 diff --git a/src/Constants.java b/src/Constants.java index 50b8156..1a722cb 100644 --- a/src/Constants.java +++ b/src/Constants.java @@ -21,9 +21,11 @@ public class Constants { */ public static float u = 0.99f; public static int CITY_PERSON_SIZE = 5000;//城市总人口数量 - public static float FATALITY_RATE = 0.50f;//fatality_rate病死率,根据2月6日数据估算(病死数/确诊数)为0.02 + public static float FATALITY_RATE = 0.05f;//fatality_rate病死率,根据2月6日数据估算(病死数/确诊数)为0.02 public static int DIE_TIME = 100;//死亡时间均值,30天,从发病(确诊)时开始计时 public static double DIE_VARIANCE = 1;//死亡时间方差 + public static int CURE_TIME = 150;//治愈时间均值 + public static double CURE_VARIANCE = 20;//治愈时间方差 /** * 城市大小即窗口边界,限制不允许出城 */ diff --git a/src/MyPanel.java b/src/MyPanel.java index b12c0fa..450983d 100644 --- a/src/MyPanel.java +++ b/src/MyPanel.java @@ -65,6 +65,10 @@ public void paint(Graphics g) { g.setColor(new Color(0x000000)); break; } + case Person.State.CURED: { + //治愈患者 + g.setColor(new Color(0x00ff00)); + } } person.update();//对各种状态的市民进行不同的处理 g.fillOval(person.getX(), person.getY(), 3, 3); @@ -95,10 +99,12 @@ public void paint(Graphics g) { - PersonPool.getInstance().getPeopleSize(Person.State.FREEZE); g.drawString("急需病床:" + (needBeds > 0 ? needBeds : 0), captionStartOffsetX, captionStartOffsetY + 6 * captionSize); - g.setColor(new Color(0xccbbcc)); + g.setColor(new Color(0x000000)); g.drawString("病死人数:" + PersonPool.getInstance().getPeopleSize(Person.State.DEATH), captionStartOffsetX, captionStartOffsetY + 7 * captionSize); + g.setColor(new Color(0x00ff00)); + g.drawString("治愈人数:" + PersonPool.getInstance().getPeopleSize(Person.State.CURED), captionStartOffsetX, captionStartOffsetY + 8 * captionSize); g.setColor(new Color(0xffffff)); - g.drawString("世界时间(天):" + (int) (worldTime / 10.0), captionStartOffsetX, captionStartOffsetY + 8 * captionSize); + g.drawString("世界时间(天):" + (int) (worldTime / 10.0), captionStartOffsetX, captionStartOffsetY + 9 * captionSize); } diff --git a/src/Person.java b/src/Person.java index 9966830..0909d43 100644 --- a/src/Person.java +++ b/src/Person.java @@ -41,7 +41,7 @@ public interface State { //已治愈出院的人转为NORMAL即可,否则会与作者通过数值大小判断状态的代码冲突 int DEATH = FREEZE + 1;//病死者 - //int CURED = DEATH + 1;//治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定 + int CURED = DEATH + 1;//治愈数量用于计算治愈出院后归还床位数量,该状态是否存续待定 } public Person(City city, int x, int y) { @@ -86,6 +86,7 @@ public void setState(int state) { int infectedTime = 0;//感染时刻 int confirmedTime = 0;//确诊时刻 int dieMoment = 0;//死亡时刻,为0代表未确定,-1代表不会病死 + int cureMoment = 0;//治愈时刻 public boolean isInfected() { @@ -195,7 +196,7 @@ private void action() { public void update() { //@TODO找时间改为状态机 - if (state == State.FREEZE || state == State.DEATH) { + if (state == State.DEATH) { return;//如果已经隔离或者死亡了,就不需要处理了 } @@ -213,8 +214,20 @@ public void update() { } } - //TODO 暂时缺失治愈出院市民的处理。需要确定一个变量用于治愈时长。由于案例太少,暂不加入。 + //治愈 + if (state == State.FREEZE && cureMoment == 0) { + int cureTime = (int) MathUtil.stdGaussian(Constants.CURE_VARIANCE, Constants.CURE_TIME); + if (cureTime > dieMoment - MyPanel.worldTime && dieMoment > 0) { + cureMoment = -1; //来不及治好,死亡时间在治愈时间之前 + } else { + cureMoment = MyPanel.worldTime + cureTime; + } + } + if (state == State.FREEZE && cureMoment > 0 && MyPanel.worldTime >= cureMoment) { + state = State.CURED; + Hospital.getInstance().returnBed(useBed); + } if (state == State.CONFIRMED && MyPanel.worldTime - confirmedTime >= Constants.HOSPITAL_RECEIVE_TIME) { @@ -237,6 +250,9 @@ public void update() { //处理病死者 if ((state == State.CONFIRMED || state == State.FREEZE) && MyPanel.worldTime >= dieMoment && dieMoment > 0) { state = State.DEATH;//患者死亡 + // 随机丢弃尸体,没有火葬场和墓地,暂时这样处理 + super.setX((int) MathUtil.stdGaussian(100, city.getCenterX())); + super.setY((int) MathUtil.stdGaussian(100, city.getCenterY())); Hospital.getInstance().returnBed(useBed);//归还床位 } From d18059792c384a4e6deae941df6aca01724b5ead Mon Sep 17 00:00:00 2001 From: PlanetMoon Date: Tue, 11 Feb 2020 20:34:19 +0800 Subject: [PATCH 4/4] Updated --- src/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Constants.java b/src/Constants.java index 1a722cb..28e3513 100644 --- a/src/Constants.java +++ b/src/Constants.java @@ -8,7 +8,7 @@ */ public class Constants { - public static int ORIGINAL_COUNT = 50;//初始感染数量 + public static int ORIGINAL_COUNT = 25;//初始感染数量 public static float BROAD_RATE = 0.8f;//传播率 public static float SHADOW_TIME = 140;//潜伏时间,14天为140 public static int HOSPITAL_RECEIVE_TIME = 10;//医院收治响应时间