Skip to content

Commit

Permalink
tests/models/Predator-Prey: Use attractor/repeller forces to select …
Browse files Browse the repository at this point in the history
…the next move (#2121)

A slightly less trivial variant of the Predator-Prey model.
  • Loading branch information
jvesely authored Sep 22, 2021
2 parents c38c0ff + 09857a1 commit d3741b2
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 31 deletions.
5 changes: 5 additions & 0 deletions psyneulink/core/llvm/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def np_cmp(builder, x, y):
numpy_handlers = {
'tanh': self.call_builtin_np_tanh,
'exp': self.call_builtin_np_exp,
'sqrt': self.call_builtin_np_sqrt,
'equal': get_np_cmp("=="),
'not_equal': get_np_cmp("!="),
'less': get_np_cmp("<"),
Expand Down Expand Up @@ -470,6 +471,10 @@ def call_builtin_np_exp(self, builder, x):
x = self.get_rval(x)
return self._do_unary_op(builder, x, lambda builder, x: helpers.exp(self.ctx, builder, x))

def call_builtin_np_sqrt(self, builder, x):
x = self.get_rval(x)
return self._do_unary_op(builder, x, lambda builder, x: helpers.sqrt(self.ctx, builder, x))

def call_builtin_np_max(self, builder, x):
# numpy max searches for the largest scalar and propagates NaNs be default.
# Only the default behaviour is supported atm
Expand Down
3 changes: 3 additions & 0 deletions psyneulink/core/llvm/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ def exp(ctx, builder, x):
exp_f = ctx.get_builtin("exp", [x.type])
return builder.call(exp_f, [x])

def sqrt(ctx, builder, x):
sqrt_f = ctx.get_builtin("sqrt", [x.type])
return builder.call(sqrt_f, [x])

def tanh(ctx, builder, x):
tanh_f = ctx.get_builtin("tanh", [x.type])
Expand Down
4 changes: 4 additions & 0 deletions tests/functions/test_user_defined_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ def myFunction(variable):
@pytest.mark.parametrize("op,variable,expected", [ # parameter is string since compiled udf doesn't support closures as of present
("TANH", [[1, 3]], [0.76159416, 0.99505475]),
("EXP", [[1, 3]], [2.71828183, 20.08553692]),
("SQRT", [[1, 3]], [1.0, 1.7320508075688772]),
("SHAPE", [1, 2], [2]),
("SHAPE", [[1, 3]], [1, 2]),
("ASTYPE_FLOAT", [1], [1.0]),
Expand Down Expand Up @@ -504,6 +505,9 @@ def myFunction(variable):
elif op == "EXP":
def myFunction(variable):
return np.exp(variable)
elif op == "SQRT":
def myFunction(variable):
return np.sqrt(variable)
elif op == "SHAPE":
def myFunction(variable):
return variable.shape
Expand Down
82 changes: 51 additions & 31 deletions tests/models/test_greedy_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,48 +133,72 @@ def test_predator_prey(benchmark, mode, samples):
benchmark.group = "Predator-Prey " + str(len(samples))
obs_len = 3
obs_coords = 2
player_idx = 0
player_obs_start_idx = player_idx * obs_len
player_value_idx = player_idx * obs_len + obs_coords
player_coord_slice = slice(player_obs_start_idx,player_value_idx)
predator_idx = 1
predator_obs_start_idx = predator_idx * obs_len
predator_value_idx = predator_idx * obs_len + obs_coords
predator_coord_slice = slice(predator_obs_start_idx,predator_value_idx)
prey_idx = 2
prey_obs_start_idx = prey_idx * obs_len
prey_value_idx = prey_idx * obs_len + obs_coords
prey_coord_slice = slice(prey_obs_start_idx,prey_value_idx)

player_len = prey_len = predator_len = obs_coords

# Input Mechanisms
player_pos = ProcessingMechanism(size=player_len, name="PLAYER POS")
prey_pos = ProcessingMechanism(size=prey_len, name="PREY POS")
predator_pos = ProcessingMechanism(size=predator_len, name="PREDATOR POS")

# Perceptual Mechanisms
player_obs = ProcessingMechanism(size=prey_len, function=GaussianDistort, name="PLAYER OBS")
prey_obs = ProcessingMechanism(size=prey_len, function=GaussianDistort, name="PREY OBS")
predator_obs = TransferMechanism(size=predator_len, function=GaussianDistort, name="PREDATOR OBS")

# Action Mechanism
# Use ComparatorMechanism to compute direction of action as difference of coordinates between player and prey:
# note: unitization is done in main loop, to allow compilation of LinearCombination function) (TBI)
greedy_action_mech = ComparatorMechanism(name='ACTION',sample=player_obs,target=prey_obs)

def action_fn(variable):
predator_pos = variable[0]
player_pos = variable[1]
prey_pos = variable[2]

# Directions away from predator and towards prey
pred_2_player = player_pos - predator_pos
play_2_prey = prey_pos - player_pos

# Distances to predator and prey
distance_predator = np.sqrt(pred_2_player[0] * pred_2_player[0] + pred_2_player[1] * pred_2_player[1])
distance_prey = np.sqrt(play_2_prey[0] * play_2_prey[0] + play_2_prey[1] * play_2_prey[1])

# Normalized directions from predator and towards prey
pred_2_player_norm = pred_2_player / distance_predator
play_2_prey_norm = play_2_prey / distance_prey

# Weighted directions from predator and towards prey
# weights are reversed so closer agent has greater impact on movement
pred_2_player_n = pred_2_player_norm * (distance_prey / (distance_predator + distance_prey))
play_2_prey_n = play_2_prey_norm * (distance_predator / (distance_predator + distance_prey))

return pred_2_player_n + play_2_prey_n

# note: unitization is done in main loop
greedy_action_mech = pnl.ProcessingMechanism(function=action_fn, input_ports=["predator", "player", "prey"],
default_variable=[[0,0],[0,0],[0,0]], name="ACTION")

direct_move = ComparatorMechanism(name='DIRECT MOVE',sample=player_pos, target=prey_pos)

# Create Composition
agent_comp = Composition(name='PREDATOR-PREY COMPOSITION')
agent_comp.add_node(player_obs)
agent_comp.add_node(predator_obs)
agent_comp.add_node(prey_obs)
agent_comp.add_linear_processing_pathway([player_pos, player_obs])
agent_comp.add_linear_processing_pathway([prey_pos, prey_obs])
agent_comp.add_linear_processing_pathway([predator_pos, predator_obs])
agent_comp.add_node(greedy_action_mech)
agent_comp.exclude_node_roles(predator_obs, NodeRole.OUTPUT)
agent_comp.add_node(direct_move)
agent_comp.add_projection(pnl.MappingProjection(predator_obs, greedy_action_mech.input_ports[0]))
agent_comp.add_projection(pnl.MappingProjection(prey_obs, greedy_action_mech.input_ports[1]))
agent_comp.add_projection(pnl.MappingProjection(player_obs, greedy_action_mech.input_ports[2]))
agent_comp.exclude_node_roles(direct_move, NodeRole.OUTPUT)


ocm = OptimizationControlMechanism(state_features={SHADOW_INPUTS: [player_obs, predator_obs, prey_obs]},
ocm = OptimizationControlMechanism(state_features={SHADOW_INPUTS: [player_pos, predator_pos, prey_pos]},
agent_rep=agent_comp,
function=GridSearch(direction=MINIMIZE,
save_values=True),

objective_mechanism=ObjectiveMechanism(function=Distance(metric=NORMED_L0_SIMILARITY),
monitor=[
player_obs,
prey_obs
greedy_action_mech,
direct_move
]),
control_signals=[ControlSignal(modulates=(VARIANCE,player_obs),
allocation_samples=samples),
Expand All @@ -188,18 +212,14 @@ def test_predator_prey(benchmark, mode, samples):
agent_comp.enable_controller = True
ocm.comp_execution_mode = ocm_mode

input_dict = {player_obs:[[1.1576537, 0.60782117]],
predator_obs:[[-0.03479106, -0.47666293]],
prey_obs:[[-0.60836214, 0.1760381 ]],
input_dict = {player_pos:[[1.1576537, 0.60782117]],
predator_pos:[[-0.03479106, -0.47666293]],
prey_pos:[[-0.60836214, 0.1760381 ]],
}
run_results = agent_comp.run(inputs=input_dict, num_trials=2, execution_mode=mode)

if len(samples) == 2:
# KDM 12/4/19: modified results due to global seed offset of
# GaussianDistort assignment.
# to produce old numbers, run get_global_seed once before creating
# each Mechanism with GaussianDistort above
assert np.allclose(run_results[0], [[-10.06333025, 2.4845505 ]])
assert np.allclose(run_results[0], [[ 0.97052163, -0.13433325]])
if mode is pnl.ExecutionMode.Python:
assert np.allclose(ocm.state_feature_values, [[ 1.1576537, 0.60782117],
[-0.03479106, -0.47666293],
Expand Down

0 comments on commit d3741b2

Please sign in to comment.