import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.*; import java.lang.Math.*; /* * class Virus defines the main part of the applet * It sets up a separate frame (window) for statistics, * then it sets up the control panel at the top of the main * applet window, above the main display canvas. * It also sets up communication by passing pointers * (dispcan and statdisparea) specifying the display * canvas and the statistics canvas */ public class Virus extends Applet { ControlPanel controls; // The frame and panel for displaying statistics Frame statframe; StatArea statdisparea; public void init() { statframe = new Frame("Stats"); statdisparea = new StatArea(); statframe.setSize(500, 600); statframe.add(statdisparea); statframe.setVisible( true ); statdisparea.addString( "The leftmost column is the time and the\n" ); statdisparea.addString( "next column is the number of cells that are\n" ); statdisparea.addString( "all green. The rightmost column is the number\n" ); statdisparea.addString( "of cells that are all red. The remaining columns\n" ); statdisparea.addString( "are ten percentile counts for cells that have\n" ); statdisparea.addString( "a mix of red and green.\n\n" ); setLayout(new BorderLayout()); DisplayCanvas dispcan = new DisplayCanvas( statdisparea ); add("Center", dispcan); add("North", controls = new ControlPanel( dispcan )); } public void start() { controls.setEnabled(true); } public void stop() { controls.setEnabled(false); } public boolean handleEvent(Event e) { if (e.id == Event.WINDOW_DESTROY) { statframe.dispose(); System.exit(0); } return false; } public static void main(String args[]) { Frame f = new Frame("Virus"); Virus virus = new Virus(); virus.init(); virus.start(); f.add("Center", virus); f.setSize(900, 500); f.show(); } } /* * class StatArea defines a panel that appears as a second * frame (window) giving statistics on the simulation. */ public class StatArea extends Panel { TextArea statField; public StatArea() { super(); add(statField = new TextArea("", 40, 50 )); } // the addString method can be used as a println // to append a string to the contents of the panel public void addString( String s ) { statField.append( s ); } } /* * class DisplayCanvas defines the canvas that displays the simulation * and contains the methods that define how the cells and viruses * behave and how they're displayed * * The cells are arranged in a hexagonal grid, so each cell * has six immediate neighbors * * The constructor also initiates the statistics frame (window) */ class DisplayCanvas extends Canvas { // The number of cells along one edge of the hexagon static final int SIZE = 200; // State names for cell states static final int normal = 0; static final int infectible = 1; static final int infected1 = 2; static final int infected2 = 3; static final int infected3 = 4; static final int infected4 = 5; static final int infected5 = 6; static final int infected6 = 7; static final int deceased = 8; // State names to specify which viruses are currently displayed static final int both = 0; static final int green_only = 1; static final int red_only = 2; // Color tables for displaying both, red only, and green only static Color[] ctable = new Color[256]; static Color[] rtable = new Color[256]; static Color[] gtable = new Color[256]; // Mode flag to indicate which colors are currently displayed static int colormode; // The maximum number of each type of virus at the current // time step. Used for scaling the color when displaying // just one color static int max_red, max_green; // x and y coordinates of the corners of the focus borders static int[] focusBorderX = new int[6]; static int[] focusBorderY = new int[6]; // Multiplicity of infection for red and green static double Red_MOI; static double Green_MOI; // the number of time steps each type of virus can live outside a cell static int max_red_age; static int max_green_age; // String specifying the shapes of the foci static String redfocustype; static String greenfocustype; // The radii of the focal areas static int red_focal_radius = 20; static int green_focal_radius = 20; // The number of cells in each focus static int red_focal_size; static int green_focal_size; // Flag that indicates whether focus border is visible static boolean show_border; // The simulator clock static int timestep; // The time at which the red focus gets its initial infection static int RedTime; // The offset of the red focus from the center static int RedOffset; // Random number generator Random randval = new Random(); // The state of each cell (normal, infectible, infected, etc.) int[][] cell_state = new int[2*SIZE-1][]; // An event timer for each cell int[][] cell_timer = new int[2*SIZE-1][]; // The numbers of red and green viruses of different // ages in or waiting to enter each cell int[][][] cell_red = new int[5][2*SIZE-1][]; int[][][] cell_green = new int[5][2*SIZE-1][]; // The canvas in the statistics frame StatArea statdisparea; /* * Method enter_virus is called for each infectible cell * to allow any waiting viruses to enter the cell. * It sums all viruses waiting to enter the cell. * If there are none, the cell remains infectible. * If the cell becomes infected, its timer is set to a * random value of 4, 5, or 6. * If superinfection is not allowed, the majority virus wins. * If superinfection is allowed, it is also possible (with * probability proportional to the relative numbers of the * two viruses) that only one type of virus enters the cell. */ private void enter_virus( int row, int col, boolean superInf ) { cell_red[0][row][col] = 0; cell_green[0][row][col] = 0; for (int i=1; i<5; i++) { cell_red[0][row][col] += cell_red[i][row][col]; cell_green[0][row][col] += cell_green[i][row][col]; } if ((cell_red[0][row][col]+cell_green[0][row][col]) > 0) { cell_state[row][col] = infected1; cell_timer[row][col] = 4 + Math.abs( randval.nextInt() ) % 3; if (!superInf) { if (cell_red[0][row][col] > cell_green[0][row][col]) cell_green[0][row][col] = 0; else if (cell_red[0][row][col] == cell_green[0][row][col]) { if ((Math.abs( randval.nextInt() ) % 2) == 0) cell_green[0][row][col] = 0; else cell_red[0][row][col] = 0; } else cell_red[0][row][col] = 0; } else { int denom = cell_red[0][row][col] + cell_green[0][row][col]; double redfrac = (double) cell_red[0][row][col] / denom; double minfrac = ( redfrac < 0.5 ) ? redfrac : ( 1.0 - redfrac ); if ((Math.abs( randval.nextInt() ) % 10000) > 20000*minfrac) { if ((Math.abs( randval.nextInt() ) % 10000) < 10000*redfrac) cell_green[0][row][col] = 0; else cell_red[0][row][col] = 0; } } } else { cell_state[row][col] = infectible; } } /* * Method bud is called at alternate time steps after a cell * becomes infected to simulate viruses budding out of the cell. * It distributes viruses to the six neighboring cells in proportion * to the ratio of the two types within the cell. On the average * about 100 viruses are distributed, up to a maximum of about 200. * If superinfection is allowed and the neighboring cell is already * infected, the viruses enter the cell immediately, otherwise they * wait outside the cell. */ private void bud( int row, int col, int max_red_age, int max_green_age, boolean superInf ) { int maxred, maxgreen, numred, numgreen, prevcol, nextcol; int denom = cell_red[0][row][col] + cell_green[0][row][col]; if (denom > 0) { if (row > SIZE-1) {prevcol = 0; nextcol = -1;} else if (row < SIZE-1) {prevcol = -1; nextcol = 0;} else {prevcol = -1; nextcol = -1;} double redfrac = (double) cell_red[0][row][col] / denom; maxred = (int)(redfrac * 34); maxgreen = (int)((1-redfrac) * 34); if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row-1][col+prevcol] == normal) || (cell_state[row-1][col+prevcol] == infectible)) { cell_red[max_red_age][row-1][col+prevcol] += numred; cell_green[max_green_age][row-1][col+prevcol] += numgreen; } else if ((cell_state[row-1][col+prevcol] != deceased) && (superInf)) { cell_red[0][row-1][col+prevcol] += numred; cell_green[0][row-1][col+prevcol] += numgreen; } if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row-1][col+prevcol+1] == normal) || (cell_state[row-1][col+prevcol+1] == infectible)) { cell_red[max_red_age][row-1][col+prevcol+1] += numred; cell_green[max_green_age][row-1][col+prevcol+1] += numgreen; } else if ((cell_state[row-1][col+prevcol+1] != deceased) && (superInf)) { cell_red[0][row-1][col+prevcol+1] += numred; cell_green[0][row-1][col+prevcol+1] += numgreen; } if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row][col-1] == normal) || (cell_state[row][col-1] == infectible)) { cell_red[max_red_age][row][col-1] += numred; cell_green[max_green_age][row][col-1] += numgreen; } else if ((cell_state[row][col-1] != deceased) && (superInf)) { cell_red[0][row][col-1] += numred; cell_green[0][row][col-1] += numgreen; } if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row][col+1] == normal) || (cell_state[row][col+1] == infectible)) { cell_red[max_red_age][row][col+1] += numred; cell_green[max_green_age][row][col+1] += numgreen; } else if ((cell_state[row][col+1] != deceased) && (superInf)) { cell_red[0][row][col+1] += numred; cell_green[0][row][col+1] += numgreen; } if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row+1][col+nextcol] == normal) || (cell_state[row+1][col+nextcol] == infectible)) { cell_red[max_red_age][row+1][col+nextcol] += numred; cell_green[max_green_age][row+1][col+nextcol] += numgreen; } else if ((cell_state[row+1][col+nextcol] != deceased) && (superInf)) { cell_red[0][row+1][col+nextcol] += numred; cell_green[0][row+1][col+nextcol] += numgreen; } if (maxred == 0) numred = 0; else numred = Math.abs( randval.nextInt() ) % maxred; if (maxgreen == 0) numgreen = 0; else numgreen = Math.abs( randval.nextInt() ) % maxgreen; if ((cell_state[row+1][col+nextcol+1] == normal) || (cell_state[row+1][col+nextcol+1] == infectible)) { cell_red[max_red_age][row+1][col+nextcol+1] += numred; cell_green[max_green_age][row+1][col+nextcol+1] += numgreen; } else if ((cell_state[row+1][col+nextcol+1] != deceased) && (superInf)) { cell_red[0][row+1][col+nextcol+1] += numred; cell_green[0][row+1][col+nextcol+1] += numgreen; } } } /* * Method initFocus */ private void initFocus( int offset, int red_count, int green_count, String focus_type, int focal_radius ) { int row, col; // Four shapes are currently implemented for the focus: // Hexagon - a regular hexagon // Hex2 - a smaller hexagonal area formed by moving one // vertex of a regular hexagon to the center // Hex3 - move the opposite vertex to the center as well // (this gives two triangles joined at a vertex) // Disc - an approximation of a disc on the hexagonal grid // // For each shape of focus, the following sections calculate // the number of cells in the focus, then spread the appropriate // numbers of each type of virus at random among the cells // of the focus if (focus_type == "Hexagon") { while ((red_count+green_count) > 0) { row = Math.abs( randval.nextInt() ) % (2*focal_radius+1) + (SIZE-(focal_radius+1)); col = Math.abs( randval.nextInt() ) % (2*focal_radius+1) + (SIZE-(focal_radius+1)); if (col < (cell_state[row].length+focal_radius+1-SIZE)) { if (red_count > 0) { cell_red[max_red_age][row][col+offset]++; red_count--; } else { cell_green[max_green_age][row][col+offset]++; green_count--; } } } } else if (focus_type == "Hex2") { while ((red_count+green_count) > 0) { row = Math.abs( randval.nextInt() ) % (2*focal_radius+1) + (SIZE-(focal_radius+1)); col = Math.abs( randval.nextInt() ) % (focal_radius+1) + (SIZE-(focal_radius+1)); if (red_count > 0) { cell_red[max_red_age][row][col+offset]++; red_count--; } else { cell_green[max_green_age][row][col+offset]++; green_count--; } } } else if (focus_type == "Hex3") { while ((red_count+green_count) > 0) { row = Math.abs( randval.nextInt() ) % (2*focal_radius+1) + (SIZE-(focal_radius+1)); if (row < SIZE) { col = Math.abs( randval.nextInt() ) % (SIZE - row) + row; } else { col = Math.abs( randval.nextInt() ) % (row + 2 - SIZE) + 2*SIZE - (row + 2); } if (red_count > 0) { cell_red[max_red_age][row][col+offset]++; red_count--; } else { cell_green[max_green_age][row][col+offset]++; green_count--; } } } else if (focus_type == "Disc") { int height = (int) Math.floor(2*focal_radius/Math.sqrt(3.0)); while ((red_count+green_count) > 0) { row = Math.abs( randval.nextInt() ) % (2*height+1) + (SIZE - (1 + height)); col = Math.abs( randval.nextInt() ) % (3*focal_radius+1) + (SIZE-(2*focal_radius+1)); if (row 0) { cell_red[max_red_age][row][col+offset]++; red_count--; } else { cell_green[max_green_age][row][col+offset]++; green_count--; } } } else { if (3*(row+1-SIZE)*(row+1-SIZE) + (2*col+row+3-3*SIZE)*(2*col+row+3-3*SIZE) <= 4*focal_radius*focal_radius) { if (red_count > 0) { cell_red[max_red_age][row][col+offset]++; red_count--; } else { cell_green[max_green_age][row][col+offset]++; green_count--; } } } } } } /* * Constructor for class DisplayCanvas * The sda parameter provides for communication between this class * and the instance of Statarea used to display statistics in a * separate frame (window) */ public DisplayCanvas( StatArea sda ) { statdisparea = sda; colormode = both; show_border = false; red_focal_size = 0; green_focal_size = 0; // set max_red and max_green to 1 in case paint() is called // before these are calculated max_red = 1; max_green = 1; // Allocate space for arrays and initialize for (int row=0; row max_red) max_red = cell_red[0][row][col]; if (cell_green[0][row][col] > max_green) max_green = cell_green[0][row][col]; } } repaint(); } /* * Method to change the display mode to display both types * of virus, just green, or just red */ public void setColorMode( String mode ) { if (mode == "Both") colormode = both; else if (mode == "GreenOnly") colormode = green_only; else if (mode == "RedOnly") colormode = red_only; } /* * Method to change the display mode to show or hide * a thin black line around the edge of the focal area */ public void toggleBorder() { if (show_border) show_border = false; else show_border = true; repaint(); } /* * Method paint is called by repaint() and whenever the * canvas needs to be redrawn * * Uninfected cells are drawn as white, otherwise the * appropriate color table is used to set the color. * Each cell is represented by two pixels. */ public void paint(Graphics g) { for (int row=0; row<2*SIZE-1; row++) for (int col=0; col 0) { timestep++; iterations--; for (int row=1; row<2*SIZE-2; row++) { for (int col=1; col 0) { binnum = 255*cell_red[0][row][col]/denom; if (binnum == 0) bin[0]++; else if (binnum == 255) bin[11]++; else if (binnum < 26) bin[1]++; else if (binnum > 230) bin[10]++; else if (binnum < 52) bin[2]++; else if (binnum > 204) bin[9]++; else if (binnum < 77) bin[3]++; else if (binnum > 179) bin[8]++; else if (binnum < 103) bin[4]++; else if (binnum > 153) bin[7]++; else if (binnum < 128) bin[5]++; else bin[6]++; } } statdisparea.addString( timestep+"\t"+bin[0]+"\t"+bin[1]+"\t"+ bin[2]+"\t"+bin[3]+"\t"+bin[4]+"\t"+bin[5]+"\t"+bin[6]+"\t"+ bin[7]+"\t"+bin[8]+"\t"+bin[9]+"\t"+bin[10]+"\t"+bin[11]+"\n" ); } // After main loop, calculate new values for max_red and max_green max_red = 0; max_green = 0; for (int row=1; row<2*SIZE-2; row++) for (int col=1; col 0) { if (cell_red[0][row][col] > max_red) max_red = cell_red[0][row][col]; if (cell_green[0][row][col] > max_green) max_green = cell_green[0][row][col]; } repaint(); } } /* * class ControlPanel defines the user interface that controls * the simulation * * The ControlPanel constructor sets up the buttons, fields, * labels and menu choices in a 4 by 8 grid layout * * The actionPerformed method listens for user interface * events and passes values to the appropriate methods */ class ControlPanel extends Panel implements ActionListener { /* * User interface widgets: * * Menus: * RedAgeChoice and GreenAgeChoice give the maximum number * of time steps that a virus can stay inside a cell before * budding out * * FocusChoice specifies the shape of the focus * * Input Fields: * RedMOIField and GreenMOIField give the aferage number of * each type of virus per call in the focus for the * initial infection * * FocusField gives the radius of the focus * * RateField gives the infection rate * * IterField specifies how many time steps to simulate * with each click of the "Run" button * * MinField and MaxField give the range for the uniform * random variable that specifies how long a cell remains * susceptible to infection * * Buttons: * "Border on/off" toggles a thin black line that shows the * border of the focal region * * "Restart" reinitializes the simulation by clearing * everything and reinfecting the focal region * * "Run" continues the current simulation for the number * of time steps specified by IterField * * "Red only", "Green only", and "Red+Green" specify whether to * display only one type of virus or both. This only affects * the display - both types are still present in the simulation * * SuperYes and SuperNo are radio buttons that specify whether * to allow superinfection (simultaneous infection of a single * cell by both red and green) * */ // Flag to indicate whether canvas.init() has been called static boolean initflag = false; Choice RedAgeChoice, GreenAgeChoice; Choice RedShape, GreenShape; TextField RedMOIField, GreenMOIField; TextField RedRadius, GreenRadius; TextField RateField; TextField IterField; TextField MinField; TextField MaxField; TextField DeltaXField; TextField DeltaTField; CheckboxGroup SuperButtons; Checkbox SuperYes, SuperNo; DisplayCanvas canvas; public ControlPanel(DisplayCanvas canvas) { Button b = null; setLayout( new GridLayout( 5, 8, 5, 5 ) ); this.canvas = canvas; add(new Label(" ")); add(new Label("MOI")); add(new Label("Max Age")); add(new Label("Focus shape")); add(new Label("Focus radius")); SuperButtons = new CheckboxGroup(); add(new Label("Superinfection?")); SuperYes = new Checkbox( "Yes", SuperButtons, true ); add(SuperYes); SuperNo = new Checkbox( "No", SuperButtons, false ); add(SuperNo); add(new Label("Red virus")); add(RedMOIField = new TextField("2.0", 5)); RedAgeChoice = new Choice(); RedAgeChoice.add( "1" ); RedAgeChoice.add( "2" ); RedAgeChoice.add( "3" ); RedAgeChoice.add( "4" ); add( RedAgeChoice ); RedShape = new Choice(); RedShape.add( "Disc" ); RedShape.add( "Hexagon" ); RedShape.add( "Hex2" ); RedShape.add( "Hex3" ); add( RedShape ); add(RedRadius = new TextField("20", 4)); b = new Button("Green only"); b.addActionListener(this); add(b); b = new Button("Red+Green"); b.addActionListener(this); add(b); b = new Button("Red only"); b.addActionListener(this); add(b); add(new Label("Green virus")); add(GreenMOIField = new TextField("2.0", 5)); GreenAgeChoice = new Choice(); GreenAgeChoice.add( "1" ); GreenAgeChoice.add( "2" ); GreenAgeChoice.add( "3" ); GreenAgeChoice.add( "4" ); add( GreenAgeChoice ); GreenShape = new Choice(); GreenShape.add( "Disc" ); GreenShape.add( "Hexagon" ); GreenShape.add( "Hex2" ); GreenShape.add( "Hex3" ); add( GreenShape ); add(GreenRadius = new TextField("20", 4)); b = new Button("Border on/off"); b.addActionListener(this); add(b); b = new Button("Restart"); b.addActionListener(this); add(b); b = new Button("Run"); b.addActionListener(this); add(b); add(new Label("Delta X")); add(new Label("Delta T")); add(new Label("% infectible")); add(new Label("Min RRT")); add(new Label("Max RRT")); add(new Label(" ")); add(new Label("Iterations")); add(new Label(" ")); add(DeltaXField = new TextField("0", 4)); add(DeltaTField = new TextField("0", 4)); add(RateField = new TextField("5.0", 5)); add(MinField = new TextField("8", 4)); add(MaxField = new TextField("12", 4)); add(new Label(" ")); add(IterField = new TextField("15", 4)); add(new Label(" ")); canvas.repaint(); } // Handle clicks on "Run", "Red only", "Green only", "Red+Green", // "Border on/off", and "Restart" buttons public void actionPerformed(ActionEvent ev) { double inf_rate; double red_moi = new Double(RedMOIField.getText().trim()).doubleValue(); double green_moi = new Double(GreenMOIField.getText().trim()).doubleValue(); double inf_pct = new Double(RateField.getText().trim()).doubleValue(); inf_rate = inf_pct/100.0; String label = ev.getActionCommand(); if (label.equals("Run")) { if (!initflag) canvas.init(Integer.parseInt(RedRadius.getText().trim()), Integer.parseInt(GreenRadius.getText().trim()), red_moi, green_moi, Integer.parseInt(RedAgeChoice.getSelectedItem().trim()), Integer.parseInt(GreenAgeChoice.getSelectedItem().trim()), RedShape.getSelectedItem().trim(), GreenShape.getSelectedItem().trim(), inf_rate, SuperYes.getState(), Integer.parseInt(DeltaXField.getText().trim()), Integer.parseInt(DeltaTField.getText().trim())); initflag = true; canvas.iterate(Integer.parseInt(IterField.getText().trim()), inf_rate, Integer.parseInt(RedAgeChoice.getSelectedItem().trim()), Integer.parseInt(GreenAgeChoice.getSelectedItem().trim()), Integer.parseInt(MinField.getText().trim()), Integer.parseInt(MaxField.getText().trim()), SuperYes.getState()); } else if (label.equals("Red only")) canvas.setColorMode("RedOnly"); else if (label.equals("Green only")) canvas.setColorMode("GreenOnly"); else if (label.equals("Red+Green")) canvas.setColorMode("Both"); else if (label.equals("Border on/off")) canvas.toggleBorder(); else { canvas.init(Integer.parseInt(RedRadius.getText().trim()), Integer.parseInt(GreenRadius.getText().trim()), red_moi, green_moi, Integer.parseInt(RedAgeChoice.getSelectedItem().trim()), Integer.parseInt(GreenAgeChoice.getSelectedItem().trim()), RedShape.getSelectedItem().trim(), GreenShape.getSelectedItem().trim(), inf_rate, SuperYes.getState(), Integer.parseInt(DeltaXField.getText().trim()), Integer.parseInt(DeltaTField.getText().trim())); initflag = true; } canvas.repaint(); } }