naohiro701 commited on
Commit
306ec86
·
verified ·
1 Parent(s): 0535c91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +326 -0
app.py CHANGED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # boids_streamlit_advanced.py
2
+
3
+ import streamlit as st
4
+ import numpy as np
5
+ import plotly.graph_objects as go
6
+ import time
7
+
8
+ # Define the Boid class
9
+ class Boid:
10
+ def __init__(self, position, velocity):
11
+ self.position = np.array(position, dtype='float64')
12
+ self.velocity = np.array(velocity, dtype='float64')
13
+ self.acceleration = np.zeros(2, dtype='float64')
14
+ self.history = []
15
+
16
+ def update(self, boids, width, height, params):
17
+ self.flock(boids, params)
18
+ self.velocity += self.acceleration
19
+ speed = np.linalg.norm(self.velocity)
20
+ if speed > params['max_speed']:
21
+ self.velocity = (self.velocity / speed) * params['max_speed']
22
+ self.position += self.velocity
23
+ self.acceleration = np.zeros(2, dtype='float64')
24
+
25
+ # Screen wrapping
26
+ self.position[0] = self.position[0] % width
27
+ self.position[1] = self.position[1] % height
28
+
29
+ # Add current position to history
30
+ if params['draw_trail']:
31
+ self.history.append(self.position.copy())
32
+ if len(self.history) > params['max_history']:
33
+ self.history.pop(0)
34
+
35
+ def flock(self, boids, params):
36
+ self.acceleration = np.zeros(2, dtype='float64')
37
+ self.fly_towards_center(boids, params)
38
+ self.avoid_others(boids, params)
39
+ self.match_velocity(boids, params)
40
+ self.limit_speed(params)
41
+ self.avoid_boundaries(width=params['width'], height=params['height'], params=params)
42
+
43
+ def fly_towards_center(self, boids, params):
44
+ centering_factor = params['centering_factor']
45
+ center_x = 0.0
46
+ center_y = 0.0
47
+ num_neighbors = 0
48
+
49
+ for other in boids:
50
+ if other is not self and self.distance(other) < params['visual_range']:
51
+ center_x += other.position[0]
52
+ center_y += other.position[1]
53
+ num_neighbors += 1
54
+
55
+ if num_neighbors > 0:
56
+ center_x /= num_neighbors
57
+ center_y /= num_neighbors
58
+ direction = np.array([center_x, center_y]) - self.position
59
+ self.acceleration += centering_factor * direction
60
+
61
+ def avoid_others(self, boids, params):
62
+ min_distance = params['min_distance']
63
+ avoid_factor = params['avoid_factor']
64
+ move = np.zeros(2, dtype='float64')
65
+
66
+ for other in boids:
67
+ if other is not self and self.distance(other) < min_distance:
68
+ move += self.position - other.position
69
+
70
+ if np.linalg.norm(move) > 0:
71
+ move = move / np.linalg.norm(move) * avoid_factor
72
+ self.acceleration += move
73
+
74
+ def match_velocity(self, boids, params):
75
+ matching_factor = params['matching_factor']
76
+ avg_velocity = np.zeros(2, dtype='float64')
77
+ num_neighbors = 0
78
+
79
+ for other in boids:
80
+ if other is not self and self.distance(other) < params['visual_range']:
81
+ avg_velocity += other.velocity
82
+ num_neighbors += 1
83
+
84
+ if num_neighbors > 0:
85
+ avg_velocity /= num_neighbors
86
+ self.acceleration += matching_factor * (avg_velocity - self.velocity)
87
+
88
+ def limit_speed(self, params):
89
+ speed = np.linalg.norm(self.velocity)
90
+ if speed > params['max_speed']:
91
+ self.velocity = (self.velocity / speed) * params['max_speed']
92
+
93
+ def avoid_boundaries(self, width, height, params):
94
+ margin = params['boundary_margin']
95
+ turn_factor = params['boundary_turn_factor']
96
+
97
+ if self.position[0] < margin:
98
+ self.acceleration[0] += turn_factor
99
+ elif self.position[0] > width - margin:
100
+ self.acceleration[0] -= turn_factor
101
+
102
+ if self.position[1] < margin:
103
+ self.acceleration[1] += turn_factor
104
+ elif self.position[1] > height - margin:
105
+ self.acceleration[1] -= turn_factor
106
+
107
+ def distance(self, other):
108
+ return np.linalg.norm(self.position - other.position)
109
+
110
+ # Simulation parameters
111
+ params = {
112
+ 'num_boids': 100,
113
+ 'visual_range': 75.0,
114
+ 'min_distance': 20.0,
115
+ 'centering_factor': 0.005,
116
+ 'avoid_factor': 0.05,
117
+ 'matching_factor': 0.05,
118
+ 'max_speed': 15.0,
119
+ 'draw_trail': False,
120
+ 'max_history': 50,
121
+ 'width': 800,
122
+ 'height': 600,
123
+ 'boundary_margin': 100.0,
124
+ 'boundary_turn_factor': 0.05
125
+ }
126
+
127
+ # Streamlit sidebar for parameter adjustments
128
+ st.sidebar.title("Boids Simulation Parameters")
129
+ params['num_boids'] = st.sidebar.slider("Number of Boids", 10, 300, 100)
130
+ params['visual_range'] = st.sidebar.slider("Visual Range", 10.0, 200.0, 75.0)
131
+ params['min_distance'] = st.sidebar.slider("Minimum Separation Distance", 5.0, 100.0, 20.0)
132
+ params['centering_factor'] = st.sidebar.slider("Centering Factor", 0.001, 0.02, 0.005)
133
+ params['avoid_factor'] = st.sidebar.slider("Avoidance Factor", 0.01, 0.1, 0.05)
134
+ params['matching_factor'] = st.sidebar.slider("Matching Factor", 0.01, 0.1, 0.05)
135
+ params['max_speed'] = st.sidebar.slider("Maximum Speed", 5.0, 30.0, 15.0)
136
+ params['draw_trail'] = st.sidebar.checkbox("Draw Trails")
137
+ if params['draw_trail']:
138
+ params['max_history'] = st.sidebar.slider("Trail Length", 10, 100, 50)
139
+ params['boundary_margin'] = st.sidebar.slider("Boundary Margin", 50.0, 300.0, 100.0)
140
+ params['boundary_turn_factor'] = st.sidebar.slider("Boundary Turn Factor", 0.01, 0.2, 0.05)
141
+
142
+ # Simulation screen size
143
+ width, height = 800, 600
144
+ params['width'] = width
145
+ params['height'] = height
146
+
147
+ # Initialize Boids
148
+ boids = []
149
+ for _ in range(params['num_boids']):
150
+ position = [np.random.uniform(0, width), np.random.uniform(0, height)]
151
+ angle = np.random.uniform(0, 2 * np.pi)
152
+ velocity = [np.cos(angle), np.sin(angle)]
153
+ boids.append(Boid(position, velocity))
154
+
155
+ # Plotly graph setup
156
+ fig = go.Figure(
157
+ layout=go.Layout(
158
+ xaxis=dict(range=[0, width], autorange=False, showgrid=False, zeroline=False),
159
+ yaxis=dict(range=[0, height], autorange=False, showgrid=False, zeroline=False),
160
+ width=width,
161
+ height=height,
162
+ margin=dict(l=0, r=0, t=0, b=0)
163
+ )
164
+ )
165
+
166
+ # Plot initial positions of Boids
167
+ scatter = go.Scatter(
168
+ x=[boid.position[0] for boid in boids],
169
+ y=[boid.position[1] for boid in boids],
170
+ mode='markers',
171
+ marker=dict(size=8, color='blue')
172
+ )
173
+
174
+ fig.add_trace(scatter)
175
+
176
+ # Trail trace
177
+ if params['draw_trail']:
178
+ trail_scatter = go.Scatter(
179
+ x=[],
180
+ y=[],
181
+ mode='lines',
182
+ line=dict(color='rgba(0,0,255,0.2)', width=1),
183
+ showlegend=False
184
+ )
185
+ fig.add_trace(trail_scatter)
186
+
187
+ # Simplified Title
188
+ st.title("Boids Simulation")
189
+
190
+ # Animation display area
191
+ animation_placeholder = st.empty()
192
+
193
+ # Explanation Section Title
194
+ st.header("Mathematical Background of the Boids Algorithm")
195
+
196
+ # Explanation Section
197
+ st.markdown("### **Overview of the Boids Algorithm**")
198
+ st.markdown("""
199
+ The Boids algorithm, proposed by Craig Reynolds in 1986, is a method for simulating flocking behavior in groups of agents called Boids. Each agent follows simple rules to recreate complex group dynamics. The three fundamental rules are:
200
+
201
+ 1. **Separation**: Maintain a suitable distance from nearby Boids to avoid collisions.
202
+ 2. **Alignment**: Align velocity with the average velocity of neighboring Boids.
203
+ 3. **Cohesion**: Move towards the average position of neighboring Boids.
204
+ """)
205
+
206
+ st.markdown("### **Mathematical Model**")
207
+ st.markdown("""
208
+ The movement of each Boid is represented by its position vector \(\mathbf{p}_i(t)\) and velocity vector \(\mathbf{v}_i(t)\). The position and velocity of Boid \(i\) at time \(t\) are described by the following differential equations:
209
+ """)
210
+
211
+ st.latex(r"""
212
+ \frac{d\mathbf{p}_i(t)}{dt} = \mathbf{v}_i(t)
213
+ """)
214
+ st.latex(r"""
215
+ \frac{d\mathbf{v}_i(t)}{dt} = \mathbf{a}_i(t)
216
+ """)
217
+
218
+ st.markdown("""
219
+ Here, the acceleration \(\mathbf{a}_i(t)\) is the sum of three forces:
220
+ """)
221
+ st.latex(r"""
222
+ \mathbf{a}_i(t) = \mathbf{a}_{\text{separation}} + \mathbf{a}_{\text{alignment}} + \mathbf{a}_{\text{cohesion}}
223
+ """)
224
+
225
+ st.markdown("#### **1. Separation**")
226
+ st.markdown("""
227
+ To prevent collisions, the separation force is calculated based on the distance \(d_{ij}\) between Boid \(i\) and its neighboring Boids \(j\):
228
+ """)
229
+ st.latex(r"""
230
+ \mathbf{a}_{\text{separation}} = \sum_{j \in N(i)} \frac{\mathbf{p}_i - \mathbf{p}_j}{d_{ij}^2}
231
+ """)
232
+ st.markdown("where \(N(i)\) is the set of neighboring Boids around Boid \(i\).")
233
+
234
+ st.markdown("#### **2. Alignment**")
235
+ st.markdown("""
236
+ The alignment force encourages Boid \(i\) to match the average velocity \(\mathbf{v}_{\text{avg}}\) of its neighbors:
237
+ """)
238
+ st.latex(r"""
239
+ \mathbf{a}_{\text{alignment}} = \frac{\mathbf{v}_{\text{avg}} - \mathbf{v}_i}{\tau}
240
+ """)
241
+ st.markdown("where \(\tau\) is a scaling parameter.")
242
+
243
+ st.markdown("#### **3. Cohesion**")
244
+ st.markdown("""
245
+ The cohesion force steers Boid \(i\) towards the average position \(\mathbf{C}_{\text{avg}}\) of its neighbors:
246
+ """)
247
+ st.latex(r"""
248
+ \mathbf{a}_{\text{cohesion}} = \frac{\mathbf{C}_{\text{avg}} - \mathbf{p}_i}{\sigma}
249
+ """)
250
+ st.markdown("where \(\sigma\) is a scaling parameter.")
251
+
252
+ st.markdown("### **Update Rules**")
253
+ st.markdown("""
254
+ Each Boid's position and velocity are updated based on discrete time steps \(\Delta t\) as follows:
255
+ """)
256
+ st.latex(r"""
257
+ \mathbf{v}_i(t + \Delta t) = \mathbf{v}_i(t) + \mathbf{a}_i(t) \Delta t
258
+ """)
259
+ st.latex(r"""
260
+ \mathbf{p}_i(t + \Delta t) = \mathbf{p}_i(t) + \mathbf{v}_i(t + \Delta t) \Delta t
261
+ """)
262
+ st.markdown("""
263
+ These equations ensure that each Boid updates its velocity and position based on the combined separation, alignment, and cohesion forces.
264
+ """)
265
+
266
+ st.markdown("### **Additional Features**")
267
+ st.markdown("""
268
+ This simulation includes the following additional features:
269
+
270
+ 1. **Boundary Avoidance**: When a Boid approaches the edge of the simulation area, it receives a steering force to remain within bounds, preventing it from moving off-screen.
271
+ 2. **Trail Drawing**: The past positions of each Boid are displayed as trails, allowing visualization of their movement patterns.
272
+ """)
273
+
274
+ st.markdown("### **Parameters and Their Roles**")
275
+ st.markdown("""
276
+ - **Boundary Margin (\(M\))**: The distance from the edge of the simulation area at which Boids begin to steer away.
277
+ - **Boundary Turn Factor (\(\gamma\))**: The strength of the steering force applied when avoiding boundaries.
278
+
279
+ These parameters allow fine-tuning of Boid behavior near the edges of the simulation area.
280
+ """)
281
+
282
+ # Animation settings
283
+ frame_rate = 30 # Frames per second
284
+ sleep_time = 1.0 / frame_rate
285
+
286
+ # Reset button
287
+ if st.sidebar.button("Reset Simulation"):
288
+ boids = []
289
+ for _ in range(params['num_boids']):
290
+ position = [np.random.uniform(0, width), np.random.uniform(0, height)]
291
+ angle = np.random.uniform(0, 2 * np.pi)
292
+ velocity = [np.cos(angle), np.sin(angle)]
293
+ boids.append(Boid(position, velocity))
294
+
295
+ # Animation loop
296
+ while True:
297
+ # Update Boids
298
+ for boid in boids:
299
+ boid.update(boids, width, height, params)
300
+
301
+ # Update Boids' positions
302
+ scatter.x = [boid.position[0] for boid in boids]
303
+ scatter.y = [boid.position[1] for boid in boids]
304
+
305
+ # Update trails
306
+ if params['draw_trail']:
307
+ trail_x = []
308
+ trail_y = []
309
+ for boid in boids:
310
+ trail_x.extend([pos[0] for pos in boid.history])
311
+ trail_y.extend([pos[1] for pos in boid.history])
312
+ trail_scatter.x = trail_x
313
+ trail_scatter.y = trail_y
314
+
315
+ # Update the Plotly figure
316
+ fig.data[0].x = scatter.x
317
+ fig.data[0].y = scatter.y
318
+ if params['draw_trail']:
319
+ fig.data[1].x = trail_scatter.x
320
+ fig.data[1].y = trail_scatter.y
321
+
322
+ # Display the animation
323
+ animation_placeholder.plotly_chart(fig, use_container_width=True)
324
+
325
+ # Control the frame rate
326
+ time.sleep(sleep_time)