Skip to content

Commit

Permalink
Fix/comp/proj warnings (#2264)
Browse files Browse the repository at this point in the history
* • component.py
  docstring mod to **size**

* • optimizationcontrolmechanism.py:
  added feature_input_ports attribute and num_feature_input_ports property

* • optimizationcontrolmechanism.py:
  added feature_input_ports attribute and num_feature_input_ports property

• parameterestimationcomposition.py: fixed misplacement of its Parameters() attribute

* • optimizationcontrolmechanism.py:
  added feature_input_ports attribute and num_feature_input_ports property

• parameterestimationcomposition.py: fixed misplacement of its Parameters() attribute

• optimizationfunctions.py: made num_estimates a Parameter

* - modified test_mode_based_num_estimates

* -

* -

* • optimizationcontrolmechanism.py:
  - _instantiate_control_signals: random_seeds -> random_seed_mod_values

* • composition.py
  - _add_controller:  modifying to instantiate feature_input_ports if none are specified

* • composition.py:
  - add_controller:  now adds feature_input_ports for Compostion INPUT nodes if not state_features not specified

* -

* • composition.py
  - _add_controller:  modifying to instantiate feature_input_ports if none are specified

* • composition.py:
  - add_controller:  assign simulation_input_ports

* -

* • optimizationcontrolmechanism.py:
  - feature_input_ports -> state_input_ports
  - _instantiate_input_ports():
      state_features only allowed to specifying state_input_ports if
      agent_rep is a CompositionFunctionApproximator (i.e., model-free optimization)

• composition.py:
  - add_controller: adds state_input_ports to shadow INPUT Nodes of Composition
    if controller.agent_rep is Composition (model-based optimziation)
    or state_features have not been specified (for model-free optimizaton)

* -

* • optimizationcontrolmechanism.py:
  _instantiate_input_ports:
     reinstate allowance of state_features specification if agent_rep is a Composition
     (i.e., model-based optimization) as long as they are all INPUT Nodes of agent_rep

* -

* -

* • optimizationcontrolmechanism.py
  - _gen_llvm_evaluate_function: num_estimates -> num_estimates_per_trial

* -

* • optimizationcontrolmechanism.py
  - _gen_llvm_evaluate_function: num_estimates -> num_trial_per_estimate

* • optimizationcontrolmechanism.py
  - _gen_llvm_evaluate_function: num_estimates -> num_trials_per_estimate

* -

* -

* -

* -

* • composition.py
  - __init__: moved controller instantiation until after nodes, projections and pathways

* • composition.py
  - __init__: restored add_controller position

* llvm/struct generation: Make sure num_estimats per trial is always integer

Signed-off-by: Jan Vesely <[email protected]>

* -

* • composition.py:
  - _update_controller: added
  - add_controller and _analyze_graph(): call _update_controller

* -

* • composition.py
  _update_controller:  fixed to loop through all input_ports of comp INPUT nodes

* • test_control.py
  - test_agent_rep_assignement_as_controller_and_replacement:
      updated to test that shadowing projections to state_input_ports
      are properly added and deleted

* • optimizationfunctions.py:
  - _function: refactored to put use aggregation_function at end
  - _grid_evaluate:  still needs to return all_samples

* -

* • composition.py
  - added call to _update_controller to add_node
  - moved test for projections to controller.state_input_ports to run()

* -

* • composition.py:
  moved calls to _update_controller to _complete_init_of_partially_initialized_nodes
  moved _update_controller to ocm._update_state_input_ports

• optimizationcontrolmechanism.py:
  added _update_state_input_ports [**still needed work**]

* • composition.py:
  moved calls to _update_controller to _complete_init_of_partially_initialized_nodes
  moved _update_controller to ocm._update_state_input_ports
  _instantiate_controller_shadow_projections [still needs to be implemented]

• optimizationcontrolmechanism.py:
  added _update_state_input_ports [**still needed work**]

* • composition.py
  added needs_update_controller

* -

* • composition.py:
  - implemented self.needs_update_controller
  - moved implementation of controlsignal projections
    from add_controller to _instantiate_control_projections
    that is called in _complete_init_of_partially_initialized_nodes

Note: still need to set  self.needs_update_controller to False
      after instantiating state_input_ports and projections to them

* -

* -

* -

* -

* -

* -

* -

* -

* • Passing all test_control tests except test_mode_based_num_estimates

* • Passing all test_control tests

* -

* • optimizationcontrolmechanism.py
  - _update_state_input_ports_for_controller: handle nested input nodes

* -

* • optimizationcontrolmechanism.py
  _update_state_input_ports_for_controller:  fixed bug with > 1 INPUT node in Composition

* • test_show_graph.py: passes all tests

* -

* • test_report.py:  passing all tests

* • Passes all tests!

* -

* -

* • composition.py: reorganize with #region and #enregions

* • composition.py: reorganize with #region and #enregions

* • controlmechanism.py, optimizationcontrolmechanism.py:
  - _instantiate_monitor_for_control_input_ports -> _parse_monitor_control_input_ports
  - refactored to support allow_probes option on ocm

* -

* -

* -

* • controlmechanism.py, optimizationcontrolmechanism.py:
  - _instantiate_monitor_for_control_input_ports -> _parse_monitor_control_input_ports
  - refactored to support allow_probes option on ocm

* • controlmechanism.py, optimizationcontrolmechanism.py:
  - _instantiate_monitor_for_control_input_ports -> _parse_monitor_control_input_ports
  - refactored to support allow_probes option on ocm

* -

* • composition.py:
  __init__: move controller to after add_nodes and add_linear_pathway

* -

* - test_control: only test_hanging_control_spec_outer_controller not passing

* -

* -

* -

* -

* -

* -

* • composition.py:
  _instantiate_control_projections:
     weird requirement for double-call to controller._instantiate_control_signal

* • test_paremtercomposition.py:
  restored parameter spec that causes crash ('threshold',Decision2)

* ª Attempt to fix problem with partially overlapping local and ocm control specs

- composition.py
  - _get_control_signals_for_composition:  (see 11/20/21)
      - added (but commented out change) to "if node.controller" to "if not node.controller"
      - changed append to extend
  - _instantiation_control_projection:
      - got rid of try and except double-call to controller._instantiate_control_signals
      -  outdented call to self.controller._activate_projections_for_composition at end

- controlmechanism.py:
    - _check_for_duplicates:  add warning and return duplicates

- optimizationcontrolmechanism._instantiate_control_signals:
    - add call to self.agent_rep._get_control_signals_for_composition() to get local control specs (on mechs in comp)
    - eliminate duplicates with control_signal specs on OCM
    - instantiate local + ocm control_signals

- parameterestimationcomposition.py
  - added context to various calls

* see later commit

* see later commit

* see later commit

* see later commit

* - This branch passes all tests except:
   - test_parameterestimationcomposition
   - test_composition/test_partially_overlapping_control_specs (ADDED IN THIS COMMINT)

- All relevant changes to this branch are marked as "11/21/21."
  However, most are commented out as they break other things.

- The tests above both involve local control specifications (on mechanism within a nested comp)
  and on the OCM for the outer composition, some of which are for the same nested mechs

- Both tests fail with:
   "AttributeError: 'NoneType' object has no attribute '_get_by_time_scale'" (in component.py LINE 3276)
   This may be due to a problem with context setting, since the error is because the modulation Parameter
   of the ControlProjection is returning "None" rather than "multiplicative_param" (when called with get(context)),
   whereas "multiplicative_param" is returned with a call to get() (i.e., with no context specified)

- Most of test_partially_overlapping_control_specs is passed if
   changes marked "11/21/21 NEW" in optimizationcontrolmechanism.py (LINE 1390) are implemented,
   but it does not properly route ControlProjections through parameter_CIMS (see last assert in test).
   Furthermore, test_parameterestimationcompsition fails with the mod param error, even though the
   model has similar structure (i.e., outer composition -- in this case a ParameterEstimationComposition)
   with an OCM that is given control specs that overlap with ones in a nested composition.

- There are also several other things in composition I found puzzling and tried modifying, but that cuased failures:
  - _get_control_signals_for_composition():
      - seems "if node.controller" should be "if **not** node.controller" (emphasis added just for comment)
      - "append" should be "extend"
  - _instantiate_control_projection():
      -  call to self.controller._activate_projections_for_composition (at end of method) should not be indented

* - small mods; don't impact anything relevant to prior commit message

* - small mods; don't impact anything relevant to prior commit message

* - small mods; don't impact anything relevant to prior commit message

* - finished adding formatting regions to composition.py

* -

* • composition.py:
  - rename _check_projection_initialization_status -> _check_controller_initialization_status
  - add _check_nodes_initialization_status(context=context)
    (and calls it with _check_controller_initialization_status)

* • show_graph.py:  addressed bug associated with ocm.allow_direct_probe

* • show_graph.py:  addressed bug associated with ocm.allow_direct_probe

* -

* Composition: add_controller: set METHOD as context source early

* -

* • composition.py
  retore append of control_signals in _instantiate_control_projections()

* • composition.py
  restore append of control_signals in _instantiate_control_projections()

• test_composition.py:
  add test_partially_overlapping_local_and_control_mech_control_specs_in_unnested_and_nested_comp

* • test_partially_overlapping_local_and_control_mech_control_specs_in_unnested_and_nested_comp():
  - added clear_registry() to allow names to be reused in both runs of test

* • composition.py
  docstring:  added projections entry to list of attributes
  - add_controller:  added call to _add_node_aux_components() for controller

* • composition.py
  _add_node_aux_components(): added deletion of item from aux_components if instantiated

* • composition.py
  - comment out _add_node_aux_components() (causing new failures)
  - move _instantiate_control_projections to be with _instantiate_control_projections,
       after self.add_node(self.controller.objective_mechanism (to be more orderly)

* -

* - confirm that it passes all tests exception test_composition/test_partially_overlapping...
  (with addition of _add_aux_components in add_controller commented out)

* • composition.py:  some more fixed to add_controller that now fail only one test:
    - test_agent_rep_assignement_as_controller_and_replacement

* • Passes *all* current tests

* • composition.py:
  - add_controller:  few more minor mods;
  still passes all tests

* -

* -

* -

* • controlmechanism.py:
  - __init__: resrict specification to only one of control, modulatory_signals, or control_signals (synonyms)

* -

* • composition.py:  in progress fix of bug in instantiating shadow projections for ocm.state_input_ports

* • composition.py:
  - _get_original_senders():  added support for nested composition
    needs to be checked for more than one level
    needs to be refactored to be recursive

* • optimizationcontrolmechanism.py
  - _update_state_input_ports_for_controller:  fix invalid_state_features to allow input_CIM of nested comp in agent_rep

* -

* • composition.py
 - _get_original_senders: made recursive

* • test_show_graph.py: update for fixes

* -

* • tests:  passes all in test_show_graph.py and test_report.py

* Passes all tests

* - comment clean-up

* • composition.py
 - add_controller and _get_nested_node_CIM_port:
   added support for forced assignment of NodeRole.OUTPUT for nodes specified in OCM.monitor_for_control,
   but referenced 'allow_probes' attribute still needs to be implemented

* • composition.py, optimizationcontrolmechanism.py:
  allow_probes fully implemented

* • show_graph.py:  fixed bug causing extra projections to OCM

* • composition.py:
  - _update_shadow_projections(): fix handling of deep nesting

* • optimizationcontrolmechanism.py: add agent_rep_type property

* • optimizationcontrolmechanism.py:
  - state_feature_function -> state_feature_functions

* • optimizationcontrolmechanism.py:
  - _validate_params:  validate state_feature_functions
  - _update_state_input_ports_for_controller: implement assignment of state_feature_functions

* -

* -

* • Passes all tests except test_json with 'model_with_control'

* -

* • composition.py
  - add_projection:  delete instantiation of shadow projections (handled by _update_shadow_projections)

* • composition.py
  - add_projection:  delete instantiation of shadow projections (handled by _update_shadow_projections)
  - remove calls to _update_shadows_dict

* • composition.py
  - add_projection:  delete instantiation of shadow projections (handled by _update_shadow_projections)
  - remove calls to _update_shadows_dict

* -

* • test_two_origins_two_input_ports:  crashes on failure of C->B to update

* -

* • composition.py
  - added property shadowing_dict that has shadowing ports as keys and the ports they shadow as values
  - refactored _update_shadowing_projections to use shadowing_dict

* • optimizationcontrolmechanism.py
  - _update_state_input_ports:  modified validations for nested nodes;  still failing some tests

* • optimizationcontrolmechanism.py
  - _update_state_input_ports:
     more careful and informative validation that state_input_ports are in comp or nested comp and are INPUT nodes thereof;
     passes all tests except test_two_origins_two_input_ports as before

* • composition.py
  _get_invalid_aux_components():  defer all shadow projections until _update_shadow_projections

* • composition.py
  _get_invalid_aux_components():  bug fix in test for shadow projections

* Port: _remove_projection_to_port: don't reduce variable below length 1

even ports with no incoming projections have variable at least length 1

* • composition.py
  add_node(): marked (but haven't removed) code block instantiating shadow_projections
   that seems now to be redundant with _update_shadow_projection

* • show_graph.py
  - _assign_cim_components: supress showing projections not in composition

* • composition.py:
  _analyze_graph():  add extra call to _determine_node_roles after _update_shadow_projections
  _run():  moved block of code at beginning initializing scheduler to after
           _complete_init_of_partially_initialized_nodes and _analyze_graph()

• show_graph.py
  - add test to all loops on projections:  "if proj in composition.projection"

* • show_graph.py
  - add show_projections_not_in_composition option for debugging

* • composition.py
  _update_shadow_projections(): delete unused shadow projections and corresponding ports

* • composition.py
  _update_shadow_projections(): fix bug in deletion of unused shadow projections and ports

• test_show_graph:  tests failing, need mods to accomodate changes

* • composition.py:
  _analyze_graph():  add extra call to _determine_node_roles after _update_shadow_projections
  _run():  moved block of code at beginning initializing scheduler to after
           _complete_init_of_partially_initialized_nodes and _analyze_graph()

• show_graph.py
  - add test to all loops on projections:  "if proj in composition.projection"

* • show_graph.py
  fixes; now passes all show_graph tests

* -

* • composition.py
  _update_shadow_projections:  raise error for attempt to shadow INTERNAL Node of nested comp

* -

* -

* • test_composition.py
  implemented test_shadow_nested_nodes that tests shadowing of nested nodes

* -

* -

* -

* -

* • optimizationcontrolmechanism.py: docstring mods

* • composition.py:
  - add allow_probes and exclude_probes_from_output

* • composition.py:
  - docstring mods re: allow_probes

• optimizationcontrolmechanism.py:
  - allow_probes:  eliminate DIRECT setting
  - remove _parse_monitor_for_control_input_ports (no longer needed without allow_probes=DIRECT)

* • composition.py:
  - change "exclude_probes_from_output" -> "include_probes_in_output"

* • composition.py:
  - docstring mods re: allow_probes and include_probes_in_output

* • composition.py:
  - docstring mods re: allow_probes and include_probes_in_output

* • controlmechanism.py:
  - add allow_probes handling (moved from OCM)

• optimizationcontrolmechanism.py:
  - move allow_probes to controlmechanism.py

• composition.py:
  - refactor handling of allow_probes to permit for any ControlMechanism

• objectivemechanism.py:
  - add modulatory_mechanism attribute

* • controlmechanism.py:
  - add allow_probes handling (moved from OCM)

• optimizationcontrolmechanism.py:
  - move allow_probes to controlmechanism.py

• composition.py:
  - refactor handling of allow_probes to permit for any ControlMechanism
  - add _handle_allow_probes_for_control() to reconcile setting on Composition and ControlMechanism

• objectivemechanism.py:
  - add modulatory_mechanism attribute

* • composition.py
  add assignment of learning_mechanism to objective_mechanism.modulatory_mechanism for add_learning methods

* • docstring mods

* -

* -

* • optimizationcontrolmechanism.py: docstring revs

* -

* -

* • test_composition.py:
  - add test_unnested_PROBE
  - add test_nested_PROBES
    TBD: test include_probes_in_output

* -

* • composition.py
  - add_node():  support tuple with required_role

* -

* • composition.py:
  - _determine_node_roles:
     fix bug in which nested comp was prevented from being an OUTPUT Node if,
     in addition to Nodes that qualifed as OUTPUT, it also had nodes that projected
     to Nodes in an outer comp (making it look like it was INTERNAL)

* -

* • composition.py:
  - add_node(): enforce include_probes_in_output = True for nested Compositions
  - execute():
    - replace return of output_value with get_output_value()

* -

* • CompositionInterfaceMechanism.rst:
  - correct path ref

• compositioninterfacemechanism.py:
  - docstring fixes

* -

* -

* • test_control.py:
  - add test_warning_for_add_controller_twice()
  - add test_warning_for_controller_assigned_to_another_comp()

* • test_control.py:
  - add test_warning_for_replacement_of_controller()

* -

* -

* • composition.py
  - _check_for_unused_projections(): fix crash for projections in deferred_init
  - _check_controller_initialization_status(): edited warning message
  - _add_node_aux_components(): add projections in deferred_init to invalid_aux_components

* • test_control.py:
  - add test_add_node_with_controller_spec_and_control_mech_but_not_a_controller()

* • test_control.py:
  = add test_bad_objective_mechanism_spec()

Co-authored-by: jdcpni <pniintel55>
Co-authored-by: Jan Vesely <[email protected]>
Co-authored-by: Katherine Mantel <[email protected]>
  • Loading branch information
3 people authored Dec 30, 2021
1 parent 5d2089a commit 9da53b9
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1385,11 +1385,10 @@ def _validate_params(self, request_set, target_set=None, context=None):
validate_monitored_port_spec(self, obj_mech_spec_list)

if not isinstance(target_set[OBJECTIVE_MECHANISM], (ObjectiveMechanism, list, bool)):
raise ControlMechanismError("Specification of {} arg for {} ({}) must be an {}"
"or a list of Mechanisms and/or OutputPorts to be monitored for control".
format(OBJECTIVE_MECHANISM,
self.name, target_set[OBJECTIVE_MECHANISM],
ObjectiveMechanism.componentName))
raise ControlMechanismError(f"Specification of {OBJECTIVE_MECHANISM} arg for '{self.name}' "
f"({target_set[OBJECTIVE_MECHANISM].name}) must be an "
f"{ObjectiveMechanism.componentType} or a list of Mechanisms and/or "
f"OutputPorts to be monitored for control.")

if CONTROL in target_set and target_set[CONTROL]:
control = target_set[CONTROL]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ class ObjectiveMechanism(ProcessingMechanism_Base):
"""

componentType = OBJECTIVE_MECHANISM
componentType = 'ObjectiveMechanism'

classPreferenceLevel = PreferenceLevel.SUBTYPE
# These will override those specified in TYPE_DEFAULT_PREFERENCES
Expand Down
47 changes: 31 additions & 16 deletions psyneulink/core/compositions/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2613,7 +2613,7 @@ def input_function(env, result):
from psyneulink.core.compositions.showgraph import ShowGraph, INITIAL_FRAME, SHOW_CIM, EXECUTION_SET, SHOW_CONTROLLER
from psyneulink.core.globals.context import Context, ContextFlags, handle_external_context
from psyneulink.core.globals.keywords import \
AFTER, ALL, ALLOW_PROBES, ANY, BEFORE, COMPONENT, CONTROL, CONTROL_SIGNAL, CONTROLLER, DEFAULT, \
AFTER, ALL, ALLOW_PROBES, ANY, BEFORE, COMPONENT, COMPOSITION, CONTROL, CONTROL_SIGNAL, CONTROLLER, DEFAULT, \
FEEDBACK, FUNCTION, HARD_CLAMP, IDENTITY_MATRIX, INPUT, INPUT_PORTS, INPUTS, INPUT_CIM_NAME, \
LEARNED_PROJECTIONS, LEARNING_FUNCTION, LEARNING_MECHANISM, LEARNING_MECHANISMS, LEARNING_PATHWAY, \
MATRIX, MATRIX_KEYWORD_VALUES, MAYBE, \
Expand Down Expand Up @@ -3788,6 +3788,7 @@ def _analyze_graph(self, context=None):
self._create_CIM_ports(context=context)
# Call after above so shadow_projections have relevant organization
self._update_shadow_projections(context=context)
# FIX: 12/29/21: MOVE TO _update_shadow_projections
# Call again to accomodate any changes from _update_shadow_projections
self._determine_node_roles(context=context)
self._check_for_projection_assignments(context=context)
Expand Down Expand Up @@ -4338,6 +4339,12 @@ def _add_node_aux_components(self, node, context=None):
feedback=proj_spec[1])
del node.aux_components[node.aux_components.index(proj_spec)]

# MODIFIED 12/29/21 NEW:
# # Finally, check for any deferred_init Projections
invalid_aux_components.extend([p for p in node.projections
if p._initialization_status & ContextFlags.DEFERRED_INIT])
# MODIFIED 12/29/21 END

return invalid_aux_components

def _get_invalid_aux_components(self, node):
Expand Down Expand Up @@ -5838,10 +5845,16 @@ def _check_for_unused_projections(self, context):
if isinstance(node, Composition):
node._check_for_unused_projections(context)
if isinstance(node, Mechanism):
unused_projections.extend([(f"{proj.name} (to '{node.name}' from '{proj.sender.owner.name}')")
for proj in node.afferents if proj not in self.projections])
unused_projections.extend([(f"{proj.name} (from '{node.name}' to '{proj.receiver.owner.name}')")
for proj in node.efferents if proj not in self.projections])
for proj in [p for p in node.projections if p not in self.projections]:
proj_deferred = proj._initialization_status & ContextFlags.DEFERRED_INIT
proj_name = proj._name if proj_deferred else proj.name
if proj in node.afferents:
first_item = '' if proj_deferred else f" (to '{node.name}'"
second_item = '' if proj_deferred else f" from '{proj.sender.owner.name}')."
if proj in node.efferents:
first_item = '' if proj_deferred else f" (from '{node.name}'"
second_item = '' if proj_deferred else f" to '{proj.receiver.owner.name}')."
unused_projections.append(f"{proj_name}{first_item}{second_item}")
if unused_projections:
warning = f"\nThe following Projections were specified but are not being used by Nodes in '{self.name}':"
warnings.warn(warning + "\n\t" + "\n\t".join(unused_projections))
Expand Down Expand Up @@ -7890,18 +7903,19 @@ def _check_controller_initialization_status(self, context=None):
else:
owner = component.receiver.owner
warnings.warn(
f"The controller of {self.name} has been specified to project to {owner.name}, "
f"but {owner.name} is not in {self.name} or any of its nested Compositions. "
f"This projection will be deactivated until {owner.name} is added to {self.name} "
f"The controller of '{self.name}' has been specified to project to '{owner.name}', "
f"but '{owner.name}' is not in '{self.name}' or any of its nested Compositions. "
f"This projection will be deactivated until '{owner.name}' is added to' {self.name}' "
f"in a compatible way."
)
elif isinstance(component, Mechanism):
warnings.warn(
f"The controller of {self.name} has a specification that includes the Mechanism "
f"{component.name}, but {component.name} is not in {self.name} or any of its "
f"nested Compositions. This Mechanism will be deactivated until {component.name} is "
f"added to {self.name} or one of its nested Compositions in a compatible way."
f"The controller of '{self.name}' has a specification that includes the Mechanism "
f"'{component.name}', but '{component.name}' is not in '{self.name}' or any of its "
f"nested Compositions. This Mechanism will be deactivated until '{component.name}' is "
f"added to '{self.name}' or one of its nested Compositions in a compatible way."
)
assert False, "WARNING MESSAGE"

# If Composition is not preparing to execute, allow deferred_inits to persist without warning
if context and ContextFlags.PREPARING not in context.execution_phase:
Expand All @@ -7912,10 +7926,11 @@ def _check_controller_initialization_status(self, context=None):
for projection in node.projections:
if projection.initialization_status == ContextFlags.DEFERRED_INIT:
if isinstance(projection, ControlProjection):
warnings.warn(f"The {projection.receiver.name} parameter of {projection.receiver.owner.name} \n"
f"is specified for control, but {self.name} does not have a controller. Please \n"
f"add a controller to {self.name} or the control specification will be \n"
f"ignored.")
warnings.warn(f"The '{projection.receiver.name}' parameter of "
f"'{projection.receiver.owner.name}' is specified for control, "
f"but the {COMPOSITION} it is in ('{self.name}') does not have a controller; "
f"if a controller is not added to {self.name} "
f"the control specification will be ignored.")

def _check_nodes_initialization_status(self, context=None):

Expand Down
4 changes: 2 additions & 2 deletions tests/composition/test_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,8 @@ def test_unused_projections_warning(self):
with pytest.warns(UserWarning) as warning:
ocomp = Composition(name='OUTER COMPOSITION',pathways=[comp1, comp2])
ocomp.run()
assert repr(warning[0].message.args[0]) == '"\\nThe following Projections were specified but are not being used by Nodes in \'iCOMP\':\\n\\tMappingProjection from A[OutputPort-0] to C[InputPort-0] (from \'A\' to \'C\')"'
assert repr(warning[1].message.args[0]) == '"\\nThe following Projections were specified but are not being used by Nodes in \'COMP_2\':\\n\\tMappingProjection from A[OutputPort-0] to C[InputPort-0] (to \'C\' from \'A\')"'
assert repr(warning[0].message.args[0]) == '"\\nThe following Projections were specified but are not being used by Nodes in \'iCOMP\':\\n\\tMappingProjection from A[OutputPort-0] to C[InputPort-0] (from \'A\' to \'C\')."'
assert repr(warning[1].message.args[0]) == '"\\nThe following Projections were specified but are not being used by Nodes in \'COMP_2\':\\n\\tMappingProjection from A[OutputPort-0] to C[InputPort-0] (to \'C\' from \'A\')."'


class TestPathway:
Expand Down
58 changes: 49 additions & 9 deletions tests/composition/test_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ class TestControlSpecification:
# 2) specification of control in controller supercedes any conflicting specification on a node;
# 3) order of addition to the composition does not matter (i.e., Principle 2 always applies)

# FIX: OUTSTANDING ISSUES -
# When control is specified in a controller for a Mechanism that is not yet a node in the Composition
# it nevertheless gets activated (in call to controller._activate_projections_for_compositions;
# instead, it should either be put in deferred_init or added to node's aux_components attribute

def test_add_node_with_control_specified_then_add_controller(self):
# First add Mechanism with control specification to Composition,
# then add controller with NO control specification to Composition
Expand Down Expand Up @@ -89,28 +84,53 @@ def test_redundant_control_spec_add_controller_in_comp_constructor_then_add_node
pnl.ControlProjection(
function=pnl.Linear,
control_signal_params={ALLOCATION_SAMPLES: np.arange(0.1, 1.01, 0.3)}))))
comp = pnl.Composition(controller=pnl.ControlMechanism(control_signals=("drift_rate", ddm)))
expected_warning = "The controller of 'Composition-0' has been specified to project to 'DDM-0', but 'DDM-0' " \
"is not in 'Composition-0' or any of its nested Compositions. This projection will be " \
"deactivated until 'DDM-0' is added to' Composition-0' in a compatible way."
with pytest.warns(UserWarning, match=expected_warning):
comp = pnl.Composition(controller=pnl.ControlMechanism(control_signals=("drift_rate", ddm)))
comp.add_node(ddm)
assert comp.controller.control_signals[0].efferents[0].receiver == ddm.parameter_ports['drift_rate']
assert ddm.parameter_ports['drift_rate'].mod_afferents[0].sender.owner == comp.controller
assert comp.controller.control_signals[0].allocation_samples is None

def test_redundant_control_spec_add_controller_in_comp_constructor_then_add_node_with_alloc_samples_specified(self):
# First create Composition with controller that has HAS control specification,
# First create Composition with controller that has HAS control specification that includes allocation_samples,
# then add Mechanism with control specification to Composition;
# Control specification on controller should supercede one on Mechanism (which should be ignored)
ddm = pnl.DDM(function=pnl.DriftDiffusionAnalytical(
drift_rate=(1.0,
pnl.ControlProjection(
function=pnl.Linear,
control_signal_params={ALLOCATION_SAMPLES: np.arange(0.1, 1.01,0.3)}))))
comp = pnl.Composition(controller=pnl.ControlMechanism(control_signals={ALLOCATION_SAMPLES:np.arange(0.2,1.01, 0.3),
PROJECTIONS:('drift_rate', ddm)}))
expected_warning = "The controller of 'Composition-0' has been specified to project to 'DDM-0', but 'DDM-0' " \
"is not in 'Composition-0' or any of its nested Compositions. This projection will be " \
"deactivated until 'DDM-0' is added to' Composition-0' in a compatible way."
with pytest.warns(UserWarning, match=expected_warning):
comp = pnl.Composition(controller=pnl.ControlMechanism(control_signals={ALLOCATION_SAMPLES:np.arange(0.2,1.01, 0.3),
PROJECTIONS:('drift_rate', ddm)}))
comp.add_node(ddm)
assert comp.controller.control_signals[0].efferents[0].receiver == ddm.parameter_ports['drift_rate']
assert ddm.parameter_ports['drift_rate'].mod_afferents[0].sender.owner == comp.controller
assert np.allclose(comp.controller.control[0].allocation_samples(), [0.2, 0.5, 0.8])

# def test_missing_mech_referenced_by_controller_warning(self):
# mech = pnl.ProcessingMechanism()
# warning_msg_1 = ''
# with pytest.warns(UserWarning) as warning:
# comp = pnl.Composition(controller=pnl.ControlMechanism(objective_mechanism=mech))
# assert repr(warning[1].message.args[0]) == warning_msg_1

def test_bad_objective_mechanism_spec(self):
mech = pnl.ProcessingMechanism()
expected_error = 'Specification of objective_mechanism arg for \'ControlMechanism-0\' ' \
'(ProcessingMechanism-0) must be an ObjectiveMechanism or a list of Mechanisms ' \
'and/or OutputPorts to be monitored for control.'
with pytest.raises(pnl.ControlMechanismError) as error:
comp = pnl.Composition(controller=pnl.ControlMechanism(objective_mechanism=mech))
error_msg = error.value.error_value
assert expected_error in error_msg

def test_deferred_init(self):
# Test to insure controller works the same regardless of whether it is added to a composition before or after
# the nodes it connects to
Expand Down Expand Up @@ -1331,6 +1351,26 @@ def test_control_of_mech_port(self, comp_mode):
results = comp.run(inputs=inputs, num_trials=1, execution_mode=comp_mode)
assert np.allclose(comp.results, [[[0.375]]])

@pytest.mark.control
@pytest.mark.composition
def test_add_node_with_controller_spec_and_control_mech_but_not_a_controller(self):
mech = pnl.ProcessingMechanism(name='MECH', function=pnl.Linear(slope=(2, pnl.CONTROL)))
ctl = pnl.ControlMechanism(name='CONTROL MECHANISM')
warning_msg_1 = '"OutputPort (\'ControlSignal-0\') of \'CONTROL MECHANISM\' doesn\'t have any efferent ' \
'Projections in \'COMPOSITION\'."'
warning_msg_4 = '"\\nThe following Projections were specified but are not being used by Nodes in ' \
'\'COMPOSITION\':\\n\\tControlProjection for MECH[slope]"'
warning_msg_5 = '"The \'slope\' parameter of \'MECH\' is specified for control, but the Composition it is in ' \
'(\'COMPOSITION\') does not have a controller; if a controller is not added to COMPOSITION ' \
'the control specification will be ignored."'
with pytest.warns(UserWarning) as warning:
comp = pnl.Composition(name='COMPOSITION', pathways=[ctl])
comp.add_node(mech)
comp.run()
assert repr(warning[1].message.args[0]) == warning_msg_1
assert repr(warning[4].message.args[0]) == warning_msg_4
assert repr(warning[5].message.args[0]) == warning_msg_5

@pytest.mark.control
@pytest.mark.composition
@pytest.mark.parametrize("cost, expected, exp_values", [
Expand Down

0 comments on commit 9da53b9

Please sign in to comment.