From e0f9fbd05b406129d2e100fbde15beed8343bcaa Mon Sep 17 00:00:00 2001 From: jdcpni Date: Tue, 16 Jan 2018 22:15:55 -0500 Subject: [PATCH] Fix/controlmechanism/assign as controller (#620) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * • System - show_graph(): fixed bug producing empty image for graphs with just one Mechanism added auto-recurrent projections * • Log - added numpy_array output method * - * - * - * - * - * - * - * - * - * - * - * - * - * • Log - fixed bugs preventing logging during INITIALIZATION * • Log - fixed bugs preventing logging during INITIALIZATION * - * - * - * - * - * • Log - logged_item, print_entries: corrected to use 'value' rather than owner's name in reports * • Log - logged_item, print_entries: corrected to use 'value' rather than owner's name in reports • Tests test_log: added test_log_initialization * • Log - _alias_owner_name: added to used 'value' rather than owner's name in reports - nparray: bug fix to handle None values • Tests test_log: added test_log_initialization * • Log - log_value: added • Tests - test_multilayer: added test of log_value * • Log   - log_value: implemented   - logged_item, print_entries:  corrected to use 'value' rather than owner's name in reports • Tests   - test_multilayer: added test of log_value * • Log   - log_value: implemented   - logged_item, print_entries:  corrected to use 'value' rather than owner's name in reports • Tests   - test_multilayer: added test of log_value * - * • Component - moved value property to Component (from Mechanism, Projection and State) (left an override on ControlSignal that needs it for the getter) * • Log - docstring: added hint about using call_before_trial and call_after_trial to log values * - * - * • ObjectiveMechanism _instantiate_input_states(): corrected to used monitored_output_state_specs * • System _instantiate_controller(): added assignment of controller.control_signals to self.control_signals * - * • ControlMechanism _instantiate_output_states(): modifed to not instantiate a default OutputState if no ControlSignals are specified * • ControlMechanism _instantiate_output_states(): reverted to ordinary instantation of default ControlSignal if none specified * • ControlMechanism _instantiate_output_states(): reverted to ordinary instantation of default ControlSignal if none specified * • System _get_monitored_output_states_for_system(): fixed bug in which string and tuple specifications were ignored * • System _get_monitored_output_states_for_system(): fixed bug in which string and tuple specifications were ignored * • System _get_monitored_output_states_for_system(): fixed bug in which string and tuple specifications were ignored * - * - * - * • Function LinearCombination()._validate_params: fixed check on length of weight and exponents * • Function LinearCombination()._validate_params: fixed check on length of weight and exponents * • IntegratorMechanism - added input_states as argument to constructor * • IntegratorMechanism - added input_states as argument to constructor * • IntegratorMechanism - added input_states as argument to constructor • System, ControlMechanism, EVCControlMechanism - clean-up for assign_as_controller * - --- .../adaptive/control/controlmechanism.py | 5 -- .../processing/integratormechanism.py | 2 + .../processing/objectivemechanism.py | 47 ++++++------ .../components/projections/projection.py | 4 +- psyneulink/components/system.py | 17 ++--- psyneulink/globals/keywords.py | 4 +- .../subsystems/evc/evccontrolmechanism.py | 75 ++++++++----------- 7 files changed, 68 insertions(+), 86 deletions(-) diff --git a/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py b/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py index a274c26019d..5230573b7cd 100644 --- a/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py +++ b/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py @@ -1043,13 +1043,8 @@ def assign_as_controller(self, system:System_Base, context=COMMAND_LINE): # Flag ObjectiveMechanism as associated with a ControlMechanism that is a controller for the System self._objective_mechanism.controller = True - # Finally, assign the self as controller for system - # # MODIFIED 1/14/18 OLD: - # system.controller = self - # MODIFIED 1/14/18 NEW: if context != 'System.controller setter': system._controller = self - # MODIFIED 1/14/18 END @property def monitored_output_states(self): diff --git a/psyneulink/components/mechanisms/processing/integratormechanism.py b/psyneulink/components/mechanisms/processing/integratormechanism.py index 19d2ce61243..b39b5846820 100644 --- a/psyneulink/components/mechanisms/processing/integratormechanism.py +++ b/psyneulink/components/mechanisms/processing/integratormechanism.py @@ -196,6 +196,7 @@ class ClassDefaults(ProcessingMechanism_Base.ClassDefaults): def __init__(self, default_variable=None, size=None, + input_states:tc.optional(tc.any(list, dict))=None, function=AdaptiveIntegrator(rate=0.5), params=None, name=None, @@ -219,6 +220,7 @@ def __init__(self, super(IntegratorMechanism, self).__init__(variable=default_variable, size=size, + input_states=input_states, params=params, name=name, prefs=prefs, diff --git a/psyneulink/components/mechanisms/processing/objectivemechanism.py b/psyneulink/components/mechanisms/processing/objectivemechanism.py index eed7d1c3863..cce37c633bf 100644 --- a/psyneulink/components/mechanisms/processing/objectivemechanism.py +++ b/psyneulink/components/mechanisms/processing/objectivemechanism.py @@ -687,11 +687,10 @@ def add_monitored_output_states(self, monitored_output_states_specs, context=Non """ monitored_output_states_specs = list(monitored_output_states_specs) - # If ObjectiveMechanism has only its default InputState and it has no afferent Projections: + # If ObjectiveMechanism has only its default InputState and that has no afferent Projections: # delete it and first item of variable if len(self.input_states)==1 and self.input_state.name=='InputState-0' and not self.input_state.path_afferents: del self.input_states[0] - # FIX: 1/14/18 - CHECK WITH KEVIN WHETHER THIS IS THE THING TO DO HERE self.instance_defaults.variable = [] self._update_variable(self.instance_defaults.variable) @@ -704,30 +703,28 @@ def add_monitored_output_states(self, monitored_output_states_specs, context=Non # If it is a MonitoredOutputStateTuple, create InputState specification dictionary if isinstance(spec, MonitoredOutputStateTuple): - # Create InputState specification dictionary: - monitored_output_states_specs[i] = {NAME: spec.output_state.name, - VARIABLE: spec.output_state.value, - WEIGHT: spec.weight, - EXPONENT: spec.exponent, - PROJECTIONS: [(spec.output_state, spec.matrix)]} - reference_value.append(spec.output_state.value) - + spec = {NAME: spec.output_state.name, + VARIABLE: spec.output_state.value, + WEIGHT: spec.weight, + EXPONENT: spec.exponent, + PROJECTIONS: [(spec.output_state, spec.matrix)]} + monitored_output_states_specs[i] = spec + + # Parse spec to get value of OutputState and (possibly) the Projection from it + input_state = _parse_state_spec(owner=self, state_type = InputState, state_spec=spec) + + # There should be only one ProjectionTuple specified, + # that designates the OutputState and (possibly) a Projection from it + if len(input_state[PARAMS][PROJECTIONS])!=1: + raise ObjectiveMechanismError("PROGRAM ERROR: Failure to parse item in monitored_output_states_specs " + "for {} (item: {})".format(self.name, spec)) + projection_tuple = input_state[PARAMS][PROJECTIONS][0] + # If Projection is specified, use its value + if PROJECTION in projection_tuple.projection: + reference_value.append(projection_tuple.projection[PROJECTION].value) + # Otherwise, use its sender's (OutputState) value else: - # Otherwise, parse spec to get value of OutputState and (possibly) the Projection from it - input_state = _parse_state_spec(owner=self, state_type = InputState, state_spec=spec) - - # There should be only one ProjectionTuple specified, - # that designates the OutputState and (possibly) a Projection from it - if len(input_state[PARAMS][PROJECTIONS])!=1: - raise ObjectiveMechanismError("PROGRAM ERROR: Failure to parse item in monitored_output_states_specs " - "for {} (item: {})".format(self.name, spec)) - projection_tuple = input_state[PARAMS][PROJECTIONS][0] - # If Projection is specified, use its value - if PROJECTION in projection_tuple.projection: - reference_value.append(projection_tuple.projection[PROJECTION].value) - # Otherwise, use its sender's (OutputState) value - else: - reference_value.append(projection_tuple.state.value) + reference_value.append(projection_tuple.state.value) input_states = self._instantiate_input_states(monitored_output_states_specs=monitored_output_states_specs, reference_value=reference_value, diff --git a/psyneulink/components/projections/projection.py b/psyneulink/components/projections/projection.py index ad466f427fc..d0475818724 100644 --- a/psyneulink/components/projections/projection.py +++ b/psyneulink/components/projections/projection.py @@ -1070,8 +1070,8 @@ def _parse_projection_spec(projection_spec, All keys in kwargs must be from PROJECTION_ARGS - If projection_spec is or resolves to a Projection object, returns State object. - Otherwise, return State specification dictionary using any arguments provided as defaults + If projection_spec is or resolves to a Projection object, returns Projection object. + Otherwise, return Projection specification dictionary using any arguments provided as defaults """ bad_arg = next((key for key in kwargs if not key in PROJECTION_ARGS), None) diff --git a/psyneulink/components/system.py b/psyneulink/components/system.py index c7ece30465b..616ffba2004 100644 --- a/psyneulink/components/system.py +++ b/psyneulink/components/system.py @@ -2063,8 +2063,9 @@ def _get_monitored_output_states_for_system(self, controller=None, context=None) # - a MonitoredOutputStatesTuple (returned by _get_monitored_states_for_system when # specs were initially processed by the System to parse its *monitor_for_control* argument; # - a specification for an existing Mechanism or OutputStates from the *monitor_for_control* arg of System. - all_specs_extracted_from_tuples = [] - for i, spec in enumerate(all_specs.copy()): + all_specs_extracted_from_tuples=[] + all_specs_parsed=[] + for i, spec in enumerate(all_specs): # Leave MonitoredOutputStatesOption and MonitoredOutputStatesTuple spec in place; # these are parsed later on @@ -2137,17 +2138,11 @@ def _get_monitored_output_states_for_system(self, controller=None, context=None) "is not a recognized specification for an {}". format(MONITOR_FOR_CONTROL, self.name, spec, OutputState.__name__)) - # Delete original item of all_specs, and assign ones parsed into monitored_output_state_tuple(s) - del all_specs[i] - # # MODIFIED 1/15/17 OLD: - # all_specs.insert(i, monitored_output_state_tuples) - # MODIFIED 1/15/17 NEW: - all_specs = insert_list(all_specs, i, monitored_output_state_tuples) - # MODIFIED 1/15/17 END - - + all_specs_parsed.extend(monitored_output_state_tuples) all_specs_extracted_from_tuples.extend([item.output_state for item in monitored_output_state_tuples]) + all_specs = all_specs_parsed + try: all (isinstance(item, (OutputState, MonitoredOutputStatesOption)) for item in all_specs_extracted_from_tuples) diff --git a/psyneulink/globals/keywords.py b/psyneulink/globals/keywords.py index 2c2a5455ca4..bba95faea38 100644 --- a/psyneulink/globals/keywords.py +++ b/psyneulink/globals/keywords.py @@ -74,7 +74,8 @@ 'OFF', 'OFFSET', 'ON', 'OPERATION', 'ORIGIN', 'ORNSTEIN_UHLENBECK_INTEGRATOR_FUNCTION', 'OUTCOME_FUNCTION', 'OUTPUT_STATE', 'OUTPUT_STATE_PARAMS', 'OUTPUT_STATES', 'OUTPUT_TYPE', 'OWNER', 'PARAM_CLASS_DEFAULTS', 'PARAM_INSTANCE_DEFAULTS', 'PARAMETER_STATE', 'PARAMETER_STATE_PARAMS', 'PARAMETER_STATES', 'PARAMS', - 'PARAMS_CURRENT', 'PATHWAY', 'PATHWAY_PROJECTION', 'PEARSON', 'PREDICTION_MECHANISM', 'PREDICTION_MECHANISM_OUTPUT', + 'PARAMS_CURRENT', 'PATHWAY', 'PATHWAY_PROJECTION', 'PEARSON', + 'PREDICTION_MECHANISM', 'PREDICTION_MECHANISMS', 'PREDICTION_MECHANISM_OUTPUT', 'PREDICTION_MECHANISM_PARAMS', 'PREDICTION_MECHANISM_TYPE', 'PREFS_ARG', 'PRIMARY', 'PROB', 'PROCESS', 'PROCESSING', 'PROCESS_INIT', 'PROCESSES', 'PROCESSES_DIM', 'PROCESSING_MECHANISM', 'PRODUCT', 'PROJECTION', 'PROJECTION_PARAMS', 'PROJECTION_SENDER', 'PROJECTION_SENDER_VALUE', 'PROJECTION_TYPE', 'PROJECTIONS', @@ -662,6 +663,7 @@ def _is_metric(metric): OBJECTIVE_MECHANISM = "objective_mechanism" MONITOR_FOR_CONTROL = "monitor_for_control" PREDICTION_MECHANISM = "Prediction Mechanism" +PREDICTION_MECHANISMS = "prediction_mechanisms" PREDICTION_MECHANISM_TYPE = "prediction_mechanism_type" PREDICTION_MECHANISM_PARAMS = "prediction_mechanism_params" PREDICTION_MECHANISM_OUTPUT = "PredictionMechanismOutput" diff --git a/psyneulink/library/subsystems/evc/evccontrolmechanism.py b/psyneulink/library/subsystems/evc/evccontrolmechanism.py index c36a2fef533..04956b9afb0 100644 --- a/psyneulink/library/subsystems/evc/evccontrolmechanism.py +++ b/psyneulink/library/subsystems/evc/evccontrolmechanism.py @@ -337,7 +337,8 @@ from psyneulink.components.shellclasses import Function, System_Base from psyneulink.globals.defaults import defaultControlAllocation from psyneulink.globals.keywords import COMMAND_LINE, CONTROL, COST_FUNCTION, EVC_MECHANISM, FUNCTION, INITIALIZING, \ - INIT_FUNCTION_METHOD_ONLY, PARAMETER_STATES, PREDICTION_MECHANISM, PREDICTION_MECHANISM_PARAMS, PREDICTION_MECHANISM_TYPE, SUM + INIT_FUNCTION_METHOD_ONLY, PARAMETER_STATES, PREDICTION_MECHANISM, PREDICTION_MECHANISMS, \ + PREDICTION_MECHANISM_PARAMS, PREDICTION_MECHANISM_TYPE, SUM from psyneulink.globals.preferences.componentpreferenceset import is_pref_set from psyneulink.globals.preferences.preferenceset import PreferenceLevel from psyneulink.globals.utilities import ContentAddressableList @@ -740,25 +741,25 @@ def _instantiate_input_states(self, context=None): """Instantiate PredictionMechanisms """ if self.system is not None: - self._instantiate_prediction_mechanisms(context=context) + self._instantiate_prediction_mechanisms(system=self.system, context=context) super()._instantiate_input_states(context=context) - def _instantiate_prediction_mechanisms(self, context=None): - """Add prediction Mechanism and associated process for each `ORIGIN` (input) Mechanism in the System + def _instantiate_prediction_mechanisms(self, system:System_Base, context=None): + """Add prediction Mechanism and associated process for each `ORIGIN` (input) Mechanism in system - Instantiate prediction_mechanisms for `ORIGIN` Mechanisms in self.system; these will now be `TERMINAL` + Instantiate prediction_mechanisms for `ORIGIN` Mechanisms in system; these will now be `TERMINAL` Mechanisms: - if their associated input mechanisms were TERMINAL MECHANISMS, they will no longer be so; therefore... - if an associated input Mechanism must be monitored by the EVCControlMechanism, it must be specified - explicitly in an OutputState, Mechanism, controller or System OBJECTIVE_MECHANISM param (see below) + explicitly in an OutputState, Mechanism, controller or system OBJECTIVE_MECHANISM param (see below) - For each `ORIGIN` Mechanism in self.system: + For each `ORIGIN` Mechanism in system: - instantiate a corresponding predictionMechanism - instantiate a Process, with a pathway that projects from the ORIGIN to the prediction Mechanism - - add the process to self.system.processes + - add the Process to system.processes Instantiate self.predicted_input dict: - - key for each entry is an `ORIGIN` Mechanism of the System + - key for each entry is an `ORIGIN` Mechanism of system - value of each entry is the value of the corresponding predictionMechanism: each value is a 2d array, each item of which is the value of an InputState of the predictionMechanism @@ -766,11 +767,17 @@ def _instantiate_prediction_mechanisms(self, context=None): context: """ + # FIX: 1/16/18 - Should should check for any new origin_mechs? What if origin_mech deleted? + # If system's controller already has prediction_mechanisms, use those + if hasattr(system.controller, PREDICTION_MECHANISMS): + self.prediction_mechanisms = system.controller.prediction_mechanisms + self.origin_prediction_mechanisms = system.controller.origin_prediction_mechanisms + self.predicted_input = system.controller.predicted_input + return + # Dictionary of prediction_mechanisms, keyed by the ORIGIN Mechanism to which they correspond self.origin_prediction_mechanisms = {} - # self.predictionProcesses = [] - # List of prediction Mechanism tuples (used by system to execute them) self.prediction_mechs = [] @@ -780,23 +787,18 @@ def _instantiate_prediction_mechanisms(self, context=None): except KeyError: prediction_mechanism_params = {} + for origin_mech in system.origin_mechanisms.mechanisms: + state_names = [] + variable = [] + for state_name in origin_mech.input_states.names: + state_names.append(state_name) + variable.append(origin_mech.input_states[state_name].instance_defaults.variable) - for origin_mech in self.system.origin_mechanisms.mechanisms: - # FIX: 1/15/18 - # # IMPLEMENT THE FOLLOWING ONCE INPUT_STATES CAN BE SPECIFIED IN CONSTRUCTION OF ALL MECHANISMS - # # (AS THEY CAN CURRENTLY FOR ObjectiveMechanisms) - # state_names = [] - # variables = [] - # for state_name in origin_mech.input_states.keys(): - # state_names.append(state_name) - # variables.append(origin_mech_intputStates[state_name].instance_defaults.variable) - - # Instantiate predictionMechanism + # Instantiate PredictionMechanism prediction_mechanism = self.paramsCurrent[PREDICTION_MECHANISM_TYPE]( name=origin_mech.name + " " + PREDICTION_MECHANISM, - default_variable = origin_mech.input_state.instance_defaults.variable, - # default_variable=variables, - # INPUT_STATES=state_names, + default_variable=variable, + input_states=state_names, params = prediction_mechanism_params, context=context, ) @@ -826,17 +828,16 @@ def _instantiate_prediction_mechanisms(self, context=None): self.prediction_mechs.append(prediction_mechanism) # Add to system execution_graph and execution_list - self.system.execution_graph[prediction_mechanism] = set() - self.system.execution_list.append(prediction_mechanism) + system.execution_graph[prediction_mechanism] = set() + system.execution_list.append(prediction_mechanism) self.prediction_mechanisms = MechanismList(self, self.prediction_mechs) # Assign list of destinations for predicted_inputs: - # the variable of the ORIGIN Mechanism for each process in the system + # the variable of the ORIGIN Mechanism for each Process in the system self.predicted_input = {} - for i, origin_mech in zip(range(len(self.system.origin_mechanisms)), self.system.origin_mechanisms): - # self.predicted_input[origin_mech] = self.system.processes[i].origin_mechanisms[0].input_value - self.predicted_input[origin_mech] = self.system.processes[i].origin_mechanisms[0].instance_defaults.variable + for i, origin_mech in zip(range(len(system.origin_mechanisms)), system.origin_mechanisms): + self.predicted_input[origin_mech] = system.processes[i].origin_mechanisms[0].instance_defaults.variable def _instantiate_attributes_after_function(self, context=None): @@ -871,18 +872,8 @@ def _instantiate_attributes_after_function(self, context=None): @tc.typecheck def assign_as_controller(self, system:System_Base, context=COMMAND_LINE): - # # MODIFIED 1/15/18 OLD: - # super().assign_as_controller(system=system, context=context) - # self._instantiate_prediction_mechanisms(context=context) - # MODIFIED 1/15/18 NEW: - if system.controller.prediction_mechanisms: - self.prediction_mechanisms = system.controller.prediction_mechanisms - self.origin_prediction_mechanisms = system.controller.origin_prediction_mechanisms - self.predicted_input = system.controller.predicted_input - else: - self._instantiate_prediction_mechanisms(context=context) + self._instantiate_prediction_mechanisms(system=system, context=context) super().assign_as_controller(system=system, context=context) - # MODIFIED 1/15/18 END def _execute(self, variable=None,