Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

📚 The CoCalc Library - books, templates and other resources

132922 views
License: OTHER
1
""" Code example from Complexity and Computation, a book about
2
exploring complexity science with Python. Available free from
3
4
http://greenteapress.com/complexity
5
6
Original code by Matt Aasted, modified by Allen Downey.
7
8
Based on Reynolds, "Flocks, Herds and Schools" and
9
Flake, "The Computational Beauty of Nature."
10
11
Copyright 2011 Allen B. Downey.
12
Distributed under the GNU General Public License at gnu.org/licenses/gpl.html.
13
"""
14
15
import visual
16
import numpy as np
17
18
# size of the boids
19
b_radius = 0.03
20
b_length = 0.1
21
22
# radii for sensing different rules
23
r_avoid = 0.3
24
r_center = 1.0
25
r_copy = 0.5
26
27
# viewing angle for different rules, in radians
28
a_avoid = 2*np.pi
29
a_center = 2
30
a_copy = 2
31
32
# weights for various rules
33
w_avoid = 4
34
w_center = 3
35
w_copy = 2
36
w_love = 10
37
38
# time step
39
dt = 0.1
40
41
42
def random_vector(a, b):
43
"""Create a vector with each element uniformly distributed in [a, b)."""
44
t = [np.random.uniform(a,b) for i in range(3)]
45
return visual.vector(t)
46
47
48
def limit_vector(vect):
49
"""if the magnitude is greater than 1, set it to 1"""
50
if vect.mag > 1:
51
vect.mag = 1
52
return vect
53
54
55
null_vector = visual.vector(0,0,0)
56
57
58
class Boid(visual.cone):
59
"""A Boid is a Visual cone with a velocity"""
60
61
def __init__(self, radius=b_radius, length=b_length):
62
pos = random_vector(0, 1)
63
self.vel = random_vector(0, 1).norm()
64
visual.cone.__init__(self, pos=pos, radius=radius)
65
self.axis = length * self.vel.norm()
66
67
def get_neighbors(self, others, radius, angle):
68
"""Return the list of neighbors within the given radius and angle."""
69
boids = []
70
for other in others:
71
if other is self: continue
72
offset = other.pos - self.pos
73
74
# if not in range, skip it
75
if offset.mag > radius:
76
continue
77
78
# if not within viewing angle, skip it
79
if self.vel.diff_angle(offset) > angle:
80
continue
81
82
# otherwise add it to the list
83
boids.append(other)
84
85
return boids
86
87
def avoid(self, others, carrot):
88
"""Find the center of mass of all objects in range and
89
returns a vector in the opposite direction, with magnitude
90
proportional to the inverse of the distance (up to a limit)."""
91
others = others + [carrot]
92
close = self.get_neighbors(others, r_avoid, a_avoid)
93
t = [other.pos for other in close]
94
if t:
95
center = np.sum(t)/len(t)
96
away = visual.vector(self.pos - center)
97
away.mag = r_avoid / away.mag
98
return limit_vector(away)
99
else:
100
return null_vector
101
102
def center(self, others):
103
"""Find the center of mass of other boids in range and
104
returns a vector pointing toward it."""
105
close = self.get_neighbors(others, r_center, a_center)
106
t = [other.pos for other in close]
107
if t:
108
center = np.sum(t)/len(t)
109
toward = visual.vector(center - self.pos)
110
return limit_vector(toward)
111
else:
112
return null_vector
113
114
def copy(self, others):
115
"""Return the average heading of other boids in range.
116
117
others: list of Boids
118
"""
119
close = self.get_neighbors(others, r_copy, a_copy)
120
t = [other.vel for other in close]
121
if t:
122
# TODO: replace this with mean
123
center = np.sum(t)/len(t)
124
away = visual.vector(self.pos - center)
125
return limit_vector(away)
126
else:
127
return null_vector
128
129
def love(self, carrot):
130
"""Returns a vector pointing toward the carrot."""
131
toward = carrot.pos - self.pos
132
return limit_vector(toward)
133
134
def set_goal(self, boids, carrot):
135
"""Sets the goal to be the weighted sum of the goal vectors."""
136
self.goal = (w_avoid * self.avoid(boids, carrot) +
137
w_center * self.center(boids) +
138
w_copy * self.copy(boids) +
139
w_love * self.love(carrot))
140
self.goal.mag = 1
141
142
def move(self, mu=0.1):
143
"""Update the velocity, position and axis vectors.
144
mu controls how fast the boids can turn (maneuverability)."""
145
146
self.vel = (1-mu) * self.vel + mu * self.goal
147
self.vel.mag = 1
148
149
self.pos += dt * self.vel
150
self.axis = b_length * self.vel.norm()
151
152
153
class World(object):
154
def __init__(self, n=10):
155
"""Create n Boids and one carrot.
156
157
tracking: indicates whether the carrot follows the mouse
158
"""
159
self.boids = [Boid() for i in range(n)]
160
self.carrot = visual.sphere(pos=(1,0,0), radius=0.1, color=(1,0,0))
161
self.tracking = False
162
163
def step(self):
164
"""Compute one time step."""
165
# move the boids
166
for boid in self.boids:
167
boid.set_goal(self.boids, self.carrot)
168
boid.move()
169
170
# mouse click toggles tracking
171
if scene.mouse.clicked:
172
scene.mouse.getclick()
173
self.tracking = not self.tracking
174
175
# if we're tracking, move the carrot
176
if self.tracking:
177
self.carrot.pos = scene.mouse.pos
178
179
180
def main(script, n=20):
181
global scene
182
183
n = int(n)
184
size = 5
185
scene = visual.display(title='Boids', width=800, height=600,
186
range=(size, size, size))
187
world = World(n)
188
scene.center = world.carrot.pos
189
scene.autoscale = False
190
191
while 1:
192
# update the screen once per time step
193
visual.rate(1/dt)
194
world.step()
195
196
197
if __name__ == '__main__':
198
import sys
199
main(*sys.argv)
200
201