diff --git a/README.md b/README.md index 90df23b5c..839742de8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# MATLAB Fall 2014 – Research Plan (Template) +# MATLAB Fall 2017 – Research Plan (Template) (text between brackets to be removed) > * Group Name: (be creative!) diff --git a/code.m b/code.m new file mode 100644 index 000000000..892a70c3b --- /dev/null +++ b/code.m @@ -0,0 +1,100 @@ +%{ +GENERAL NOTES +============= + +Each function should be defined in a separate file according +to MATLAB requirements. Upload these files to the code folder +in the repository. +When iterating over the matrix, if possible, parallelize the +accesses using parfor to increase efficiency. To iterate over +the people inside of one sector, use the following piece of +code: + +itr = matrix(i, j).iterator(); + +while itr.hasNext() + person = itr.next(); + % Do something with that person +end + +Inside the matrix, the position of the individual is represented in +absolute terms (i.e. relative to the entire matrix, not the sector). For +precision reasons, the coordinates range from 0 to 10 * resolution (for +now, we can always make it finer-grained by changing the SECTOR_SIZE +global). + +To convert coordinates to a sector, use the function +sectorForCoords(individual). You can also pass pure coordinates to this +function. + +Define all globals in the runSimulation.m file. This way, the parameters of +the simulation are easy to find and change. + +============= +%} + +% Definition of individual + +%{ +An individual is a vector with +- positionX +- positionY +- velocityX +- velocityY +- isParticipating in moshpit +%} + +%{ +Initializes the position matrix. The ground covered by the people +attending the concert is divided into sectors. The people in +each sector are represented with a java.util.LinkedList. +The coordinates of each person are given as absolute coordinates, +i.e. not relative to the sector, but to the entire matrix. +%} +function initializeMatrix() +end + +% Gets the position of the given individual +function [position] = getPosition(individual) +end + +% Gets the velocity of the given individual +function [velocity] = getVelocity(individual) +end + +% Returns true if the given individual is participating in the circle pit +function [isParticipating] = isParticipating(individual) +end + +% Returns the distance between two individuals +function [distance] = distance(individual1, individual2) +end + +%{ +Returns the neighbors of the given individual as a matrix where each column +is an individual. +%} +function [neighbors] = getNeighbors(individual) +end + +% TODO +%{ +Main loop that iterates through the individuals and updates all their +positions, returning the modified main matrix. +%} +function runOneTimestep(matrix) +end + +% TODO +%{ +Runs the simulation. +%} +function runSimulation() + initializeMatrix(); + for i = 1:MAX + runOneTimestep(matrix); + end +end + + + diff --git a/code/README.md b/code/README.md index cf73f2a80..d85664519 100644 --- a/code/README.md +++ b/code/README.md @@ -1,3 +1,6 @@ # Code Folder -Your code goes here. You could also replace the content of this file with something more meaningful +Contains the code for the MATLAB implementation of the project. +The structure of the code is laid out in the `code.m` file in the root directory of the repository. + +__WARNING__: The MATLAB version was retired on November 17, 2017 and is obsolete. We have moved to Java for speed reasons. See the `java_code` folder for the most recent version. diff --git a/code/createIndividual.m b/code/createIndividual.m new file mode 100644 index 000000000..220b7eaac --- /dev/null +++ b/code/createIndividual.m @@ -0,0 +1,8 @@ +function [individual] = createIndividual(coordinates, velocity, isParticipating) +%CREATEINDIVIDUAL Creates a new individual +% Creates a new vector representing an individual with the specified +% properties. + individual = [coordinates(1); coordinates(2); velocity(1); velocity(2); isParticipating]; + +end + diff --git a/code/distance.m b/code/distance.m new file mode 100644 index 000000000..b951e535c --- /dev/null +++ b/code/distance.m @@ -0,0 +1,12 @@ +function distance = distance(individual1, individual2) +%DISTANCE Gets the distance between two individuals. + x1 = individual1(1); + y1 = individual1(2); + x2 = individual2(1); + y2 = individual2(2); + + dx = x1 - x2; + dy = y1 - y2; + + distance = sqrt(dx*dx + dy*dy); +end diff --git a/code/getNeighbors.m b/code/getNeighbors.m new file mode 100644 index 000000000..7969f2b70 --- /dev/null +++ b/code/getNeighbors.m @@ -0,0 +1,69 @@ +function [individuals] = getNeighbors(individual, radius) +%GETNEIGHBORS Gets the neighbors of the given individual +% Gets the neighbors of an individual in the radius specified by the +% NEIGHBOR_SEARCH_RADIUS global. Returns these individuals as columns in +% a matrix. + + individuals = []; + + global matrix; + + % Get location of the individual + coords = getPosition(individual); + sector = sectorForCoords(coords); + + % Get the sectors where we need to search + sectorsToSearch = sector; + northSector = sectorForCoords(coords - [0; radius]); + if ~isequal(northSector, sector) + sectorsToSearch = [sectorsToSearch, northSector]; + end + + southSector = sectorForCoords(coords + [0; radius]); + if ~isequal(southSector, sector) + sectorsToSearch = [sectorsToSearch, southSector]; + end + + westSector = sectorForCoords(coords - [radius; 0]); + if ~isequal(westSector, sector) + sectorsToSearch = [sectorsToSearch, westSector]; + end + + eastSector = sectorForCoords(coords + [radius; 0]); + if ~isequal(eastSector, sector) + sectorsToSearch = [sectorsToSearch, eastSector]; + end + + northEastSector = sectorForCoords(coords + [radius; -radius]); + if ~isequal(northEastSector, sector) + sectorsToSearch = [sectorsToSearch, northEastSector]; + end + + northWestSector = sectorForCoords(coords + [-radius; -radius]); + if ~isequal(northWestSector, sector) + sectorsToSearch = [sectorsToSearch, northWestSector]; + end + + southEastSector = sectorForCoords(coords + [radius; radius]); + if ~isequal(southEastSector, sector) + sectorsToSearch = [sectorsToSearch, southEastSector]; + end + + southWestSector = sectorForCoords(coords + [-radius; radius]); + if ~isequal(southWestSector, sector) + sectorsToSearch = [sectorsToSearch, southWestSector]; + end + + % Search over those vectors + for k = 1:size(sectorsToSearch, 2) + i = sectorsToSearch(1, k); + j = sectorsToSearch(2, k); + itr = matrix(i, j).iterator(); + while itr.hasNext() + person = itr.next(); + if distance(individual, person) < radius + individuals = [individuals, person]; + end + end + end +end diff --git a/code/getPosition.m b/code/getPosition.m new file mode 100644 index 000000000..a4a4760eb --- /dev/null +++ b/code/getPosition.m @@ -0,0 +1,5 @@ +function [position] = getPosition(individual) +%GETPOSITION Gets the position of the given individual. + position = [individual(1); individual(2)]; +end + diff --git a/code/getVelocity.m b/code/getVelocity.m new file mode 100644 index 000000000..25f9a9cf9 --- /dev/null +++ b/code/getVelocity.m @@ -0,0 +1,4 @@ +function [velocity] = getVelocity(individual) +%GETVELOCITY Gets the velocity of the given individual. + velocity = [individual(3); individual(4)]; +end diff --git a/code/getXY.m b/code/getXY.m new file mode 100644 index 000000000..f4c99021d --- /dev/null +++ b/code/getXY.m @@ -0,0 +1,29 @@ +function [X, Y] = getXY() +%GETXY Gets the x and y coordinates of all the individuals for plotting. +% Stores the x coordinates of all the individuals in X and the +% corresponding y coordinates in Y. +% TODO: Find a way to separate the people based on isParticipating + + global matrix; + global NUMBER_OF_PEOPLE; + + X = zeros(NUMBER_OF_PEOPLE, 1); + Y = zeros(NUMBER_OF_PEOPLE, 1); + + k = 1; + + for i = 1:matrix.length + for j = 1:matrix(i).length + itr = matrix(i, j).iterator(); + while itr.hasNext() + individual = itr.next(); + position = getPosition(individual); + X(k) = position(1); + Y(k) = position(2); + k = k + 1; + end + end + end + +end + diff --git a/code/initializeMatrix.m b/code/initializeMatrix.m new file mode 100644 index 000000000..fd66122e3 --- /dev/null +++ b/code/initializeMatrix.m @@ -0,0 +1,46 @@ + +function [matrix] = initializeMatrix(resolution, numberOfPeople) +%INITIALIZEMATRIX Initializes the position matrix used by the simulation. +% The matrix is a low-resolution representation of the people at the +% concert and is divided into "sectors". Each sector can contain several +% people (whose position is defined by their x and y coordinates). The +% matrix initially contains numberOfPeople people. Each sector is a +% reference to a java.util.ArrayList which contains a list of the people +% currently in that sector. + + % Initialize a matrix of the proper size + width = resolution(1); + height = resolution(2); + + global SECTOR_SIZE; + + % We have to use a Java array because MATLAB matrices cannot store Java + % objects + matrix = javaArray('java.util.LinkedList', width, height); + + for i = 1:width + for j = 1:height + matrix(i, j) = javaObject('java.util.LinkedList'); + end + end + + for i = 1:numberOfPeople + % Generate random coordinates + coords(1) = rand() * width * SECTOR_SIZE; + coords(2) = rand() * height * SECTOR_SIZE; + + % Generate random velocity + % TODO: Generate more sensible velocity + velocity = [rand() - 0.5; rand() - 0.5]; + + % TODO: Decide whether individual is participating + isParticipating = false; + + individual = createIndividual(coords, velocity, isParticipating); + + % Add individual to appropriate sector + sector = sectorForCoords(coords); + matrix(sector(1), sector(2)).add(individual); + end + +return; diff --git a/code/isParticipating.m b/code/isParticipating.m new file mode 100644 index 000000000..e6263496d --- /dev/null +++ b/code/isParticipating.m @@ -0,0 +1,6 @@ +function [isParticipating] = isParticipating(individual) +%ISPARTICIPATING Returns true if the given individual is participating in +%the cirle pit. + isParticipating = individual(5); +end + diff --git a/code/plotMatrix.m b/code/plotMatrix.m new file mode 100644 index 000000000..cf9c8b6a8 --- /dev/null +++ b/code/plotMatrix.m @@ -0,0 +1,39 @@ +global matrix; +global INDIVIDUAL_RADIUS; +global SECTOR_SIZE; +global MATRIX_SIZE; + +% try + close all + hfig = figure('NumberTitle','off','name','Moshpit simulation','MenuBar','none'); + axis([0, SECTOR_SIZE * MATRIX_SIZE, 0, SECTOR_SIZE * MATRIX_SIZE]); + axis off %do not show the coordinator + +% while 1 + for i = 1:matrix.length + for j = 1:matrix(i).length + disp('Position: '); + disp([i, j]); + list = matrix(i, j); + itr = list.iterator(); + while itr.hasNext() + individual = itr.next(); + position = getPosition(individual); + x0 = position(1); + y0 = position(2); + if(isParticipating(individual)) + %erase mode, if the individual is participating, then + %we draw a red dot, otherwise it's black + head = line(x0, y0, 'color', 'r', 'Marker', '.', 'erasemode', 'xor', 'markersize', INDIVIDUAL_RADIUS * 10); + else + head = line(x0, y0, 'color', 'k', 'Marker', '.', 'erasemode', 'xor', 'markersize', INDIVIDUAL_RADIUS * 10); + end + end + end + end + drawnow; %refresh the screen +% end + +% catch +% return +% end diff --git a/code/runOneTimestep.m b/code/runOneTimestep.m new file mode 100644 index 000000000..2a59ae33c --- /dev/null +++ b/code/runOneTimestep.m @@ -0,0 +1,98 @@ +function runOneTimestep() +%RUNONETIMESTEP Runs one timestep of the simulation. +% Updates all the individuals in the position matrix according to the +% model from the paper. + + global matrix; + global INDIVIDUAL_RADIUS; + global FLOCK_RADIUS; + global EPSILON; + global ALPHA; + global MU; + + % TODO: Implement equations from model + + % Basic skeleton for updating the individuals + for i = 1:matrix.length + for j = 1:matrix(i).length + itr = matrix(i, j).iterator(); + while itr.hasNext() + individual = itr.next(); + + % Since MATLAB has no support for pass-by-reference, our + % only choice is to remove the individual from the + % LinkedList and then add it back again. Luckily this is + % O(1) using the iterator. + itr.remove(); + + % ==================== INITIALIZATION ==================== + F = zeros(2, 1); % sum over forces + sumOverVelocities = zeros(2, 1); % For calculating the flocking effect + neighbours = getNeighbors(individual, FLOCK_RADIUS); + position = getPosition(individual); + velocity = getVelocity(individual); + r0 = INDIVIDUAL_RADIUS; + dt = 0.1; + + % ****** definition of the forces ****** + % ================ CALCULATING THE FORCES ================ + for k = 1:size(neighbours, 2) + neighbour = neighbours(:, k); + positionNeighbour = getPosition(neighbour); + velocityNeighbour = getVelocity(neighbour); + + distance1 = distance(individual, neighbour); + + % Repulsive Force + % We only use neighbors within a radius of 2 * r0 + if distance1 < 2 * r0 + F = F + EPSILON * (1 - distance1 / (2*r0))^(5/2) * (position - positionNeighbour) / distance1; + end + + % Velocity summation + sumOverVelocities = sumOverVelocities + velocityNeighbour; + end + + % Propulsion + % + % TODO: v0 is the "preferred speed", vi is the + % "instantaneous speed" +% F = F + MU * dot(velocity - velocityNeighbour, velocityNeighbour / length(velocityNeighbour)); + + % Flocking + if ~isequal(sumOverVelocities, [0; 0]) + F = F + ALPHA * sumOverVelocities / norm(sumOverVelocities); + end + % Add noise + F = F + 0; + + % ****** calculate timestep ****** + % using the leap-frog method to integrate the differential + % equation + % differential equation d^2y/dt^2=rhs(y) + + % shifted initial velocity for leap-frog + v_temp = velocity + 0.5*dt*F; + % timestep +% + individual(1:2) = position + dt*v_temp; + individual(3:4) = v_temp + dt*F/2; + + % TEST +% individual(1:2) = individual(1:2) + individual(3:4); + + % We need to add the individual to the appropriate sector + % Check if the sector has changed + newSector = sectorForCoords(individual); +% disp(newSector); + % If it has, add the individual to the new sector + if ~isequal([i; j], newSector) + matrix(newSector(1), newSector(2)).add(individual); + % Else, just add it back to the same sector + else + itr.add(individual); + end + end + end + end +end diff --git a/code/runSimulation.m b/code/runSimulation.m new file mode 100644 index 000000000..7ab7ed315 --- /dev/null +++ b/code/runSimulation.m @@ -0,0 +1,86 @@ +function runSimulation() +%RUNSIMULATION Runs the simulation. +% Execute this function to start running the simulation + + % Size of a vector that represents one individual + global INDIVIDUAL_SIZE; + INDIVIDUAL_SIZE = 5; + + % Size of the matrix + global MATRIX_SIZE; + MATRIX_SIZE = 2; + + % Coordinate space of one sector of the position matrix + global SECTOR_SIZE; + SECTOR_SIZE = 10; + + % The size of one individual + global INDIVIDUAL_RADIUS; + INDIVIDUAL_RADIUS = 2; + + % The number of people at the concert + global NUMBER_OF_PEOPLE; + NUMBER_OF_PEOPLE = 50; + + % The radius in which to search for neighbors + global FLOCK_RADIUS; + FLOCK_RADIUS = 10; + + % ===================== PARAMETERS FROM THE PAPER ===================== + % TODO: Set these variables + global EPSILON; + EPSILON = 5; + + global MU; + MU = 1; + + global ALPHA; + ALPHA = 1; + + % Initialize the initial conditions of the simulation + % We make the matrix a global variable to simulate pass-by-reference + global matrix; + matrix = initializeMatrix([MATRIX_SIZE, MATRIX_SIZE], NUMBER_OF_PEOPLE); + + % =============================== TEST =============================== + + % This is a temporary way to plot the data + + [X, Y] = getXY(); + + p = scatter(X, Y); + axis([0, SECTOR_SIZE * MATRIX_SIZE, 0, SECTOR_SIZE * MATRIX_SIZE]); + + axis off; + + % We run the simulation endlessly + while true + runOneTimestep(); + [X, Y] = getXY(); + set(p, 'XData', X, 'YData', Y); + % TODO: Find a better way of drawing the matrix + drawnow; + end + + % ==================================================================== + + % TEST (old) + +% runOneTimestep(); +% +% for i = 1:matrix.length +% for j = 1:matrix(i).length +% disp('Position: '); +% disp([i, j]); +% list = matrix(i, j); +% itr = list.iterator(); +% while itr.hasNext() +% individual = itr.next(); +% disp(individual'); +% end +% end +% end + + +end + diff --git a/code/sectorForCoords.m b/code/sectorForCoords.m new file mode 100644 index 000000000..97e407205 --- /dev/null +++ b/code/sectorForCoords.m @@ -0,0 +1,25 @@ +function [sector] = sectorForCoords(individual) +%SECTORFORCOORDS Gets which sector an individual is in based on coords. + + global SECTOR_SIZE; + global MATRIX_SIZE; + + x = individual(1) / SECTOR_SIZE; + y = individual(2) / SECTOR_SIZE; + + if x < 0 + x = 0; + elseif x >= MATRIX_SIZE + x = MATRIX_SIZE - 1; + end + + if y < 0 + y = 0; + elseif y >= MATRIX_SIZE + y = MATRIX_SIZE - 1; + end + + sector = floor([x; y]); + sector = sector + [1; 1]; +end + diff --git a/doc/report.pdf b/doc/report.pdf new file mode 100644 index 000000000..63f5d6c20 Binary files /dev/null and b/doc/report.pdf differ diff --git a/flashtalk/flash_talk.pdf b/flashtalk/flash_talk.pdf new file mode 100644 index 000000000..9ada6bba2 Binary files /dev/null and b/flashtalk/flash_talk.pdf differ diff --git a/java_code/.classpath b/java_code/.classpath new file mode 100644 index 000000000..c751861a6 --- /dev/null +++ b/java_code/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/java_code/.idea/.name b/java_code/.idea/.name new file mode 100644 index 000000000..debb33621 --- /dev/null +++ b/java_code/.idea/.name @@ -0,0 +1 @@ +MSSSM \ No newline at end of file diff --git a/java_code/.idea/kotlinc.xml b/java_code/.idea/kotlinc.xml new file mode 100644 index 000000000..1c24f9a8d --- /dev/null +++ b/java_code/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/java_code/.idea/misc.xml b/java_code/.idea/misc.xml new file mode 100644 index 000000000..5fdad115f --- /dev/null +++ b/java_code/.idea/misc.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_code/.idea/modules.xml b/java_code/.idea/modules.xml new file mode 100644 index 000000000..89e5730da --- /dev/null +++ b/java_code/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/java_code/.idea/workspace.xml b/java_code/.idea/workspace.xml new file mode 100644 index 000000000..4379f5081 --- /dev/null +++ b/java_code/.idea/workspace.xml @@ -0,0 +1,1629 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + E-5 + E-6 + E-7 + E-8 + runOne + 46.539 + random + counter + counter.py + e + E + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $USER_HOME$/.subversion + + + + + 1510786148795 + + + 1510873844677 + + + 1510957707295 + + + 1511046476633 + + + 1511194413391 + + + 1511376286791 + + + 1511377363968 + + + 1511433114434 + + + 1512207256811 + + + 1512211346969 + + + 1512214896712 + + + 1512215705641 + + + 1512225195799 + + + 1512225700228 + + + 1512226953164 + + + 1512229811506 + + + 1512234993725 + + + 1512727044322 + + + 1512730512569 + + + 1512731822121 + + + 1512748060241 + + + 1512750491931 + + + 1512752177197 + + + 1512831991421 + + + 1512861675748 + + + 1513011959520 + + + 1513012403809 + + + 1513015570254 + + + 1513017212602 + + + 1513081885309 + + + 1513440914134 + + + 1513443130995 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Python + + + + + + + + Python 2.7.10 (/usr/bin/python) interpreter library + + + + + + + + Python 3.6.3 (/usr/local/bin/python3) + + + + + + + + Python|MSSSM + + + + + + + + Python 2.7.10 (/usr/bin/python) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_code/.project b/java_code/.project new file mode 100644 index 000000000..7d7a21d22 --- /dev/null +++ b/java_code/.project @@ -0,0 +1,23 @@ + + + MSSSM + + + + + + org.python.pydev.PyDevBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + org.python.pydev.pythonNature + + diff --git a/java_code/.pydevproject b/java_code/.pydevproject new file mode 100644 index 000000000..baf7d0d60 --- /dev/null +++ b/java_code/.pydevproject @@ -0,0 +1,5 @@ + + +Default +python interpreter + diff --git a/java_code/.settings/org.eclipse.core.resources.prefs b/java_code/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..0dbcdb82e --- /dev/null +++ b/java_code/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/data_analysis.py=utf-8 diff --git a/java_code/MSSSM.eml b/java_code/MSSSM.eml new file mode 100644 index 000000000..b8f1f3be1 --- /dev/null +++ b/java_code/MSSSM.eml @@ -0,0 +1,6 @@ + + + + + + diff --git a/java_code/MSSSM.iml b/java_code/MSSSM.iml new file mode 100644 index 000000000..86f903cfa --- /dev/null +++ b/java_code/MSSSM.iml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_code/MSSSM.userlibraries b/java_code/MSSSM.userlibraries new file mode 100644 index 000000000..ac69dbabc --- /dev/null +++ b/java_code/MSSSM.userlibraries @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/java_code/README.md b/java_code/README.md new file mode 100644 index 000000000..cbc6d10a8 --- /dev/null +++ b/java_code/README.md @@ -0,0 +1,3 @@ +# Java Code Folder + +Contains an IntelliJ/Eclipse project with a Java port of the MATLAB code (as of November 17, 2017). The goal is transition away from MATLAB (for speed reasons) and move completely to Java. diff --git a/java_code/__pycache__/out.cpython-36.pyc b/java_code/__pycache__/out.cpython-36.pyc new file mode 100644 index 000000000..e737d8c8e Binary files /dev/null and b/java_code/__pycache__/out.cpython-36.pyc differ diff --git a/java_code/__pycache__/out0.cpython-36.pyc b/java_code/__pycache__/out0.cpython-36.pyc new file mode 100644 index 000000000..9e1234165 Binary files /dev/null and b/java_code/__pycache__/out0.cpython-36.pyc differ diff --git a/java_code/__pycache__/out1.cpython-36.pyc b/java_code/__pycache__/out1.cpython-36.pyc new file mode 100644 index 000000000..5963b24bb Binary files /dev/null and b/java_code/__pycache__/out1.cpython-36.pyc differ diff --git a/java_code/__pycache__/out2.cpython-36.pyc b/java_code/__pycache__/out2.cpython-36.pyc new file mode 100644 index 000000000..2e7cb22db Binary files /dev/null and b/java_code/__pycache__/out2.cpython-36.pyc differ diff --git a/java_code/__pycache__/out3.cpython-36.pyc b/java_code/__pycache__/out3.cpython-36.pyc new file mode 100644 index 000000000..18cdff112 Binary files /dev/null and b/java_code/__pycache__/out3.cpython-36.pyc differ diff --git a/java_code/__pycache__/out4.cpython-36.pyc b/java_code/__pycache__/out4.cpython-36.pyc new file mode 100644 index 000000000..f5d832cc2 Binary files /dev/null and b/java_code/__pycache__/out4.cpython-36.pyc differ diff --git a/java_code/configs.sim b/java_code/configs.sim new file mode 100644 index 000000000..4787eb920 --- /dev/null +++ b/java_code/configs.sim @@ -0,0 +1,6 @@ +List the names of the configs to test below (do not remove this comment). +# Insert your configurations here (and remove all lines starting with '#') +# Example: +# verticalLine +# circle +# cross \ No newline at end of file diff --git a/java_code/configs/C_shape.config b/java_code/configs/C_shape.config new file mode 100644 index 000000000..61c3775cc --- /dev/null +++ b/java_code/configs/C_shape.config @@ -0,0 +1,29 @@ +A policeman configuration resembling a 'C'. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| xxx | +| xx | +| xx | +| xxx | +| | +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/U_shape.config b/java_code/configs/U_shape.config new file mode 100644 index 000000000..4e126ccc1 --- /dev/null +++ b/java_code/configs/U_shape.config @@ -0,0 +1,29 @@ +A configuration of police officers resembling a 'U' at the top center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| x x | +| x x | +| xxxxxx | +| | +| | +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/arrow.config b/java_code/configs/arrow.config new file mode 100644 index 000000000..58a7169e3 --- /dev/null +++ b/java_code/configs/arrow.config @@ -0,0 +1,29 @@ +A policeman configuration in the shape of a left-pointing arrow. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| xx | +| xx | +| xx | +| xx | +| xx | +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/bigCircle.config b/java_code/configs/bigCircle.config new file mode 100644 index 000000000..233d00d15 --- /dev/null +++ b/java_code/configs/bigCircle.config @@ -0,0 +1,29 @@ +A "circle" of policemen larger than the 'circle' config file. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| | +| | +| x x x | +| | +| x x | +| x x | +| | +| x x x | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/center.config b/java_code/configs/center.config new file mode 100644 index 000000000..de1ca0bcd --- /dev/null +++ b/java_code/configs/center.config @@ -0,0 +1,29 @@ +A configuration with all the policemen grouped in the center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| | +| | +| | +| xxx | +| xxx | +| xxx | +| x | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/circle.config b/java_code/configs/circle.config new file mode 100644 index 000000000..98e5c2226 --- /dev/null +++ b/java_code/configs/circle.config @@ -0,0 +1,29 @@ +A "circle" of policemen in the center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| | +| | +| | +| xxx | +| x x | +| x x | +| xxx | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/configGuide.config b/java_code/configs/configGuide.config new file mode 100644 index 000000000..4dab469ca --- /dev/null +++ b/java_code/configs/configGuide.config @@ -0,0 +1,45 @@ +# This is a guide to show how to write a config file. In a real config file, +# all lines starting with '#' MUST be removed. +# Have a look at Appendix C to see a real config file. +# Each config file must start with a one-line comment: +This comment briefly explains the configuration. +# These next lines are fairly self-explanatory, they are the same as in the +# Simulation class. +epsilon: 2 # Strength of repulsive force +mu: 10 # Strength of propulsive force +alpha: 700 # Strength of flocking force +gamma: 600 # Strength of centripetal force +numberOfPeople: 500 # Initial number of people in the arena +flockRadius: 8 # r_flock (radius in which to search for + # neighbors for the flocking force +dt: 0.01 # Timestep +percentParticipating: 0.5 # Proportion of people initially participating + # in the circle pit +rParticipating: 6 # Radius in which to search for participating + # neighbors +minCirclePitSize: 1 # Min. number of neighbors needed to continue + # participating in the circle pit +minParticipatingNeighbors: 3 # Min. number of neighbors needed to join in + # circle pit +dataCollectionInterval: 2000 # After how many milliseconds to collect data +insertPoliceAfter: 0 # After how many timesteps (not milliseconds!) + # police should be inserted. +collectDataThisManyTimes: 3 # How many times to collect data +useThisManySeeds: 2 # How many seeds to test +# The next section is a "graphical" representation of where the policemen are +# located. Each policeman is denoted by an 'x'. The representation below must +# include the borders of the matrix and should be 10 spaces wide and 10 lines +# long. +policeConfig: + __________ +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | + ---------- \ No newline at end of file diff --git a/java_code/configs/corner.config b/java_code/configs/corner.config new file mode 100644 index 000000000..59a811a1f --- /dev/null +++ b/java_code/configs/corner.config @@ -0,0 +1,29 @@ +A line of policemen completely blocking off one corner of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| x | +| x | +| x | +| x | +| xxxxxx| +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/cross.config b/java_code/configs/cross.config new file mode 100644 index 000000000..82d80f07e --- /dev/null +++ b/java_code/configs/cross.config @@ -0,0 +1,29 @@ +Policemen resembling a cross in the center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| | +| | +| x x | +| x x | +| x | +| x | +| x x | +| x x | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/diagonal.config b/java_code/configs/diagonal.config new file mode 100644 index 000000000..3d0960006 --- /dev/null +++ b/java_code/configs/diagonal.config @@ -0,0 +1,29 @@ +A diagonal line of policemen from upper left to lower right. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +|x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x| + ---------- \ No newline at end of file diff --git a/java_code/configs/doubleHalfLine.config b/java_code/configs/doubleHalfLine.config new file mode 100644 index 000000000..9873d36e9 --- /dev/null +++ b/java_code/configs/doubleHalfLine.config @@ -0,0 +1,29 @@ +A double half-line of policemen at the top center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| xx | +| xx | +| xx | +| xx | +| xx | +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/no.config b/java_code/configs/no.config new file mode 100644 index 000000000..8ab0b0bc6 --- /dev/null +++ b/java_code/configs/no.config @@ -0,0 +1,29 @@ +A configuration with no policemen. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| | +| | +| | +| | +| | +| | +| | +| | +| | +| | + ---------- \ No newline at end of file diff --git a/java_code/configs/verticalLine.config b/java_code/configs/verticalLine.config new file mode 100644 index 000000000..93ea799cc --- /dev/null +++ b/java_code/configs/verticalLine.config @@ -0,0 +1,29 @@ +A vertical line of policemen in the center of the arena. Policemen are added after 5 seconds. +epsilon: 2 +mu: 10 +alpha: 700 +gamma: 600 +numberOfPeople: 500 +flockRadius: 8 +dt: 0.01 +percentParticipating: 0.5 +rParticipating: 6 +minCirclePitSize: 1 +minParticipatingNeighbors: 4 +dataCollectionInterval: 1000 +insertPoliceAfter: 100 +collectDataThisManyTimes: 300 +useThisManySeeds: 50 +policeConfig: + __________ +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | +| x | + ---------- \ No newline at end of file diff --git a/java_code/data_analysis.py b/java_code/data_analysis.py new file mode 100644 index 000000000..d2e5df79f --- /dev/null +++ b/java_code/data_analysis.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat Dec 2 16:10:23 2017 + +@author: Johanna +""" + + +from numpy import * +from matplotlib.pylab import * +from counter import n + + + + +def analysis1(isParticipating,r,density): + figure() + for k in arange(len(x)): + if(isParticipating[k]==1): + + plot(r[k],density[k],'ro') + else: + plot(r[k],density[k],'bo') + xlabel('distance to center') + ylabel('danger caused by high density') + title('Density') + #show() + +def densityAnalysis(r,density): + figure() + plot(r,density,'ro') + xlabel('distance to center') + ylabel('danger caused by density') + title('Density') + #show() + +def analysis2(r,isParticipating,F): + figure() + for k in arange(len(x)): + if(isParticipating[k]==1): + + plot(r[k],F[k],'ro') + else: + plot(r[k],F[k],'bo') + xlabel('distance to center') + ylabel('danger caused by high forces') + title('Force') + #show() + +def forceAnalysis(r,F): + figure() + plot(r,F,'ro') + xlabel('distance to center') + ylabel('danger caused by forces') + title('Force') + #show() +def isParticipatingAnalysis(r,isParticipating,density,F): + figure() + plot(isParticipating,density,'ro') + xlabel('Participating') + ylabel('danger caused by high density') + title('Density') + #show() + figure() + plot(isParticipating,F,'ro') + xlabel('Participating') + ylabel('danger caused by forces') + title('Force') + show() +def analyse(): + for k in range(0,n): + filename='out%s' % k + exec("from %s import *" %filename, globals()) + + + r=sqrt((x-50)**2+(y-50)**2) + + densityAnalysis(r,density) + analysis1(isParticipating,r,density) + forceAnalysis(r,F) + analysis2(r,isParticipating,F) + isParticipatingAnalysis(r,isParticipating,density,F) + print (k) +analyse() diff --git a/java_code/phase_diagram.py b/java_code/phase_diagram.py new file mode 100644 index 000000000..622046d07 --- /dev/null +++ b/java_code/phase_diagram.py @@ -0,0 +1,62 @@ + + +from numpy import * +from matplotlib.pylab import * +from scipy import interpolate + + +# This program shows density's and force's levels in a phase +# diagram of a single 'out#.py' file + +def phaseDiagram(x, y, density, F): + + # levels to be considerated safe/dangerous + safeDensity = 10; + density1 = 25; + density2 = 40; + + safeForce = 1000; + force1 = 2500; + force2 = 4000; + + # interpolates the data to get a full grid + grid_x, grid_y = mgrid[0:99:200j, 0:99:200j] + points = (x, y) + grid_density = interpolate.griddata(points, density, (grid_x, grid_y),method='cubic') + grid_F = interpolate.griddata(points, F, (grid_x, grid_y),method='cubic') + + figure() + # plots density diagram + subplot(121) + subplots_adjust(wspace=0.5) + imD = imshow(grid_density, extent=(0,99,0,99), origin='upper', cmap='hot', vmin=safeDensity, vmax=density2+10) + axis('off') + cbarD = colorbar(imD, fraction=0.046, pad=0.04, orientation='vertical', ticks=[safeDensity, density1, density2]) + cbarD.ax.set_yticklabels(['Low', 'Medium', 'Death']) + title('Density danger') + + # plots force diagram + subplot(122) + imF = imshow(grid_F, extent=(0,99,0,99), origin='upper', cmap='hot', vmin=safeForce, vmax=force2+1000) + axis('off') + cbarD = colorbar(imF, fraction=0.046, pad=0.04, orientation='vertical', ticks=[safeForce, force1, force2]) + cbarD.ax.set_yticklabels(['Low', 'Medium', 'Death']) + + title('Force danger') + + show() + + +def analyse(num): + # get the file and run the diagramPhase function + filename='out%s' % num + exec("from %s import *" %filename, globals()) + phaseDiagram(x, y, density, F) + +# user's interface +k = input('Which out to import?\n-> ') +analyse(k) +while k != 0: + k = input('and now?\n-> ') + analyse(k) + diff --git a/java_code/phase_diagram_multi.py b/java_code/phase_diagram_multi.py new file mode 100644 index 000000000..416a9d42f --- /dev/null +++ b/java_code/phase_diagram_multi.py @@ -0,0 +1,80 @@ +import numpy as np +import matplotlib.pylab as plt +from scipy import interpolate + + +def phase_diagram(x, y, danger): + # levels to be considered safe/dangerous + safe_danger = 0.4 + danger1 = 0.9 + danger2 = 1.4 + + # interpolates the data to get a full grid + grid_x, grid_y = mgrid[0:99:200j, 0:99:200j] + points = (x, y) + grid_danger = interpolate.griddata(points, danger, (grid_x, grid_y), + method='cubic') + + plt.figure() + # plots danger diagram + im_d = plt.imshow(grid_danger, extent=(0, 99, 0, 99), origin='upper', + cmap='hot', vmin=safe_danger, vmax=danger2 + 0.2) + plt.axis('off') + cbar_d = plt.colorbar(im_d, fraction=0.046, pad=0.04, orientation='vertical', + ticks=[safe_danger, danger1, danger2]) + cbar_d.ax.set_yticklabels(['Low', 'Medium', 'High']) + plt.title('Danger Level') + + plt.show() + + +# This program shows danger levels in a phase +# diagram as an average of a set of 'out.py' files + +# Average function which looks for two equals points +def average_(points2, d2): + global continuous_danger_level_ + global points + continuous_danger_level_ = np.append(continuous_danger_level_, d2) + points_[0] = np.append(points_[0], points2[0]) + points_[1] = np.append(points_[1], points2[1]) + + +# Imports and averages the data-set from the out files +def analyse2(test_set, time): + + # Import the data from the out.py files + to_do = "from %s.counter import *" % test_set + exec(to_do, globals()) + + global points_ + global continuous_danger_level_ + exec("from %s.out_0_0 import *" % (test_set), globals()) + points_ = [x, y] + continuous_danger_level_ = continuousDangerLevel + + # Iterate over the seeds + for i in range(0, m): + try: + filename_ = 'out_%s_%s' % (i, time) + exec("from %s.%s import *" % (test_set, filename_), globals()) + except ModuleNotFoundError: + break + + # Average over the seeds + average_((x, y), continuousDangerLevel) + + phase_diagram(points_[0], points_[1], continuous_danger_level_) + + +if __name__ == '__main__' and __package__ is None: + import sys, os.path as path + + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + + # Iterate over all the config files in configs.sim + with open("configs.sim") as f: + next(f) + for test_set in f: + time = int(input('Choose time -> ')) + analyse2(test_set.strip(), time) diff --git a/java_code/policeman.png b/java_code/policeman.png new file mode 100644 index 000000000..be36acd74 Binary files /dev/null and b/java_code/policeman.png differ diff --git a/java_code/src/AutomaticSimulator.java b/java_code/src/AutomaticSimulator.java new file mode 100644 index 000000000..f49f1d988 --- /dev/null +++ b/java_code/src/AutomaticSimulator.java @@ -0,0 +1,166 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; +import java.util.Scanner; + +/** + * An object that reads the configs given in configs.sim and + * executes the corresponding simulations while dumping data for analysis. + */ +public class AutomaticSimulator { + + private Queue configNames = new LinkedList(); + // The configs to simulate + private Simulation simulation; + + public AutomaticSimulator() throws FileNotFoundException { + readConfigNames(); + } + + /** + * Runs the automatic simulation. + * + * @throws FileNotFoundException either if configs.sim could + * not be found or if a configuration + * specified in configs.sim could + * not be found. + */ + public void run() throws FileNotFoundException { + // Keep going while not all configurations have been tested + while (!configNames.isEmpty()) { + String config = configNames.poll(); + Config c = readConfigFile(config); + newSimulation(c); + // Poll the simulation to see if it is done yet (really ugly but + // oh well...) + while (!simulation.isCurrentIterationFinished) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + simulation.isCurrentIterationFinished = false; + } + System.exit(0); + } + + // Reads the configurations to simulate from configs.sim + private void readConfigNames() throws FileNotFoundException { + Scanner scanner = new Scanner(new File("configs.sim")); + scanner.nextLine(); + while (scanner.hasNext()) { + configNames.add(scanner.next()); + } + System.out.println("Starting simulation with configs: " + configNames); + } + + // Reads a configuration file + private Config readConfigFile(String config) throws FileNotFoundException { + Scanner scanner = + new Scanner(new File("configs/" + config + ".config")); + scanner.nextLine(); // Skip comment on first line + double[] data = new double[15]; + Config c = new Config(); + c.name = config; + for (int i = 0; i < data.length; + i++) { // Read parameters from config file + scanner.next(); + data[i] = scanner.nextDouble(); + } + c.readNumbersFromArray(data); + scanner.nextLine(); // Move to line after all the number values + scanner.nextLine(); // Move over line with "policeSectors: " + scanner.nextLine(); // Move over upper boundary of ASCII-based + // representation of matrix + for (int i = 0; i < 10; i++) { + String line = scanner.nextLine(); + for (int j = 0; j < 10; j++) { + if (line.charAt(j + 1) == + 'x') { // j+1 because of leading |, 'x' means there + // is a policeman there + c.policeSectors[j][i] = true; + } + } + } + System.out.println("Current config: " + config); + System.out.println(" data = " + Arrays.toString(data)); + System.out.println(" policeSectors = "); + for (int i = 0; i < c.policeSectors.length; i++) { + System.out.println(" " + Arrays.toString(c.policeSectors[i])); + } + return c; + } + + private void newSimulation(Config c) { + if (simulation == null) { + // Initialize the simulation with data from the config file + simulation = new Simulation(c.epsilon, c.mu, c.alpha, c.gamma, + c.numberOfPeople, c.flockRadius, c.dt, + c.percentParticipating, + c.rParticipating, c.minCirclePitSize, + c.minParticipatingNeighbors); + simulation.createWindow(); + simulation.createNewTimer(new DataCollector(simulation, c.name, + c.dataCollectionInterval, + c.insertPoliceAfter, + c.amountOfSeeds, + c.collectionTimes, + c.policeSectors)); + simulation.start(); + } else { + // Reinitialize all the parameters with data from the config file + simulation.stop(); + simulation.epsilon = c.epsilon; + simulation.alpha = c.alpha; + simulation.mu = c.mu; + simulation.gamma = c.gamma; + simulation.numberOfPeople = c.numberOfPeople; + simulation.flockRadius = c.flockRadius; + simulation.dt = c.dt; + simulation.percentParticipating = c.percentParticipating; + simulation.rParticipating = c.rParticipating; + simulation.minCirclePitSize = c.minCirclePitSize; + simulation.minParticipatingNeighbors = c.minParticipatingNeighbors; + simulation.createNewTimer(new DataCollector(simulation, c.name, + c.dataCollectionInterval, + c.insertPoliceAfter, + c.amountOfSeeds, + c.collectionTimes, + c.policeSectors)); + simulation.basicReset(); + simulation.start(); + } + } + + // A class that stores all the parameters from a config file + private class Config { + String name; + double epsilon, alpha, mu, gamma, dt, percentParticipating; + int numberOfPeople, flockRadius, rParticipating, minCirclePitSize, + minParticipatingNeighbors, dataCollectionInterval, + insertPoliceAfter, collectionTimes, amountOfSeeds; + boolean[][] policeSectors = new boolean[10][10]; + + private void readNumbersFromArray(double[] array) { + assert array.length == 15; + epsilon = array[0]; + mu = array[1]; + alpha = array[2]; + gamma = array[3]; + numberOfPeople = (int) array[4]; + flockRadius = (int) array[5]; + dt = array[6]; + percentParticipating = array[7]; + rParticipating = (int) array[8]; + minCirclePitSize = (int) array[9]; + minParticipatingNeighbors = (int) array[10]; + dataCollectionInterval = (int) array[11]; + insertPoliceAfter = (int) array[12]; + collectionTimes = (int) array[13]; + amountOfSeeds = (int) array[14]; + } + } +} diff --git a/java_code/src/ControlPanel.java b/java_code/src/ControlPanel.java new file mode 100644 index 000000000..7ca390a69 --- /dev/null +++ b/java_code/src/ControlPanel.java @@ -0,0 +1,288 @@ +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class ControlPanel extends JPanel + implements PropertyChangeListener, ChangeListener { + + private Simulation simulation; + private SimulationPanel simulationpanel; + private JButton startPauseButton; + private JButton restartButton; + private JButton exportDataButton; + private JCheckBox enableDensity; + private JCheckBox enableForce; + private JCheckBox showParticipants; + private JLabel epsilonLabel, muLabel, alphaLabel, flockRadiusLabel, + gammaLabel, initialParticipantsLabel, percLabel, rPartLabel, + sizeLabel, minNeighborsLabel; + private JFormattedTextField epsilonField, muField, alphaField, gammaField, + initialParticipantsField, percField, sizeField, minNeighborsField; + private JSlider rPartSlider, flockRadiusSlider; + + private boolean paused = true; + + public ControlPanel(Simulation simulation, + SimulationPanel simulationpanel) { + this.simulation = simulation; + this.simulationpanel = simulationpanel; + setPreferredSize(new Dimension(500, 230)); + setBackground(Color.WHITE); + setLayout(new GridLayout(13, 2)); + + initComponents(); + addListeners(); + addComponents(); + } + + private void initComponents() { + startPauseButton = new JButton("Start"); + restartButton = new JButton("Reinitialize"); + showParticipants = new JCheckBox("Show Participants"); + showParticipants.setSelected(true); + exportDataButton = new JButton("Export Data for Analysis"); + + enableDensity = new JCheckBox("Density is a Danger Factor"); + enableDensity.setSelected(true); + enableForce = new JCheckBox("Force is a Danger Factor"); + enableForce.setSelected(true); + + epsilonLabel = new JLabel("Repulsion Strength (\u03b5)"); + + epsilonField = new JFormattedTextField(); + epsilonField.setValue(simulation.epsilon); + epsilonField.setColumns(10); + + muLabel = new JLabel("Propulsion Strength (\u03bc)"); + + muField = new JFormattedTextField(); + muField.setValue(simulation.mu); + muField.setColumns(10); + + alphaLabel = new JLabel("Flocking Force Strength (\u03b1)"); + + alphaField = new JFormattedTextField(); + alphaField.setValue(simulation.alpha); + alphaField.setColumns(10); + + flockRadiusLabel = new JLabel("Flocking Force Radius"); + flockRadiusSlider = new JSlider(); + flockRadiusSlider.setValue((int) simulation.flockRadius); + configureSlider(flockRadiusSlider, 1, 5, 5, 2 * Simulation.SECTOR_SIZE); + + gammaLabel = new JLabel("Centripetal Force Strength (\u03b3)"); + + gammaField = new JFormattedTextField(); + gammaField.setValue(simulation.gamma); + gammaField.setColumns(10); + + initialParticipantsLabel = new JLabel("Initial Size of Crowd"); + + initialParticipantsField = new JFormattedTextField(); + initialParticipantsField.setValue(simulation.numberOfPeople); + initialParticipantsField.setColumns(10); + + percLabel = new JLabel("Percentage of Initial Participants"); + + percField = new JFormattedTextField(); + percField.setValue(simulation.percentParticipating); + percField.setColumns(10); + + rPartLabel = new JLabel("Search Radius for Part. Neighbors"); + + rPartSlider = new JSlider(); + rPartSlider.setValue((int) simulation.rParticipating); + configureSlider(rPartSlider, 1, 10, 0, 2 * Simulation.SECTOR_SIZE); + + sizeLabel = new JLabel("Neighbors Needed to Continue Participating"); + + String fontFamily = sizeLabel.getFont().getFamily(); + sizeLabel.setFont(new Font(fontFamily, Font.PLAIN, 11)); + + + sizeField = new JFormattedTextField(); + sizeField.setValue(simulation.minCirclePitSize); + sizeField.setColumns(10); + + minNeighborsLabel = new JLabel( + "Neighbors Needed to Join in Circle Pit"); + + minNeighborsField = new JFormattedTextField(); + minNeighborsField.setValue(simulation.minParticipatingNeighbors); + minNeighborsField.setColumns(10); + + } + + private void configureSlider(JSlider slider, int minorSpacing, + int majorSpacing, int lowerLimit, + int upperLimit) { + slider.setMajorTickSpacing(majorSpacing); + slider.setMinorTickSpacing(minorSpacing); + slider.setMinimum(lowerLimit); + slider.setMaximum(upperLimit); + slider.setPaintLabels(true); + slider.setPaintTicks(true); + } + + private void addListeners() { + startPauseButton.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + if (paused) { + simulation.getSimulationTimer().start(); + startPauseButton.setText("Pause"); + } else { + simulation.getSimulationTimer().stop(); + startPauseButton.setText("Start"); + } + paused = !paused; + } + }); + + restartButton.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + startPauseButton.setText("Start"); + paused = true; + simulation.resetMatrix(); + } + }); + + showParticipants.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + simulationpanel.shouldShowParticipants = + !simulationpanel.shouldShowParticipants; + getParent().repaint(); + } + }); + + exportDataButton.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + simulation.exportData(); + } + }); + + enableDensity.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + if (!simulation.enableDensity) { + simulation.enableDensity = true; + } else { + simulation.enableDensity = false; + for (Individual individual : simulation.getMatrix() + .getIndividuals()) { + individual.dangerLevel = 0; + } + } + } + }); + + enableForce.addActionListener(new ActionListener() { + /* @Override */ + public void actionPerformed(ActionEvent e) { + if (!simulation.enableForce) { + simulation.enableForce = true; + } else { + simulation.enableForce = false; + for (Individual individual : simulation.getMatrix() + .getIndividuals()) { + individual.dangerLevel = 0; + } + } + } + }); + + epsilonField.addPropertyChangeListener("value", this); + alphaField.addPropertyChangeListener("value", this); + flockRadiusSlider.addChangeListener(this); + muField.addPropertyChangeListener("value", this); + gammaField.addPropertyChangeListener("value", this); + initialParticipantsField.addPropertyChangeListener("value", this); + percField.addPropertyChangeListener("value", this); + rPartSlider.addChangeListener(this); + sizeField.addPropertyChangeListener("value", this); + minNeighborsField.addPropertyChangeListener("value", this); + } + + private void addComponents() { + add(startPauseButton); + add(restartButton); + add(showParticipants); + add(exportDataButton); + add(enableDensity); + add(enableForce); + add(epsilonLabel); + add(epsilonField); + add(muLabel); + add(muField); + add(alphaLabel); + add(alphaField); + add(flockRadiusLabel); + add(flockRadiusSlider); + add(gammaLabel); + add(gammaField); + add(initialParticipantsLabel); + add(initialParticipantsField); + add(percLabel); + add(percField); + add(rPartLabel); + add(rPartSlider); + add(sizeLabel); + add(sizeField); + add(minNeighborsLabel); + add(minNeighborsField); + + setVisible(true); + } + + /** + * Called when a field's "value" property changes. + */ + /* @Override */ + public void propertyChange(PropertyChangeEvent e) { + Object source = e.getSource(); + if (source == epsilonField) { + simulation.epsilon = + ((Number) epsilonField.getValue()).doubleValue(); + } else if (source == muField) { + simulation.mu = ((Number) muField.getValue()).doubleValue(); + } else if (source == alphaField) { + simulation.alpha = ((Number) alphaField.getValue()).doubleValue(); + } else if (source == gammaField) { + simulation.gamma = ((Number) gammaField.getValue()).doubleValue(); + } else if (source == initialParticipantsField) { + simulation.numberOfPeople = + ((Number) initialParticipantsField.getValue()).intValue(); + } else if (source == percField) { + simulation.percentParticipating = + ((Number) percField.getValue()).doubleValue(); + } else if (source == rPartSlider) { + simulation.rParticipating = + ((Number) rPartSlider.getValue()).doubleValue(); + } else if (source == sizeField) { + simulation.minCirclePitSize = + ((Number) sizeField.getValue()).intValue(); + } else if (source == minNeighborsField) { + simulation.minParticipatingNeighbors = + ((Number) minNeighborsField.getValue()).intValue(); + } + } + + /* @Override */ + public void stateChanged(ChangeEvent e) { + JSlider source = (JSlider) e.getSource(); + if (source == rPartSlider && !source.getValueIsAdjusting()) { + simulation.rParticipating = (double) rPartSlider.getValue(); + } else if (source == flockRadiusSlider && + !source.getValueIsAdjusting()) { + simulation.flockRadius = (double) flockRadiusSlider.getValue(); + } + } +} diff --git a/java_code/src/DataCollector.java b/java_code/src/DataCollector.java new file mode 100644 index 000000000..3b31fdf78 --- /dev/null +++ b/java_code/src/DataCollector.java @@ -0,0 +1,296 @@ +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Class that automatically runs the simulation for one police configuration and + * harvests data from it. + */ +public class DataCollector implements ActionListener { + + private final int MAX_TIME; // Max amount of timepoints to dump data for + private final int MAX_SEEDS; // Max amount of seeds to test + private final int dataCollectionInterval; // How often to collect data + public boolean hasInsertedPolice = false; // True if police inserted + private Simulation simulation; // Simulation for which to collect data + private PrintWriter fileCounterWriter; // Writes number of timepoints and + // number of seeds + private PrintWriter outWriter; // Writes data + private int seedCounter = 0; // Counts how many seeds have already been + // tested + private int timeCounter = 0; // Counts how many timepoints we have + // already dumped data for + private int realTimeElapsed = 0; // Amount of timesteps of the simulation + // elapsed + private int insertPoliceAfterCounter = 0; // Counter to test if we should + // insert police + private boolean stillWaitingToInsertPolice = true; // True if police not + // inserted yet + private int insertPoliceAfter; // How many timesteps to wait before + // inserting police + private boolean[][] policeSectors = new boolean[10][10]; // Sectors with + // police + + private String configurationName; // Name of the configuration we are + // currently testing + + public DataCollector(Simulation simulation, String configurationName, + int dataCollectionInterval, int insertPoliceAfter, + int maxSeeds, int maxTime, boolean[][] policeSectors) { + this.simulation = simulation; + this.dataCollectionInterval = dataCollectionInterval; + this.insertPoliceAfter = insertPoliceAfter; + this.policeSectors = policeSectors; + MAX_TIME = maxTime; + MAX_SEEDS = maxSeeds; + // Files are named as out_[seed#]_[time#].py in a subfolder for this + // configuration + File file = new File(configurationName + "/out_0_0.py"); + file.getParentFile().mkdirs(); + try { + fileCounterWriter = new PrintWriter( + configurationName + "/counter.py"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + try { + outWriter = new PrintWriter(configurationName + "/out_0_0.py", + "UTF-8"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + this.configurationName = configurationName; + File initFile = new File(configurationName + "/__init__.py"); + try { + initFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Iterates over the given list and, for each element of that list, adds the + * value of the field with the given name to a numpy array that is written + * to a file using the provided PrintWriter. The resulting array is named + * description. + * + * @param list The list from which to gather the data + * @param propertyName The property that we want to output + * @param writer The writer with which to write the data + * @param description The name of the numpy array + */ + public static void writeNumPyArray(List list, String propertyName, + PrintWriter writer, String description) { + writer.print(description + " = array(["); + boolean firstTime = true; + try { + for (Object object : list) { + Object value = object.getClass().getField(propertyName).get( + object); + if (firstTime) writer.print(value); + else writer.print(", " + value); + firstTime = false; + } + writer.println("])"); + writer.flush(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /*@Override*/ + public void actionPerformed(ActionEvent e) { + PositionMatrix matrix = simulation.getMatrix(); + realTimeElapsed += + Simulation.TIMESTEP; // Check how much time has elapsed to + // see if we should dump data + if (!simulation.isCurrentIterationFinished) { + simulation.runOneTimestep(); + simulation.repaint(); + if (stillWaitingToInsertPolice) { + insertPoliceAfterCounter++; + } + if (insertPoliceAfterCounter >= + insertPoliceAfter) { // Check if the appropriate amount + // of time before inserting police + // has elapsed + stillWaitingToInsertPolice = false; + } + if (!stillWaitingToInsertPolice && + !hasInsertedPolice) { // Insert the police + simulation.setMonitoredSectors(policeSectors); + this.hasInsertedPolice = true; + } + } + // Dump data if now is the right time + if (realTimeElapsed % dataCollectionInterval == 0 && + !simulation.isCurrentIterationFinished) { + realTimeElapsed = 0; + + outWriter.println("from numpy import *"); + outWriter.flush(); + + List individuals = matrix.getIndividuals(); + + // Write data on positions of individuals + writeNumPyArray(individuals, "x", outWriter, "x"); + writeNumPyArray(individuals, "y", outWriter, "y"); + + // Write data on danger levels of individuals + writeNumPyArray(individuals, "dangerLevel", outWriter, + "dangerLevel"); + writeNumPyArray(individuals, "continuousDangerLevel", outWriter, + "continuousDangerLevel"); + + writeIsParticipatingData( + matrix); // Write data on which individuals are + // participating + writeNumPyArray(individuals, "f", outWriter, + "F"); // Write data on force acting on individuals + writeNumPyArray(individuals, "density", outWriter, + "density"); // Write data on density surrounding + // individuals + writeAverageDanger(matrix); + writeMaxDanger(matrix); + writeMedianDanger(matrix); + + // If we have dumped data enough times, restart with a new seed + if (timeCounter >= MAX_TIME) { + timeCounter = 0; + simulation.setSeed(seedCounter); + seedCounter++; + simulation.restartSimulation(); + hasInsertedPolice = false; + stillWaitingToInsertPolice = true; + insertPoliceAfterCounter = 0; + } + // If we have tested enough seeds, finish the simulation + if (seedCounter >= MAX_SEEDS) { + fileCounterWriter.println("n = " + MAX_TIME); + fileCounterWriter.println("m = " + MAX_SEEDS); + fileCounterWriter.flush(); + fileCounterWriter.close(); + outWriter.flush(); + outWriter.close(); + simulation.isCurrentIterationFinished = true; + } else { + resetWriters(configurationName + "/out_" + seedCounter + "_" + + timeCounter + ".py"); + timeCounter++; + } + } + } + + // Writes 1 to a numpy array if individual is participating, else 0 + private void writeIsParticipatingData(PositionMatrix matrix) { + outWriter.print("isParticipating = array(["); + boolean firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) outWriter.print( + (individual.isParticipating ? 1 : 0)); + else outWriter.print(", " + (individual.isParticipating ? 1 : 0)); + firstTime = false; + } + outWriter.println("])"); + outWriter.flush(); + } + + // Writes the average danger of all the individuals + private void writeAverageDanger(PositionMatrix matrix) { + outWriter.print("averageDanger = "); + double averageDanger = 0; + for (Individual individual : matrix.getIndividuals()) { + averageDanger += individual.f / 10000 + individual.density / 50; + } + averageDanger /= matrix.getIndividuals().size(); + outWriter.println(averageDanger); + outWriter.flush(); + } + + // Writes the max danger of all the individuals + private void writeMaxDanger(PositionMatrix matrix) { + outWriter.print("maxDanger = "); + double maxDanger = Double.MIN_VALUE; + for (Individual individual : matrix.getIndividuals()) { + maxDanger = Math.max(maxDanger, individual.f / 10000 + + individual.density / 50); + } + outWriter.println(maxDanger); + outWriter.flush(); + } + + private void writeMedianDanger(PositionMatrix matrix) { + outWriter.print("medianDanger = "); + // Check if there is an even amount of individuals + ArrayList individuals = + (ArrayList) matrix.getIndividuals(); + int nOver2 = individuals.size() / 2; + + double median = (individuals.size() % 2 == 0) ? + (getNthSmallestContinuousDangerLevel(individuals, nOver2) + + getNthSmallestContinuousDangerLevel(individuals, + nOver2 + 1)) / 2 : + getNthSmallestContinuousDangerLevel(individuals, nOver2); + + outWriter.println(median); + outWriter.flush(); + } + + // QuickSelect algorithm + private static double getNthSmallestContinuousDangerLevel( + ArrayList individuals, int n) { + double result; + double pivot; + + // 3 ArrayLists for elements smaller than pivot, equal to pivot, + // larger than pivot + ArrayList lessThanPivot = new ArrayList(); + ArrayList greaterThanPivot = new ArrayList(); + ArrayList equalToPivot = new ArrayList(); + + // Select a random pivot + pivot = individuals.get((int) (Math.random() * + individuals.size())).continuousDangerLevel; + + // Add each element of the given list to the appropriate category + for (Individual individual : individuals) { + if (individual.continuousDangerLevel < pivot) { + lessThanPivot.add(individual); + } else if (individual.continuousDangerLevel > pivot) { + greaterThanPivot.add(individual); + } else { + equalToPivot.add(individual); + } + } + + // Recurse into the appropriate category or return the pivot + if (n < lessThanPivot.size()) { + result = getNthSmallestContinuousDangerLevel(lessThanPivot, n); + } else if (n < lessThanPivot.size() + equalToPivot.size()) { + result = pivot; + } else { + result = getNthSmallestContinuousDangerLevel(greaterThanPivot, n - + lessThanPivot.size() - equalToPivot.size()); + } + + return result; + } + + // Make sure we are writing to the appropriate file + private void resetWriters(String newOutFileName) { + try { + outWriter.close(); + outWriter = new PrintWriter(newOutFileName, "UTF-8"); + } catch (FileNotFoundException e3) { + e3.printStackTrace(); + } catch (UnsupportedEncodingException e3) { + e3.printStackTrace(); + } + } +} diff --git a/java_code/src/Individual.java b/java_code/src/Individual.java new file mode 100644 index 000000000..a1735a4f9 --- /dev/null +++ b/java_code/src/Individual.java @@ -0,0 +1,94 @@ +/** + * Represents an individual. + */ +public class Individual { + /** + * The x position of this individual. + */ + public double x; + /** + * The y position of this individual. + */ + public double y; + /** + * The x velocity of this individual. + */ + public double vx; + /** + * The y velocity of this individual. + */ + public double vy; + /** + * True if the individual is participating in the circle pit. + */ + public boolean isParticipating; + /** + * The size of the individual. + */ + public double radius = 2; + /** + * The preferred speed of the individual. + */ + public double preferredSpeed = 30; + /** + * The danger level that the individual is currently at. Ranges from 0 to 6. + */ + public int dangerLevel; + /** + * The norm of the force currently acting on the individual. + */ + public double f; + /** + * Number of neighbors of the individual. + */ + public double density; + /** + * Continuous (rather than discrete) measure of the danger level of an + * individual. + */ + public double continuousDangerLevel; + + public Individual(double x, double y, double vx, double vy, + boolean isParticipating, int dangerLevel) { + this.x = x; + this.y = y; + this.vx = vx; + this.vy = vy; + this.isParticipating = isParticipating; + this.dangerLevel = dangerLevel; + } + + public Individual(double[] position, double[] velocity, + boolean isParticipating, int dangerLevel) { + this(position[0], position[1], velocity[0], velocity[1], + isParticipating, dangerLevel); + } + + public double[] getPosition() { + return new double[]{x, y}; + } + + public double[] getVelocity() { + return new double[]{vx, vy}; + } + + public double distanceTo(Individual other) { + double dx = Math.abs(x - other.x); + double dy = Math.abs(y - other.y); + + return Math.sqrt(dx * dx + dy * dy); + } + + public double distanceTo(double[] point) { + double dx = Math.abs(x - point[0]); + double dy = Math.abs(y - point[1]); + + return Math.sqrt(dx * dx + dy * dy); + } + + @Override + public String toString() { + return "Individual: position = (" + x + ", " + y + "), velocity = (" + + vx + ", " + vy + "), isParticipating = " + isParticipating; + } +} diff --git a/java_code/src/Main.java b/java_code/src/Main.java new file mode 100644 index 000000000..83aa713ad --- /dev/null +++ b/java_code/src/Main.java @@ -0,0 +1,14 @@ +import java.io.FileNotFoundException; + +public class Main { + public static void main(String[] args) throws FileNotFoundException { + // Run a manual simulation + // Simulation simulation = new Simulation(2, 10, 700, 600,500, 8, + // 0.01, 0.5, 6, 1, 3); + // simulation.runManualSimulation(); + + // Run an automatic simulation + AutomaticSimulator test = new AutomaticSimulator(); + test.run(); + } +} diff --git a/java_code/src/PositionMatrix.java b/java_code/src/PositionMatrix.java new file mode 100644 index 000000000..a0831b34e --- /dev/null +++ b/java_code/src/PositionMatrix.java @@ -0,0 +1,224 @@ +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Data structure representing the position matrix in the simulation. + */ +public class PositionMatrix { + + /** + * The width of the matrix + */ + public final int width; + /** + * The height of the matrix + */ + public final int height; + /** + * The size of one sector + */ + public final int sectorSize; + /** + * A boolean matrix to indicate whether sector (i, j) is under police + * surveillance + */ + public boolean[][] isPoliceAtSector; + // The actual matrix + private LinkedList[][] matrix; + // A List containing all the individuals, makes iterating over them easier + private ArrayList individuals; + + /** + * Creates a new PositionMatrix + * + * @param width The desired width of the matrix + * @param height The desired height of the matrix + * @param sectorSize The desired sector size + */ + public PositionMatrix(int width, int height, int sectorSize) { + this.height = height; + this.width = width; + this.sectorSize = sectorSize; + + // Initialize the matrix + matrix = (LinkedList[][]) new LinkedList[width][height]; + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + matrix[i][j] = new LinkedList(); + } + } + + individuals = new ArrayList(); + isPoliceAtSector = new boolean[width][height]; + } + + /** + * Gets an individual at position (i, j) in the matrix. + * + * @param i The row of the individual + * @param j The column of the individual + * @return The individual at (i, j) + */ + public List get(int i, int j) { + return matrix[i][j]; + } + + /** + * Gets an individual at the specified sector in the matrix. + * + * @param sector The sector that the individual is in. + * @return The individual at the given sector. + */ + public List get(Sector sector) { + return matrix[sector.row][sector.col]; + } + + /** + * Gets a list of all the individuals. + * + * @return The list of the individuals. + */ + public List getIndividuals() { + return individuals; + } + + /** + * Adds a new individual to the matrix at the proper position. + * + * @param individual The individual to be added + */ + public void add(Individual individual) { + Sector sector = getSectorForCoords(individual); + matrix[sector.row][sector.col].add(individual); + individuals.add(individual); + } + + public void removeAndAdd(Individual individual, Sector oldSector, + Sector newSector) { + matrix[oldSector.row][oldSector.col].remove(individual); + matrix[newSector.row][newSector.col].add(individual); + } + + /** + * Gets the neighbors of an individual. + * + * @param individual The individual for whom to search for neighbors + * @param radius The radius in which to search + * @return A java.util.List of the neighbors. + */ + public List getNeighborsFor(Individual individual, + double radius) { + ArrayList neighbors = new ArrayList(10); + ArrayList sectorsToSearch = new ArrayList(9); + Sector sector = getSectorForCoords(individual); + sectorsToSearch.add(sector); + + // Look around the current sector + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + Sector newSector = getSectorForCoords(individual.x + i * radius, + individual.y + + i * radius); + if (!sector.equals(newSector)) { + sectorsToSearch.add(newSector); + } + } + } + + // Look for neighbors at the appropriate distance in the sectors to + // search + for (Sector sectorToSearch : sectorsToSearch) { + List possibleNeighbors = get(sectorToSearch); + for (Individual neighbor : possibleNeighbors) { + if (individual.distanceTo(neighbor) < radius) { + neighbors.add(neighbor); + } + } + } + + return neighbors; + } + + /** + * Gets the sector for the given coordinates. + * + * @param x The x coordinate + * @param y The y coordinate + * @return A Sector object corresponding the the given + * coordinates. + */ + public Sector getSectorForCoords(double x, double y) { + int sectorX = (int) x / sectorSize; + int sectorY = (int) y / sectorSize; + + // Make sure the sector is not out of bounds + if (sectorX < 0) sectorX = 0; + else if (sectorX >= width) sectorX = width - 1; + + if (sectorY < 0) sectorY = 0; + else if (sectorY >= height) sectorY = height - 1; + + return new Sector(sectorX, sectorY); + } + + /** + * Gets the sector that the given individual is in. + * + * @param individual The individual for whom to get the sector + * @return The sector that the individual is in. + * @see PositionMatrix#getSectorForCoords(double, double) + */ + public Sector getSectorForCoords(Individual individual) { + return getSectorForCoords(individual.x, individual.y); + } + + /** + * Gets the sector that the given individual is in given an array of {x, y} + * coordinates. + * + * @param position The array of coordinates + * @return The sector corresponding to position + * @see PositionMatrix#getSectorForCoords(double, double) + */ + public Sector getSectorForCoords(double[] position) { + return getSectorForCoords(position[0], position[1]); + } + + /** + * Checks if the given sector is monitored by the police. + */ + public boolean isSectorMonitored(Sector sector) { + return isPoliceAtSector[sector.row][sector.col]; + } + + /** + * Adds the given list of sectors to be monitored by the police. + * + * @param policeSectors A comma separated list of sectors, e.g. 0, 0, 1, 1, + * 2, 2 + */ + public void setMonitoredSectors(int... policeSectors) { + for (int i = 0; i < policeSectors.length; i += 2) { + isPoliceAtSector[policeSectors[i]][policeSectors[i + 1]] = true; + } + } + + /** + * An inner class that represents one sector at a particular location in the + * matrix. + */ + public class Sector { + int row; // Row of the matrix + int col; // Column of the matrix + + public Sector(int row, int col) { + this.row = row; + this.col = col; + } + + public boolean equals(Sector other) { + return row == other.row && col == other.col; + } + } +} diff --git a/java_code/src/Simulation.java b/java_code/src/Simulation.java new file mode 100644 index 000000000..c58641991 --- /dev/null +++ b/java_code/src/Simulation.java @@ -0,0 +1,683 @@ +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Random; + +/** + * Class representing one or multiple simulations of a circle pit. + */ +public class Simulation { + + /** + * Size of one sector of the matrix + */ + public static final int SECTOR_SIZE = 10; + /** + * The duration of one timestep (in milliseconds) + */ + public final static int TIMESTEP = 50; + /** + * Strength of repulsive force + */ + public double epsilon; + /** + * Strength of propulsion + */ + public double mu; + /** + * Strength of flocking force + */ + public double alpha; + /** + * Strength of the centripetal force + */ + public double gamma; + /** + * Number of people in the simulation + */ + public double numberOfPeople; + /** + * Radius within which velocity of neighbors has an effect on the flocking + * force + */ + public double flockRadius; + /** + * Timestep of the simulation + */ + public double dt; + /** + * Percentage of people initially participating in the circle pit + */ + public double percentParticipating; + /** + * Radius within which the 'isParticipating' of neighbors affects the + * individual + */ + public double rParticipating; + /** + * Minimum amount of people necessary to constitute a circle pit + */ + public int minCirclePitSize; + /** + * Necessary number of neighbors participating to start participating + */ + public int minParticipatingNeighbors; + /** + * Center of the matrix + */ + public double[] center; + /** + * Window with the simulation + */ + public SimulationGUI window; + /** + * Safe density level + */ + public int safeDensity = 10; + /** + * Density danger level 1 + */ + public int density1 = 20; + /** + * Density danger level 2 + */ + public int density2 = 40; + /** + * safe Force danger level + */ + public int safeForce = 1000; + /** + * Force danger level 1 + */ + public int force1 = 3000; + /** + * Density danger level 2 + */ + public int force2 = 4000; + /** + * True if force is considered to be a danger factor + */ + public boolean enableForce = true; + /** + * True if density is considered to be a danger factor + */ + public boolean enableDensity = true; + /** + * False while the current iteration of the simulation is running (used for + * automation) + */ + public boolean isCurrentIterationFinished = false; + + private PrintWriter writer; // Writes analysis data to a file + + private double maxX; // Right-hand border of the terrain + private double maxY; // Bottom border of the terrain + private Timer timer; // Timer to run the simulation + + private Random random = new Random(42); // To generate random numbers + private int fileCounter = 0; // Counts how many files were written + private PrintWriter fileCounterWriter; + // Writes information about how many files were written to a file + + private PositionMatrix matrix; + + /** + * Creates a new Simulation with the specified parameters. + * + * @param epsilon Strength of the repulsive force + * @param mu Strength of the propulsive force + * @param alpha Strength of the flocking force + * @param gamma Strength of the centripetal force + * @param numberOfPeople Number of people at the concert + * @param flockRadius Radius in which to look for neighbors + * for the flocking force + * @param dt Duration of one timestep + * @param percentParticipating Proportion of people initially + * participating in the circle pit + * @param rParticipating Radius in which to search for + * participating neighbors + * @param minCirclePitSize Minimum number of participating + * neighbors needed to continue + * participating in the circle pit + * @param minParticipatingNeighbors Minimum number of participating + * neighbors needed to join in the circle + * pit + */ + public Simulation(double epsilon, double mu, double alpha, double gamma, + double numberOfPeople, double flockRadius, double dt, + double percentParticipating, double rParticipating, + int minCirclePitSize, int minParticipatingNeighbors) { + this.epsilon = epsilon; + this.mu = mu; + this.alpha = alpha; + this.gamma = gamma; + this.numberOfPeople = numberOfPeople; + this.flockRadius = flockRadius; + this.dt = dt; + this.percentParticipating = percentParticipating; + this.rParticipating = rParticipating; + this.minCirclePitSize = minCirclePitSize; + this.minParticipatingNeighbors = minParticipatingNeighbors; + try { + fileCounterWriter = new PrintWriter("special_counter.py"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + initializeMatrix(); + } + + // Initializes the matrix with individuals with random velocity and + // position. + private void initializeMatrix() { + int matrixSize = 10; + + // The maximum x and y values that an individual can have + maxX = matrixSize * SECTOR_SIZE; + maxY = matrixSize * SECTOR_SIZE; + + center = new double[]{maxX / 2, maxY / 2}; + + matrix = new PositionMatrix(matrixSize, matrixSize, + SECTOR_SIZE); + + for (int i = 0; i < numberOfPeople; i++) { + // Generate random coordinates + double[] coords = new double[2]; + coords[0] = random.nextDouble() * SECTOR_SIZE * matrixSize; + coords[1] = random.nextDouble() * SECTOR_SIZE * matrixSize; + + // Generate random velocity + double[] velocity = new double[]{random.nextDouble() - 0.5, + random.nextDouble() - 0.5}; + + // Decide whether individual is initially participating + boolean isParticipating = + random.nextDouble() < percentParticipating; + + //levels of danger, 0 is safe, by default it's safe + int dangerLevel = 0; + + Individual individual = new Individual(coords, velocity, + isParticipating, + dangerLevel); + + // Add individual to appropriate sector + matrix.add(individual); + } + } + + /** + * Resets the matrix of the simulation. + */ + public void resetMatrix() { + stop(); + basicReset(); + } + + /** + * Restarts the simulation. + */ + public void restartSimulation() { + stop(); + basicReset(); + start(); + } + + /** + * Performs a basic reset of the simulation back to its initial state. + */ + public void basicReset() { + initializeMatrix(); + window.resetSimulationPanel(); + window.repaint(); + } + + /** + * Stops the simulation. + */ + public void stop() { + timer.stop(); + } + + /** + * Starts the simulation. + */ + public void start() { + timer.start(); + } + + /** + * Creates a new Timer to run the simulation + * + * @param listener The ActionListener the new + * Timer should be based on + */ + public void createNewTimer(ActionListener listener) { + timer = new Timer(TIMESTEP, listener); + } + + /** + * Redraws the simulation. + */ + public void repaint() { + window.repaint(); + } + + /** + * Sets the seed of the Random object used during the + * simulation. + * + * @param seed The seed to set the Random object to + */ + public void setSeed(int seed) { + random = new Random(seed); + } + + // Checks if the given neighbor at the given distance is participating + private boolean isNeighborParticipating(Individual neighbor, + double distance) { + return neighbor.isParticipating && distance < rParticipating; + } + + /** + * Runs the simulation manually (so that the user can interact with it). + */ + public void runManualSimulation() { + window = new SimulationGUI(this); + + // Run a new frame every 50 milliseconds + timer = new Timer(50, new ActionListener() { + /*@Override*/ + public void actionPerformed(ActionEvent e) { + runOneTimestep(); + window.repaint(); + } + }); + + window.setVisible(true); + } + + /** + * Creates a new window that displays and animates the simulation. + */ + public void createWindow() { + window = new SimulationGUI(this); + window.setVisible(true); + } + + /** + * Runs the simulation automatically (for use for automatic data + * collection). + * + * @param name The name of the folder in which to put + * generated data. + * @param dataCollectionInterval How often to collect data (in + * milliseconds) + * @param insertPoliceAfter The amount of time after which police + * should be inserted, if any + * @param amountOfSeeds How many random seeds to test + * @param collectionTimes How many times to collect data per seed + */ + public void runAutomaticSimulation(String name, int dataCollectionInterval, + int insertPoliceAfter, int amountOfSeeds, + int collectionTimes) { + window = new SimulationGUI(this); + + DataCollector collector = new DataCollector(this, name, + dataCollectionInterval, + insertPoliceAfter, + amountOfSeeds, + collectionTimes, + new boolean[10][10]); + timer = new Timer(TIMESTEP, collector); + timer.start(); + window.setVisible(true); + } + + /** + * Runs one timestep of the simulation. + */ + public void runOneTimestep() { + + // List of all the individuals in the matrix + List individuals = matrix.getIndividuals(); + + // Iterate over each individual + for (Individual individual : individuals) { + // Sector where the individual is before updating position + PositionMatrix.Sector initialSector = matrix.getSectorForCoords( + individual); + // Amount of neighbors participating with the radius rParticipating + int sumParticipating = 0; + // Forces acting upon individual + double[] F = {0.0, 0.0}; + // Sum of the neighbor velocities + double[] sumOverVelocities = {0.0, 0.0}; + // List of neighbors + List neighbors = matrix.getNeighborsFor(individual, + flockRadius); + // Position and velocity of individual + double[] position = individual.getPosition(); + double[] velocity = individual.getVelocity(); + double r0 = 2 * individual.radius; + + individual.dangerLevel = 0; + // Calculate the danger level + int numNeighbors = neighbors.size(); + + // We use the number of people in the neighbor list to represent + // density + if (enableDensity) { + if (numNeighbors > density2) + individual.dangerLevel = 3; + else if (numNeighbors > density1) + individual.dangerLevel = 2; + else if (numNeighbors > safeDensity) + individual.dangerLevel = 1; + else if (numNeighbors < safeDensity) + individual.dangerLevel = 0; + } + + // ================= CALCULATION OF THE FORCES ===================== + for (Individual neighbor : neighbors) { + // Make sure we are not using the individual him/herself + if (neighbor == individual) { + continue; + } + double[] positionNeighbor = neighbor.getPosition(); + double[] velocityNeighbor = neighbor.getVelocity(); + + double distance = individual.distanceTo(neighbor); + + sumParticipating += isNeighborParticipating(neighbor, + distance) ? 1 : 0; + + // Repulsive force + // We only use neighbors within a radius of 2 * r0 + + if (distance < 2 * individual.radius) { + F[0] += epsilon * 500 * (individual.x - neighbor.x) / + distance; + F[1] += epsilon * 500 * (individual.y - neighbor.y) / + distance; + } else if (distance < 2 * r0) { + F[0] += -epsilon * ((1 / (distance - 2 * r0)) * + (position[0] - positionNeighbor[0])) / distance; + F[1] += -epsilon * ((1 / (distance - 2 * r0)) * + (position[1] - positionNeighbor[1])) / distance; + } + + sumOverVelocities[0] += velocityNeighbor[0]; + sumOverVelocities[1] += velocityNeighbor[1]; + } + + // Calculate the norm of the force + double jointForce = norm(F); + + if (enableForce) { + if (jointForce > force2) + individual.dangerLevel += 3; + else if (jointForce > force1) + individual.dangerLevel += 2; + else if (jointForce > safeForce) + individual.dangerLevel += 1; + else if (jointForce < safeForce) + individual.dangerLevel += 0; + } + + // Adjust the danger level if only one of the two options is enabled + if (enableForce ^ enableDensity) { + individual.dangerLevel *= 2; + } + + // Decide if the individual is participating or not + if (sumParticipating >= minParticipatingNeighbors) { + individual.isParticipating = true; + } + + if (sumParticipating < minCirclePitSize) { + individual.isParticipating = false; + } + + if (matrix.isSectorMonitored( + matrix.getSectorForCoords(individual))) { + individual.isParticipating = false; + } + + // Set preferred speed accordingly + individual.preferredSpeed = + individual.isParticipating ? 30 : 5 * random.nextDouble(); + + // Propulsion + // Makes the individual want to travel at their preferred speed + double vi = individual.preferredSpeed; + F[0] += -mu * (norm(velocity) - vi) * velocity[0] / norm(velocity); + F[1] += -mu * (norm(velocity) - vi) * velocity[1] / norm(velocity); + + // Flocking + if (individual.isParticipating && + !(sumOverVelocities[0] == 0 && sumOverVelocities[1] == 0)) { + double norm = norm(sumOverVelocities); + F[0] += alpha * sumOverVelocities[0] / norm; + F[1] += alpha * sumOverVelocities[1] / norm; + } + + // Centripetal Force + if (individual.isParticipating) { + double distanceToCenter = individual.distanceTo(center); + + // Normalized vector from center to individual + double[] r = new double[]{center[0] - individual.x, + center[1] - individual.y}; + r[0] /= distanceToCenter; + r[1] /= distanceToCenter; + + F[0] += gamma * r[0]; + F[1] += gamma * r[1]; + } + + // Add noise + // TODO: Generate noise + + // Make sure that F does not become too large to fit into a double + double normOfF = norm(F); + + if (normOfF > Long.MAX_VALUE) { + F[0] /= normOfF * Long.MAX_VALUE; + F[1] /= normOfF * Long.MAX_VALUE; + } + + // ====================== CALCULATE TIMESTEP ====================== + // Using the leap-frog method to integrate the differential equation + // d^2y/dt^2 = rhs(y) + + // Shifted initial velocity for leap-frog + double[] v_temp = new double[2]; + v_temp[0] = velocity[0] + 0.5 * dt * F[0]; + v_temp[1] = velocity[1] + 0.5 * dt * F[1]; + + // New position of the individual + double newX = position[0] + dt * v_temp[0]; + double newY = position[1] + dt * v_temp[1]; + + // New velocity of the individual + double newVx = v_temp[0] + dt * F[0] / 2; + double newVy = v_temp[1] + dt * F[1] / 2; + + // Make sure individuals rebound off the edges of the space + if (newX < 0 || newX > maxX) { + newVx = -newVx; + F[0] = -F[0]; + } + if (newY < 0 || newY > maxY) { + newVy = -newVy; + F[1] = -F[1]; + } + + // Make sure they don't get stuck in an out-of-bounds area + if (newX >= 0 && newX <= maxX) + individual.x = newX; + if (newY >= 0 && newY <= maxY) + individual.y = newY; + + individual.vx = newVx; + individual.vy = newVy; + + // Add the individual to the correct sector + PositionMatrix.Sector newSector = matrix.getSectorForCoords( + individual); + if (!newSector.equals(initialSector)) { + matrix.removeAndAdd(individual, initialSector, newSector); + } + + // Set the force acting on this individual and the amount of + // neighbors so that the danger level can be assessed + individual.f = norm(F); + individual.density = neighbors.size(); + individual.continuousDangerLevel = + individual.f / 10000 + individual.density / 50; + } + } + + /** + * Writes a small Python/Numpy script to allow analysis of the current + * situation. + */ + public void exportData() { + try { + writer.close(); + writer = new PrintWriter("special" + fileCounter + ".py", "UTF-8"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (NullPointerException e) { + try { + writer = new PrintWriter("special" + fileCounter + ".py", + "UTF-8"); + } catch (FileNotFoundException e1) { + e1.printStackTrace(); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + } + } + try { + fileCounterWriter.close(); + fileCounterWriter = new PrintWriter("special_counter.py"); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + writer.println("from numpy import *"); + writer.print("x = array(["); + boolean firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print(individual.x); + else writer.print(", " + individual.x); + firstTime = false; + } + writer.println("])"); + writer.flush(); + + writer.print("y = array(["); + firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print(individual.y); + else writer.print(", " + individual.y); + firstTime = false; + } + writer.println("])"); + writer.flush(); + + writer.print("dangerLevel = array(["); + firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print(individual.dangerLevel); + else writer.print(", " + individual.dangerLevel); + firstTime = false; + } + writer.println("])"); + writer.flush(); + + writer.print("isParticipating = array(["); + firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print((individual.isParticipating ? 1 : 0)); + else writer.print(", " + (individual.isParticipating ? 1 : 0)); + firstTime = false; + } + writer.println("])"); + writer.flush(); + + writer.print("F = array(["); + firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print(individual.f); + else writer.print(", " + individual.f); + firstTime = false; + } + writer.println("])"); + writer.flush(); + + writer.print("density = array(["); + firstTime = true; + for (Individual individual : matrix.getIndividuals()) { + if (firstTime) writer.print(individual.density); + else writer.print(", " + individual.density); + firstTime = false; + } + writer.println("])"); + writer.flush(); + fileCounter++; + fileCounterWriter.println("n = " + fileCounter); + fileCounterWriter.flush(); + } + + // Calculates the norm of the given vector. + private double norm(double[] vector) { + return Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]); + } + + /** + * Gets the matrix associated with this object. + * + * @return The PositionMatrix object the individuals are stored + * in. + */ + public PositionMatrix getMatrix() { + return matrix; + } + + /** + * Gets the Timer that is running this simulation. + * + * @return + */ + public Timer getSimulationTimer() { + return timer; + } + + /** + * Sets which sectors are monitored by the police. + * + * @param sectors A comma-separated list of comma-separated pairs of numbers + * representing sectors monitored by the police + */ + public void setMonitoredSectors(int... sectors) { + matrix.setMonitoredSectors(sectors); + } + + /** + * Sets which sectors are monitored by the police. + * + * @param sectors A boolean[][] array containing + * true at position (i, j) if a policeman is in + * that sector + */ + public void setMonitoredSectors(boolean[][] sectors) { + matrix.isPoliceAtSector = sectors; + } +} diff --git a/java_code/src/SimulationGUI.java b/java_code/src/SimulationGUI.java new file mode 100644 index 000000000..9bc8446a2 --- /dev/null +++ b/java_code/src/SimulationGUI.java @@ -0,0 +1,55 @@ +import javax.swing.*; +import java.awt.*; + +public class SimulationGUI extends JFrame { + + private SimulationPanel simulationPanel; + private ControlPanel controlPanel; + private Simulation simulation; + + public SimulationGUI(Simulation simulation) { + this.simulation = simulation; + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + setTitle("Circle Pit Simulation"); + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setLayout(new BorderLayout()); + setResizable(false); + + initComponents(); + addComponents(); + pack(); + setLocationRelativeTo(null); + } + + private void initComponents() { + PositionMatrix matrix = simulation.getMatrix(); + simulationPanel = new SimulationPanel(500, 500, simulation, + matrix.width * matrix.sectorSize, + matrix.height * + matrix.sectorSize); + + controlPanel = new ControlPanel(simulation, simulationPanel); + } + + private void addComponents() { + add(simulationPanel, BorderLayout.CENTER); + add(controlPanel, BorderLayout.EAST); + } + + /** + * Resets the simulation panel. + */ + public void resetSimulationPanel() { + simulationPanel.setIndividuals(simulation.getMatrix().getIndividuals()); + } +} diff --git a/java_code/src/SimulationPanel.java b/java_code/src/SimulationPanel.java new file mode 100644 index 000000000..2697a1c7d --- /dev/null +++ b/java_code/src/SimulationPanel.java @@ -0,0 +1,166 @@ +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +/** + * Draws the matrix into a JPanel. + */ +public class SimulationPanel extends JPanel { + + public boolean shouldShowParticipants = true; + private java.util.List individuals; + private double xScalingFactor, yScalingFactor; + private Simulation simulation; + private BufferedImage image; + + public SimulationPanel(int width, int height, Simulation simulation, + int matrixWidth, int matrixHeight) { + this.individuals = simulation.getMatrix().getIndividuals(); + xScalingFactor = width / matrixWidth; + yScalingFactor = height / matrixHeight; + this.simulation = simulation; + try { + image = ImageIO.read(new File("policeman.png")); + } catch (IOException e) { + e.printStackTrace(); + } + this.setSize(new Dimension(width, height)); + this.setPreferredSize(new Dimension(width, height)); + this.setMinimumSize(new Dimension(width, height)); + this.setBackground(Color.WHITE); + addListeners(); + setFocusable(false); + } + + private void addListeners() { + // Add a policeman on mouse click + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + double x = e.getX() / xScalingFactor; + double y = e.getY() / yScalingFactor; + PositionMatrix.Sector sector = + simulation.getMatrix().getSectorForCoords(x, y); + simulation + .getMatrix().isPoliceAtSector[sector.row][sector.col] = + !(simulation + .getMatrix().isPoliceAtSector[sector + .row][sector.col]); + getParent().repaint(); + } + }); + } + + /** + * Sets the individuals list to the given parameter. + */ + public void setIndividuals(java.util.List individuals) { + this.individuals = individuals; + } + + // Increases the rendering quality of the simulation + private void increaseRenderingQuality(Graphics2D g2d) { + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_DITHERING, + RenderingHints.VALUE_DITHER_ENABLE); + g2d.setRenderingHint(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_ON); + g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, + RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, + RenderingHints.VALUE_COLOR_RENDER_QUALITY); + g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_PURE); + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + int sumDanger = 0; + Graphics2D graphics2D = (Graphics2D) g.create(); + increaseRenderingQuality(graphics2D); // Comment this out to + // potentially make animation + // smoother (if laggy) + + // Draw each individual + for (Individual individual : individuals) { + double[] coords = individual.getPosition(); + coords[0] *= xScalingFactor; + coords[1] *= yScalingFactor; + + // Set the color depending on the danger level of the individual + int dangerLevel = individual.dangerLevel; + if (dangerLevel == 0) { + graphics2D.setColor(new Color(204, 229, 255)); + } else if (dangerLevel == 1) { + graphics2D.setColor(new Color(154, 204, 255)); + } else if (dangerLevel == 2) { + graphics2D.setColor(new Color(102, 178, 255)); + } else if (dangerLevel == 3) { + sumDanger++; + graphics2D.setColor(new Color(51, 153, 255)); + } else if (dangerLevel == 4) { + sumDanger += 2; + graphics2D.setColor(new Color(0, 128, 255)); + } else if (dangerLevel == 5) { + sumDanger += 3; + graphics2D.setColor(new Color(0, 102, 204)); + } else if (dangerLevel == 6) { + sumDanger += 4; + graphics2D.setColor(new Color(255, 51, 51)); + } + + graphics2D.fill(new Ellipse2D.Double(coords[0], coords[1], + individual.radius * + xScalingFactor, + individual.radius * + yScalingFactor)); + + // Fill the individual with a white circle if not participating + if (shouldShowParticipants) { + if (!individual.isParticipating) { + graphics2D.setColor(Color.WHITE); + graphics2D.fill(new Ellipse2D.Double( + coords[0] + individual.radius * xScalingFactor / 4, + coords[1] + individual.radius * yScalingFactor / 4, + individual.radius * xScalingFactor / 2, + individual.radius * yScalingFactor / 2)); + + } + } + } + + // General Danger indicator + graphics2D.setColor(new Color(102, 102, 0)); + graphics2D.fill(new Rectangle2D.Double(0, 10, 13, sumDanger / 5)); + graphics2D.setColor(new Color(204, 204, 0)); + graphics2D.fill(new Rectangle2D.Double(1, 11, 11, sumDanger / 5 - 2)); + + // Drawing the policemen + boolean[][] monitoredSectors = simulation.getMatrix().isPoliceAtSector; + for (int i = 0; i < monitoredSectors.length; i++) { + for (int j = 0; j < monitoredSectors[i].length; j++) { + if (monitoredSectors[i][j]) { + graphics2D.drawImage(image, + (int) ((i * 10 + 4) * xScalingFactor), + (int) ((j * 10 + 4) * yScalingFactor), + (int) (3 * xScalingFactor), + (int) (3 * yScalingFactor), null); + } + } + } + g.dispose(); + } +} diff --git a/java_code/timeEvolution.py b/java_code/timeEvolution.py new file mode 100644 index 000000000..d6a2beb41 --- /dev/null +++ b/java_code/timeEvolution.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +from matplotlib.pylab import * +from numpy import * + +colors = array( + ['dodgerblue', 'darkorange', 'g', 'red', 'purple', 'brown', 'violet', + 'grey', 'y', 'lightskyblue', 'black', + 'magenta']) + + +def analyse_median_danger(test_set, i): + to_do = "from %s.counter import *" % test_set + + exec(to_do, globals()) + + median_danger_time_evolution = zeros((n - 5, m)) + + t = linspace(0, 1, n - 5) + title('Time Evolution of Average of Median Danger') + + for j in range(0, m): # iterating over different seeds + for k in range(0, n - 5): # iterating over time steps + filename = 'out_%s_%s' % (j, k) + exec("from %s.%s import *" % (test_set, filename), + globals()) # importing data for given seed and time steps + median_danger_time_evolution[k, j] += medianDanger + y = average(median_danger_time_evolution, axis=1) + ylabel('Median Danger') + ymax = max(y) + if (test_set == 'control'): + linewidth_ = 2 + else: + linewidth_ = 1.2 + plot(t, y / ymax, colors[i], linewidth=linewidth_, label=test_set) + + +def analyse_is_participating(test_set, i): + toDo = "from %s.counter import *" % test_set + + exec(toDo, globals()) + + is_participating_time_evolution = zeros(n - 5) + t = linspace(0, 1, n - 5) + ylim(0, 1) + title('Time Evolution of Participation Rate') + for j in range(0, m): # iterating over different seeds + for k in range(0, n - 5): # iterating over time steps + filename = 'out_%s_%s' % (j, k) + exec("from %s.%s import *" % (test_set, filename), + globals()) # importing data for given seed and time steps + is_participating_time_evolution[k] += sum(isParticipating) / ( + 500 * 50) + + if (test_set == 'control'): + linewidth_ = 2 + else: + linewidth_ = 1.2 + + plot(t, is_participating_time_evolution, colors[i], linewidth=linewidth_, + label=test_set) + ylabel('Participation Rate') + + +if __name__ == '__main__' and __package__ is None: + import sys, os.path as path + + # Make sure we can import the out.py files + sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) + + # isParticipating: iterate over the configs listed in configs.sim + with open("configs.sim") as f: + next(f) + figure(figsize=(7, 5)) + k = 0 + for test_set in f: + analyse_is_participating(test_set.strip(), k) + k += 1 + + legend(prop={'size': 8}, fancybox=True, ncol=3, loc=1) + xlabel('Time') + savefig('isParticipating_timeEvolution.png', dpi=600) + + # Median Danger: iterate over the configs listed in configs.sim + with open("configs.sim") as f: + next(f) + figure(figsize=(7, 5)) + k = 0 + for test_set in f: + analyse_median_danger(test_set.strip(), k) + k += 1 + + legend(prop={'size': 8}, fancybox=True, ncol=3, loc=1) + xlabel('Time') + savefig('medianDanger_timeEvolution.png', dpi=600) diff --git a/research_proposal.pdf b/research_proposal.pdf new file mode 100644 index 000000000..e972179af Binary files /dev/null and b/research_proposal.pdf differ