diff --git a/week09/improvement/boids.py b/week09/improvement/boids.py new file mode 100644 index 0000000..e1cba89 --- /dev/null +++ b/week09/improvement/boids.py @@ -0,0 +1,44 @@ +""" +A deliberately bad implementation of +[Boids](http://dl.acm.org/citation.cfm?doid=37401.37406) +for use as an exercise on refactoring. + +This code simulates the swarming behaviour of bird-like objects ("boids"). +""" + +from matplotlib import pyplot as plt +from matplotlib import animation + +import random + +boids_x=[random.uniform(-450,50.0) for x in range(50)] +boids_y=[random.uniform(300.0,600.0) for x in range(50)] +boid_x_velocities=[random.uniform(0,10.0) for x in range(50)] +boid_y_velocities=[random.uniform(-20.0,20.0) for x in range(50)] +boids=(boids_x,boids_y,boid_x_velocities,boid_y_velocities) + +def update_boids(boids): + xs,ys,xvs,yvs=boids + # Fly towards the middle + for i in range(len(xs)): + for j in range(len(xs)): + xvs[i]=xvs[i]+(xs[j]-xs[i])*0.01/len(xs) + for i in range(len(xs)): + for j in range(len(xs)): + yvs[i]=yvs[i]+(ys[j]-ys[i])*0.01/len(xs) + # Fly away from nearby boids + for i in range(len(xs)): + for j in range(len(xs)): + if (xs[j]-xs[i])**2 + (ys[j]-ys[i])**2 < 100: + xvs[i]=xvs[i]+(xs[i]-xs[j]) + yvs[i]=yvs[i]+(ys[i]-ys[j]) + # Try to match speed with nearby boids + for i in range(len(xs)): + for j in range(len(xs)): + if (xs[j]-xs[i])**2 + (ys[j]-ys[i])**2 < 10000: + xvs[i]=xvs[i]+(xvs[j]-xvs[i])*0.125/len(xs) + yvs[i]=yvs[i]+(yvs[j]-yvs[i])*0.125/len(xs) + # Move according to velocities + for i in range(len(xs)): + xs[i]=xs[i]+xvs[i] + ys[i]=ys[i]+yvs[i] \ No newline at end of file diff --git a/week09/improvement/clustering.py b/week09/improvement/clustering.py new file mode 100644 index 0000000..d6359ea --- /dev/null +++ b/week09/improvement/clustering.py @@ -0,0 +1,32 @@ +"""This code implements the k-means clustering algorithm for 2 dimensions.""" + +from math import * +from random import * + +k=3 + +lines = open('samples.csv', 'r').readlines() +ps=[] +for line in lines: ps.append(tuple(map(float, line.strip().split(',')))) + +m=[ps[randrange(len(ps))], ps[randrange(len(ps))], ps[randrange(len(ps))]] + +alloc=[None]*len(ps) +n=0 +while n<10: + for i in range(len(ps)): + p=ps[i] + d=[None] * 3 + d[0]=sqrt((p[0]-m[0][0])**2 + (p[1]-m[0][1])**2) + d[1]=sqrt((p[0]-m[1][0])**2 + (p[1]-m[1][1])**2) + d[2]=sqrt((p[0]-m[2][0])**2 + (p[1]-m[2][1])**2) + alloc[i]=d.index(min(d)) + for i in range(3): + alloc_ps=[p for j, p in enumerate(ps) if alloc[j] == i] + new_mean=(sum([a[0] for a in alloc_ps]) / len(alloc_ps), sum([a[1] for a in alloc_ps]) / len(alloc_ps)) + m[i]=new_mean + n=n+1 + +for i in range(3): + alloc_ps=[p for j, p in enumerate(ps) if alloc[j] == i] + print("Cluster " + str(i) + " is centred at " + str(m[i]) + " and has " + str(len(alloc_ps)) + " points.") \ No newline at end of file diff --git a/week09/improvement/trees.py b/week09/improvement/trees.py new file mode 100644 index 0000000..dfa03f3 --- /dev/null +++ b/week09/improvement/trees.py @@ -0,0 +1,17 @@ +"""This code produces a tree-like plot.""" + +from math import sin, cos +from matplotlib import pyplot as plt +s=1 +d=[[0,1,0]] +plt.plot([0,0],[0,1]) +for i in range(5): + n=[] + for j in range(len(d)): + n.append([d[j][0]+s*sin(d[j][2]-0.1), d[j][1]+s*cos(d[j][2]-0.1), d[j][2]-0.1]) + n.append([d[j][0]+s*sin(d[j][2]+0.1), d[j][1]+s*cos(d[j][2]+0.1), d[j][2]+0.1]) + plt.plot([d[j][0], n[-2][0]],[d[j][1], n[-2][1]]) + plt.plot([d[j][0], n[-1][0]],[d[j][1], n[-1][1]]) + d=n + s*=0.6 +plt.savefig('tree.png') \ No newline at end of file diff --git a/week09/refactoring/initial_global.py b/week09/refactoring/initial_global.py new file mode 100644 index 0000000..21f1323 --- /dev/null +++ b/week09/refactoring/initial_global.py @@ -0,0 +1,61 @@ +def average_age(): + """Compute the average age of the group's members.""" + all_ages = [person["age"] for person in group.values()] + return sum(all_ages) / len(group) + + +def forget(person1, person2): + """Remove the connection between two people.""" + group[person1]["relations"].pop(person2, None) + group[person2]["relations"].pop(person1, None) + + +def add_person(name, age, job, relations): + """Add a new person with the given characteristics to the group.""" + new_person = { + "age": age, + "job": job, + "relations": relations + } + group[name] = new_person + + +group = { + "Jill": { + "age": 26, + "job": "biologist", + "relations": { + "Zalika": "friend", + "John": "partner" + } + }, + "Zalika": { + "age": 28, + "job": "artist", + "relations": { + "Jill": "friend", + } + }, + "John": { + "age": 27, + "job": "writer", + "relations": { + "Jill": "partner" + } + } +} + +nash_relations = { + "John": "cousin", + "Zalika": "landlord" +} + +add_person("Nash", 34, "chef", nash_relations) + +forget("Nash", "John") + +if __name__ == "__main__": + assert len(group) == 4, "Group should have 4 members" + assert average_age() == 28.75, "Average age of the group is incorrect!" + assert len(group["Nash"]["relations"]) == 1, "Nash should only have one relation" + print("All assertions have passed!") diff --git a/week09/refactoring/initial_person_class.py b/week09/refactoring/initial_person_class.py new file mode 100644 index 0000000..569d26d --- /dev/null +++ b/week09/refactoring/initial_person_class.py @@ -0,0 +1,44 @@ +class Person: + """A class to represent an individual and their connections.""" + + def __init__(self, name, age, job): + """Create a new Person with the given name, age and job and no connections.""" + self.name = name + self.age = age + self.job = job + self.connections = dict() + + def add_connection(self, person, relation): + """Add a new connection to a person""" + if person in self.connections: + raise ValueError(f"I already know about {person.name}") + self.connections[person] = relation + + def forget(self, person): + """Removes any connections to a person""" + pass + + +def average_age(group): + """Compute the average age of the group's members.""" + all_ages = [person.age for person in group] + return sum(all_ages) / len(group) + + +if __name__ == "__main__": + # ...then create the group members one by one... + jill = Person("Jill", 26, "biologist") + + # ...then add the connections one by one... + # Note: this will fail from here if the person objects aren't created + jill.add_connection(zalika, "friend") + + # ... then forget Nash and John's connection + nash.forget(john) + # Then create the group + my_group = {jill, zalika, john, nash} + + assert len(my_group) == 4, "Group should have 4 members" + assert average_age(my_group) == 28.75, "Average age of the group is incorrect!" + assert len(nash.connections) == 1, "Nash should only have one relation " + print("All assertions have passed!") diff --git a/week09/refactoring/initial_two_classes.py b/week09/refactoring/initial_two_classes.py new file mode 100644 index 0000000..a54d1b3 --- /dev/null +++ b/week09/refactoring/initial_two_classes.py @@ -0,0 +1,66 @@ +class Person: + """A class to represent an individual.""" + + def __init__(self, name, age, job): + """Create a new Person with the given name, age and job.""" + self.name = name + self.age = age + self.job = job + + +class Group: + """A class that represents a group of individuals and their connections.""" + + def __init__(self): + """Create an empty group.""" + self.members = [] + self.connections = {} + + def size(self): + """Return how many people are in the group.""" + pass + + def contains(self, name): + """Check whether the group contains a person with the given name. + Useful to throw errors if we try to add a person who already exists or forget someone. + """ + return any(member.name == name for member in self.members) + + def add_person(self, name, age, job): + """Add a new person with the given characteristics to the group.""" + self.members.append(Person(name, age, job)) + + def number_of_connections(self, name): + """Find the number of connections that a person in the group has""" + pass + + def connect(self, name1, name2, relation, reciprocal=True): + """Connect two given people in a particular way. + Optional reciprocal: If true, will add the relationship from name2 to name 1 as well + """ + pass + + def forget(self, name1, name2): + """Remove the connection between two people.""" + pass + + def average_age(self): + """Compute the average age of the group's members.""" + all_ages = [person.age for person in self.members] + return sum(all_ages) / self.size() + + +if __name__ == "__main__": + # Start with an empty group... + my_group = Group() + # ...then add the group members one by one... + my_group.add_person("Jill", 26, "biologist") + # ...then their connections + my_group.connect("Jill", "Zalika", "friend") + # ... then forget Nash and John's connection + my_group.forget("Nash", "John") + + assert my_group.size() == 4, "Group should have 4 members" + assert my_group.average_age() == 28.75, "Average age of the group is incorrect!" + assert my_group.number_of_connections("Nash") == 1, "Nash should only have one relation" + print("All assertions have passed!") diff --git a/week09/versions/version1.py b/week09/versions/version1.py new file mode 100644 index 0000000..73d0b03 --- /dev/null +++ b/week09/versions/version1.py @@ -0,0 +1,11 @@ +from visualisation import * +from processing import * + +fs = ['81500.tab', '48151.tab', '62342.tab'] +R=[] +R.append(analyse(preprocess(load(fs[0]),0.3))) +R.append(analyse(preprocess(load(fs[1]),0.3))) +R.append(analyse(preprocess(load(fs[2]),0.3))) + +for i in range(3): + save_figure(fu_map(R[i])) diff --git a/week09/versions/version2.py b/week09/versions/version2.py new file mode 100644 index 0000000..c1fa39a --- /dev/null +++ b/week09/versions/version2.py @@ -0,0 +1,30 @@ +'''Analysis script for some DSEA measurements.''' + +import os + +from processing import analyse, is_valid, load, preprocess +from visualisation import fu_map, save_figure + + +def extract_mean(data_file): + """Load a dataset, smooth it and run the analysis algorithm on it. + + :param data_file: input file containing raw data from an experiment. + """ + assert os.path.exists(data_file), f"File not found: {data_file}" + assert is_valid(data_file), f"File {data_file} not in DSEA format." + data = load(data_file) + # This smoothing is necessary to avoid singular behaviour, see Kim (2012) + scaling = 0.3 # scaling factor to apply, from Reyes & Pace (2004) + smoothed_data = preprocess(data, scaling) + return analyse(smoothed_data) + + +if __name__ == "__main__": + # The files from my last 3 experiments. + # Note that we can't run this on the data from the MT-3 because the + # files lack the header! + raw_files = ['81500.tab', '48151.tab', '62342.tab'] + results = [extract_mean(input_file) for input_file in raw_files] + for result in results: + save_figure(fu_map(result))