Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

llvm, ports/ControlSignal: Add support for CostFunctions.NONE #2203

Merged
merged 3 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,11 @@ def _gen_llvm_net_outcome_function(self, *, ctx, tags=frozenset()):
ctx.int32_ty(i)])
data_out = builder.gep(op_in, [ctx.int32_ty(0), ctx.int32_ty(0),
ctx.int32_ty(0)])
if data_in.type != data_out.type:
warnings.warn("Shape mismatch: Allocation sample '{}' ({}) doesn't match input port input ({}).".format(i, self.parameters.control_allocation_search_space.get(), op.defaults.variable))
assert len(data_out.type.pointee) == 1
data_out = builder.gep(data_out, [ctx.int32_ty(0), ctx.int32_ty(0)])

builder.store(builder.load(data_in), data_out)

# Invoke cost function
Expand Down Expand Up @@ -1497,19 +1502,33 @@ def _gen_llvm_evaluate_function(self, *, ctx:pnlvm.LLVMBuilderContext,
# Construct input
comp_input = builder.alloca(sim_f.args[3].type.pointee, name="sim_input")

input_initialized = [False] * len(comp_input.type.pointee)
for src_idx, ip in enumerate(self.input_ports):
if ip.shadow_inputs is None:
continue

# shadow inputs point to an input port of of a node.
# If that node takes direct input, it will have an associated
# (input_port, output_port) in the input_CIM.
# Take the former as an index to composition input variable.
cim_in_port = self.agent_rep.input_CIM_ports[ip.shadow_inputs][0]
dst_idx = self.agent_rep.input_CIM.input_ports.index(cim_in_port)

# Check that all inputs are unique
assert not input_initialized[dst_idx], "Double initialization of input {}".format(dst_idx)
input_initialized[dst_idx] = True

src = builder.gep(arg_in, [ctx.int32_ty(0), ctx.int32_ty(src_idx)])
# Destination is a struct of 2d arrays
dst = builder.gep(comp_input, [ctx.int32_ty(0),
ctx.int32_ty(dst_idx),
ctx.int32_ty(0)])
builder.store(builder.load(src), dst)

# Assert that we have populated all inputs
assert all(input_initialized), \
"Not all inputs to the simulated composition are initialized: {}".format(input_initialized)


# FIX: 11/3/21 ??REFACTOR EITHER:
# - AROUND PASSING OF num_estimates IN CALL TO _execute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@

import numpy as np
import typecheck as tc
import warnings

# FIX: EVCControlMechanism IS IMPORTED HERE TO DEAL WITH COST FUNCTIONS THAT ARE DEFINED IN EVCControlMechanism
# SHOULD THEY BE LIMITED TO EVC??
Expand Down Expand Up @@ -1104,8 +1105,9 @@ def _gen_llvm_costs(self, *, ctx:pnlvm.LLVMBuilderContext, tags:frozenset):
"function")
func_state = pnlvm.helpers.get_state_ptr(builder, self, state,
"function")
# FIXME: Add support for other cost types
assert self.cost_options == CostFunctions.INTENSITY

# FIXME: This allows INTENSITY and NONE
assert self.cost_options & ~CostFunctions.INTENSITY == 0

cfunc = ctx.import_llvm_function(self.function.combine_costs_fct)
cfunc_in = builder.alloca(cfunc.args[2].type.pointee)
Expand All @@ -1126,9 +1128,14 @@ def _gen_llvm_costs(self, *, ctx:pnlvm.LLVMBuilderContext, tags:frozenset):
# Port input is always struct { data input, modulations }
ifunc_in = builder.gep(arg_in, [ctx.int32_ty(0), ctx.int32_ty(0)])
# point output to the proper slot in comb func input
ifunc_comb_slot = builder.gep(cfunc_in, [ctx.int32_ty(0), ctx.int32_ty(cost_funcs)])

builder.call(ifunc, [ifunc_params, ifunc_state, ifunc_in, ifunc_comb_slot])
assert cost_funcs == 0, "Intensity should eb the first cost function!"
ifunc_out = builder.gep(cfunc_in, [ctx.int32_ty(0), ctx.int32_ty(cost_funcs)])
if ifunc_out.type != ifunc.args[3].type:
warnings.warn("Shape mismatch: {} element of combination func input ({}) doesn't match INTENSITY cost output ({})".format(cost_funcs, self.function.combine_costs_fct.defaults.variable, self.function.intensity_cost_fct.defaults.value))
assert self.cost_options == CostFunctions.INTENSITY
ifunc_out = cfunc_in

builder.call(ifunc, [ifunc_params, ifunc_state, ifunc_in, ifunc_out])

cost_funcs += 1

Expand Down
30 changes: 17 additions & 13 deletions tests/composition/test_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -1052,17 +1052,20 @@ def test_control_of_mech_port(self, comp_mode):
@pytest.mark.control
@pytest.mark.composition
@pytest.mark.parametrize("cost, expected, exp_values", [
(pnl.CostFunctions.NONE, 7.0, [1, 2, 3, 4, 5]),
(pnl.CostFunctions.INTENSITY, 3, [-1.71828183, -5.3890561, -17.08553692, -50.59815003, -143.4131591]),
(pnl.CostFunctions.ADJUSTMENT, 3, [1, 1, 1, 1, 1] ),
(pnl.CostFunctions.INTENSITY | pnl.CostFunctions.ADJUSTMENT, 3, [-1.71828183, -6.3890561, -19.08553692, -53.59815003, -147.4131591]),
(pnl.CostFunctions.DURATION, 3, [-19, -22., -25., -28., -31]),
(pnl.CostFunctions.NONE, 7.0, [3, 4, 5, 6, 7]),
(pnl.CostFunctions.INTENSITY, 3, [0.2817181715409549, -3.3890560989306495, -15.085536923187664, -48.59815003314423, -141.41315910257657]),
(pnl.CostFunctions.ADJUSTMENT, 3, [3, 3, 3, 3, 3] ),
(pnl.CostFunctions.INTENSITY | pnl.CostFunctions.ADJUSTMENT, 3, [0.2817181715409549, -4.389056098930649, -17.085536923187664, -51.59815003314423, -145.41315910257657]),
(pnl.CostFunctions.DURATION, 3, [-17, -20, -23, -26, -29]),
# FIXME: combinations with DURATION are broken
# (pnl.CostFunctions.DURATION | pnl.CostFunctions.ADJUSTMENT, ,),
# (pnl.CostFunctions.ALL, ,),
pytest.param(pnl.CostFunctions.DEFAULTS, 7, [1, 2, 3, 4, 5], id="CostFunctions.DEFAULT")],
pytest.param(pnl.CostFunctions.DEFAULTS, 7, [3, 4, 5, 6, 7], id="CostFunctions.DEFAULT")],
ids=lambda x: x if isinstance(x, pnl.CostFunctions) else "")
def test_modulation_simple(self, cost, expected, exp_values):
def test_modulation_simple(self, cost, expected, exp_values, comp_mode):
if comp_mode != pnl.ExecutionMode.Python and cost not in {pnl.CostFunctions.NONE, pnl.CostFunctions.INTENSITY}:
pytest.skip("Not implemented!")

obj = pnl.ObjectiveMechanism()
mech = pnl.ProcessingMechanism()

Expand All @@ -1073,6 +1076,7 @@ def test_modulation_simple(self, cost, expected, exp_values):
comp.add_controller(
pnl.OptimizationControlMechanism(
objective_mechanism=obj,
state_features=[mech.input_port],
control_signals=pnl.ControlSignal(
modulates=('intercept', mech),
modulation=pnl.OVERRIDE,
Expand All @@ -1082,9 +1086,10 @@ def test_modulation_simple(self, cost, expected, exp_values):
)
)

ret = comp.run(inputs={mech: [2]}, num_trials=1)
ret = comp.run(inputs={mech: [2]}, num_trials=1, execution_mode=comp_mode)
assert np.allclose(ret, expected)
assert np.allclose([float(np.squeeze(x)) for x in comp.controller.function.saved_values], exp_values)
if comp_mode == pnl.ExecutionMode.Python:
assert np.allclose([float(x) for x in comp.controller.function.saved_values], exp_values)

@pytest.mark.benchmark
@pytest.mark.control
Expand Down Expand Up @@ -1144,9 +1149,8 @@ def test_modulation_of_random_state_DDM(self, comp_mode, benchmark, prng):

@pytest.mark.control
@pytest.mark.composition
@pytest.mark.parametrize("mode", [pnl.ExecutionMode.Python])
@pytest.mark.parametrize("num_generators", [5])
def test_modulation_of_random_state(self, mode, num_generators):
def test_modulation_of_random_state(self, comp_mode, num_generators):
obj = pnl.ObjectiveMechanism()
# Set original seed that is not used by any evaluation
# this prevents dirty state from initialization skewing the results.
Expand All @@ -1162,6 +1166,7 @@ def test_modulation_of_random_state(self, mode, num_generators):

comp.add_controller(
pnl.OptimizationControlMechanism(
state_features=[mech.input_port],
objective_mechanism=obj,
control_signals=pnl.ControlSignal(
modulates=('seed', mech),
Expand All @@ -1173,12 +1178,11 @@ def test_modulation_of_random_state(self, mode, num_generators):
)
)

# comp.run(inputs={mech: [1]}, num_trials=2, execution_mode=mode)
comp.run(inputs={mech: [1]},
num_trials=2,
report_output=pnl.ReportOutput.FULL,
report_params=pnl.ReportParams.MONITORED,
execution_mode=mode)
execution_mode=comp_mode)

# Construct expected results.
# First all generators rest their sequence.
Expand Down