Skip to content

Commit fbafcfa

Browse files
committed
simulation: update bee physics to allow for variable time steps, and use that in StoneExperiment
1 parent 4cbbcb6 commit fbafcfa

File tree

5 files changed

+78
-56
lines changed

5 files changed

+78
-56
lines changed

lib/pim/models/new/stone/__init__.py

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,23 +45,26 @@ def serialize(self):
4545
#"cpu4_snapshot": self.cpu4_snapshot,
4646
}
4747

48+
def reconstruct_path(self):
49+
position = np.zeros(2)
50+
positions = [position]
51+
for velocity in self.velocities:
52+
position = position + velocity
53+
positions.append(position)
54+
return positions
55+
56+
def closest_position(self):
57+
path = self.reconstruct_path()
58+
return min(path[self.parameters["T_outbound"]:], key = np.linalg.norm)
59+
4860
class StoneExperiment(Experiment):
4961
def __init__(self, parameters: dict) -> None:
5062
super().__init__()
5163
self.parameters = parameters
5264

53-
def run(self, name: str) -> ExperimentResults:
54-
# extract some parameters
55-
T_outbound = self.parameters["T_outbound"]
56-
T_inbound = self.parameters["T_inbound"]
5765
noise = self.parameters["noise"]
5866
cx_type = self.parameters["cx"]
5967

60-
logger.info(f"generating outbound route")
61-
headings, velocities = trials.generate_route(T = T_outbound, vary_speed = True)
62-
63-
logger.info("initializing central complex")
64-
6568
if cx_type == "basic":
6669
cx = basic.CXBasic()
6770
elif cx_type == "rate":
@@ -71,7 +74,23 @@ def run(self, name: str) -> ExperimentResults:
7174
else:
7275
raise RuntimeError("unknown cx type: " + cx_type)
7376

74-
cx.setup()
77+
self.cx = cx
78+
self.cx.setup()
79+
80+
def run(self, name: str) -> ExperimentResults:
81+
# extract some parameters
82+
T_outbound = self.parameters["T_outbound"]
83+
T_inbound = self.parameters["T_inbound"]
84+
noise = self.parameters["noise"]
85+
cx_type = self.parameters["cx"]
86+
time_subdivision = self.parameters["time_subdivision"] if "time_subdivision" in self.parameters else 1
87+
88+
logger.info(f"generating outbound route")
89+
headings, velocities = trials.generate_route(T = T_outbound, vary_speed = True)
90+
#headings = np.repeat(headings, time_subdivision)
91+
#headings = np.repeat(headings, time_subdivision)
92+
93+
logger.info("initializing central complex")
7594

7695
headings = np.zeros(T_outbound + T_inbound)
7796
velocities = np.zeros((T_outbound + T_inbound, 2))
@@ -84,23 +103,29 @@ def run(self, name: str) -> ExperimentResults:
84103
)
85104

86105
logger.info("simulating outbound path")
106+
dt = 1.0 / time_subdivision
87107
for heading, velocity in zip(headings[0:T_outbound], velocities[0:T_outbound, :]):
88-
cx.update(1.0, heading, velocity)
108+
for ts in range(time_subdivision):
109+
self.cx.update(dt, heading, velocity)
89110

90111
for t in range(T_outbound, T_outbound + T_inbound):
91112
heading = headings[t-1]
92113
velocity = velocities[t-1,:]
93114

94-
motor = cx.update(1.0, heading, velocity)
95-
rotation = motor
115+
for ts in range(time_subdivision):
116+
motor = self.cx.update(dt, heading, velocity)
117+
rotation = motor
96118

97-
headings[t], velocities[t,:] = bee_simulator.get_next_state(
98-
velocity=velocity,
99-
heading=heading,
100-
acceleration=0.1,
101-
drag=trials.default_drag,
102-
rotation=rotation,
103-
)
119+
heading, velocity = bee_simulator.get_next_state(
120+
dt=dt,
121+
velocity=velocity,
122+
heading=heading,
123+
acceleration=0.1,
124+
drag=trials.default_drag,
125+
rotation=rotation,
126+
)
127+
128+
headings[t], velocities[t,:] = heading, velocity
104129

105130
return StoneResults(name, self.parameters, headings, velocities, log = None, cpu4_snapshot = None)
106131

lib/pim/models/new/stone/basic.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ def step(self, network: Network, dt: float):
8181
mem_update -= 0.5 * (0.5 - tn1.reshape(2, 1))
8282

8383
# Constant purely to visualise same as rate-based model
84-
# multiply with dt here or multiply incoming TN?
8584
mem_reshaped += self.gain * mem_update * dt
8685

8786
self.memory = np.clip(mem_reshaped.reshape(-1), 0.0, 1.0)

lib/pim/models/new/stone/bee_simulator.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,26 +62,26 @@ def get_flow(heading, velocity, pref_angle=np.pi/4):
6262
return np.dot(A, velocity)
6363

6464

65-
def rotate(theta, r):
65+
def rotate(dt, theta, r):
6666
"""Return new heading after a rotation around Z axis."""
67-
return (theta + r + np.pi) % (2.0 * np.pi) - np.pi
67+
return (theta + r * dt + np.pi) % (2.0 * np.pi) - np.pi
6868

6969

70-
def thrust(theta, acceleration):
70+
def thrust(dt, theta, acceleration):
7171
"""Thrust vector from current heading and acceleration
7272
7373
theta: clockwise radians around z-axis, where 0 is forward
7474
acceleration: float where max speed is ....?!?
7575
"""
76-
return np.array([np.sin(theta), np.cos(theta)]) * acceleration
76+
return np.array([np.sin(theta), np.cos(theta)]) * acceleration * dt
7777

7878

79-
def get_next_state(heading, velocity, rotation, acceleration, drag=0.5):
79+
def get_next_state(dt, heading, velocity, rotation, acceleration, drag=0.5):
8080
"""Get new heading and velocity, based on relative rotation and
8181
acceleration and linear drag."""
82-
theta = rotate(heading, rotation)
83-
v = velocity + thrust(theta, acceleration)
84-
v -= drag * v
82+
theta = rotate(dt, heading, rotation)
83+
v = velocity + thrust(dt, theta, acceleration)
84+
v *= (1.0 - drag)**dt
8585
return theta, v
8686

8787

lib/pim/models/new/stone/rate.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ def step(self, network: Network, dt: float):
8888
cpu4[0-7] store optic flow peaking at left 45 deg
8989
cpu[8-15] store optic flow peaking at right 45 deg."""
9090
tb1 = network.output(self.TB1)
91-
tn1 = network.output(self.TN1) * dt
92-
tn2 = network.output(self.TN2) * dt
91+
tn1 = network.output(self.TN1)
92+
tn2 = network.output(self.TN2)
9393

9494
self.memory += (np.clip(np.dot(self.W_TN, 0.5-tn1), 0, 1) *
95-
self.gain * np.dot(self.W_TB1, 1.0-tb1))
96-
self.memory -= self.gain * 0.25 * np.dot(self.W_TN, tn2)
95+
self.gain * np.dot(self.W_TB1, 1.0-tb1)) * dt
96+
self.memory -= self.gain * 0.25 * np.dot(self.W_TN, tn2) * dt
9797
self.memory = np.clip(self.memory, 0.0, 1.0)
9898

9999
def output(self, network: Network) -> Output:
@@ -107,14 +107,14 @@ def step(self, network: Network, dt: float):
107107
cpu4[0-7] store optic flow peaking at left 45 deg
108108
cpu[8-15] store optic flow peaking at right 45 deg."""
109109
tb1 = network.output(self.TB1)
110-
tn1 = network.output(self.TN1) * dt
111-
tn2 = network.output(self.TN2) * dt
110+
tn1 = network.output(self.TN1)
111+
tn2 = network.output(self.TN2)
112112

113113
mem_update = np.dot(self.W_TN, tn2)
114114
mem_update -= np.dot(self.W_TB1, tb1)
115115
mem_update = np.clip(mem_update, 0, 1)
116116
mem_update *= self.gain
117-
self.memory += mem_update
117+
self.memory += mem_update * dt
118118
self.memory -= 0.125 * self.gain * dt
119119
self.memory = np.clip(self.memory, 0.0, 1.0)
120120

@@ -149,15 +149,7 @@ def build_network(self) -> Network:
149149
function = self.tn2_output,
150150
initial = np.zeros(N_TN2),
151151
),
152-
"CPU4": CPU4Layer(
153-
"TB1", "TN1", "TN2",
154-
self.W_TN_CPU4,
155-
self.W_TB1_CPU4,
156-
self.cpu4_mem_gain,
157-
self.cpu4_slope,
158-
self.cpu4_bias,
159-
self.noise,
160-
),
152+
"CPU4": self.build_cpu4_layer(),
161153
"CPU1": FunctionLayer(
162154
inputs = ["TB1", "CPU4"],
163155
function = self.cpu1_output,
@@ -169,10 +161,22 @@ def build_network(self) -> Network:
169161
)
170162
})
171163

164+
def build_cpu4_layer(self) -> Layer:
165+
return self.CPU4LayerClass(
166+
"TB1", "TN1", "TN2",
167+
self.W_TN_CPU4,
168+
self.W_TB1_CPU4,
169+
self.cpu4_mem_gain,
170+
self.cpu4_slope,
171+
self.cpu4_bias,
172+
self.noise,
173+
)
174+
172175
"""Class to keep a set of parameters for a model together.
173176
No state is held in the class currently."""
174177
def __init__(self,
175178
noise=0.1,
179+
CPU4LayerClass = CPU4Layer,
176180
tl2_slope=tl2_slope_tuned,
177181
tl2_bias=tl2_bias_tuned,
178182
tl2_prefs=np.tile(np.linspace(0, 2*np.pi, N_TB1,
@@ -190,6 +194,7 @@ def __init__(self,
190194
weight_noise=0.0,
191195
):
192196
super().__init__()
197+
self.CPU4LayerClass = CPU4LayerClass
193198

194199
# Default noise used by the model for all layers
195200
self.noise = noise
@@ -361,15 +366,7 @@ def build_network(self) -> Network:
361366
function = self.tn2_output,
362367
initial = np.zeros(N_TN2),
363368
),
364-
"CPU4": CPU4PontinLayer(
365-
"TB1", "TN1", "TN2",
366-
self.W_TN_CPU4,
367-
self.W_TB1_CPU4,
368-
self.cpu4_mem_gain,
369-
self.cpu4_slope,
370-
self.cpu4_bias,
371-
self.noise,
372-
),
369+
"CPU4": self.build_cpu4_layer(),
373370
"Pontin": FunctionLayer(
374371
inputs = ["CPU4"],
375372
function = self.pontin_output,
@@ -386,8 +383,8 @@ def build_network(self) -> Network:
386383
)
387384
})
388385

389-
def __init__(self, **kwargs):
390-
super().__init__(**kwargs)
386+
def __init__(self, CPU4LayerClass = CPU4PontinLayer, **kwargs):
387+
super().__init__(CPU4LayerClass = CPU4LayerClass, **kwargs)
391388

392389
self.cpu4_mem_gain *= 0.5
393390
self.cpu1_bias = -1.0

lib/pim/models/new/stone/trials.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def generate_route(T=1500, mean_acc=default_acc, drag=default_drag,
4444

4545
for t in range(1, T):
4646
headings[t], velocity[t, :] = bee_simulator.get_next_state(
47+
dt=1.0,
4748
heading=headings[t-1], velocity=velocity[t-1, :],
4849
rotation=rotation[t], acceleration=acceleration[t], drag=drag)
4950
return headings, velocity

0 commit comments

Comments
 (0)