diff --git a/Scripts/Examples/EVC-Gratton.py b/Scripts/Examples/EVC-Gratton.py index af61aebda4..c69dfa0f09 100644 --- a/Scripts/Examples/EVC-Gratton.py +++ b/Scripts/Examples/EVC-Gratton.py @@ -139,10 +139,18 @@ name='EVC Gratton System' ) +# mySystem.show_graph(show_control=pnl.ALL, show_dimensions=pnl.ALL) + + +control_mech = pnl.EVCControlMechanism(name='NEW CONTROLLER') +mySystem.controller = control_mech +# control_mech.assign_as_controller(mySystem) + +# mySystem.show_graph(show_control=pnl.ALL, show_dimensions=pnl.ALL) + # Show characteristics of system: mySystem.show() mySystem.controller.show() -# mySystem.show_graph(show_control=pnl.ALL, show_dimensions=pnl.ALL) # configure EVC components mySystem.controller.control_signals[0].intensity_cost_function = pnl.Exponential(rate=0.8046).function diff --git a/psyneulink/components/functions/function.py b/psyneulink/components/functions/function.py index 2e356f4fa4..3f504a8f7f 100644 --- a/psyneulink/components/functions/function.py +++ b/psyneulink/components/functions/function.py @@ -1572,7 +1572,7 @@ def _validate_params(self, request_set, target_set=None, context=None): if EXPONENTS in target_set and target_set[EXPONENTS] is not None: self._validate_parameter_spec(target_set[EXPONENTS], EXPONENTS, numeric_only=True) target_set[EXPONENTS] = np.atleast_2d(target_set[EXPONENTS]).reshape(-1, 1) - if (c in context for c in {EXECUTING, LEARNING}): + if any(c in context for c in {EXECUTING, LEARNING}): if len(target_set[EXPONENTS]) != len(self.instance_defaults.variable): raise FunctionError("Number of exponents ({0}) does not equal number of items in variable ({1})". format(len(target_set[EXPONENTS]), len(self.instance_defaults.variable.shape))) diff --git a/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py b/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py index 85196a8d77..a274c26019 100644 --- a/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py +++ b/psyneulink/components/mechanisms/adaptive/control/controlmechanism.py @@ -313,7 +313,9 @@ from psyneulink.components.states.modulatorysignals.controlsignal import ControlSignal from psyneulink.components.states.outputstate import INDEX, SEQUENTIAL from psyneulink.globals.defaults import defaultControlAllocation -from psyneulink.globals.keywords import AUTO_ASSIGN_MATRIX, CONTROL, CONTROL_PROJECTION, CONTROL_PROJECTIONS, CONTROL_SIGNAL, CONTROL_SIGNALS, EXPONENT, INIT__EXECUTE__METHOD_ONLY, NAME, OBJECTIVE_MECHANISM, PRODUCT, PROJECTIONS, PROJECTION_TYPE, SYSTEM, VARIABLE, WEIGHT +from psyneulink.globals.keywords import AUTO_ASSIGN_MATRIX, CONTROL, CONTROL_PROJECTION, CONTROL_PROJECTIONS, \ + CONTROL_SIGNAL, CONTROL_SIGNALS, EXPONENT, INIT__EXECUTE__METHOD_ONLY, NAME, OBJECTIVE_MECHANISM, PRODUCT, \ + PROJECTIONS, PROJECTION_TYPE, SYSTEM, VARIABLE, WEIGHT, COMMAND_LINE from psyneulink.globals.preferences.componentpreferenceset import is_pref_set from psyneulink.globals.preferences.preferenceset import PreferenceLevel from psyneulink.globals.utilities import ContentAddressableList @@ -647,7 +649,7 @@ def _validate_params(self, request_set, target_set=None, context=None): # If ControlMechanism has been assigned to a System, check that # all the items in the list used to specify objective_mechanism are in the same System if self.system: - self.system._validate_monitored_state_in_system([spec], context=context) + self.system._validate_monitored_states_in_system([spec], context=context) if not isinstance(target_set[OBJECTIVE_MECHANISM], (ObjectiveMechanism, list)): raise ControlMechanismError("Specification of {} arg for {} ({}) must be an {}" @@ -719,7 +721,7 @@ def _instantiate_objective_mechanism(self, context=None): # INSTANTIATE ObjectiveMechanism - # If *objective_mechanism* argument si an ObjectiveMechanism, add monitored_output_states to it + # If *objective_mechanism* argument is an ObjectiveMechanism, add monitored_output_states to it if isinstance(self.objective_mechanism, ObjectiveMechanism): if monitored_output_states: self.objective_mechanism.add_monitored_output_states( @@ -788,9 +790,6 @@ def _instantiate_output_states(self, context=None): # --------------------------------------------------- if self.control_signals: - - - self._output_states = [] # for i, control_signal in enumerate(self.control_signals): @@ -800,7 +799,7 @@ def _instantiate_output_states(self, context=None): super()._instantiate_output_states(context=context) - # Reassign control_signals to capture any user_defined ControlSignals instantiated by in call to super + # Reassign control_signals to capture any user_defined ControlSignals instantiated in call to super # and assign to ContentAddressableList self._control_signals = ContentAddressableList(component_type=ControlSignal, list=[state for state in self.output_states @@ -885,8 +884,6 @@ def _instantiate_control_signal(self, control_signal, context=None): return control_signal - - def _execute(self, variable=None, runtime_params=None, @@ -938,25 +935,32 @@ def add_monitored_output_states(self, monitored_output_states, context=None): """Instantiate OutputStates to be monitored by ControlMechanism's `objective_mechanism `. - **monitored_output_states** can be a `Mechanism`, `OutputState`, `tuple specification - `, a `State specification dicionary `, - or list with any of these. If item is a Mechanism, its `primary OutputState ` is used. + **monitored_output_states** can be any of the following: + - `Mechanism`; + - `OutputState`; + - `tuple specification `; + - `State specification dictionary `; + - list with any of the above. + If any item is a Mechanism, its `primary OutputState ` is used. OutputStates must belong to Mechanisms in the same `System` as the ControlMechanism. """ output_states = self.objective_mechanism.add_monitored_output_states( monitored_output_states_specs=monitored_output_states, context=context) if self.system: - self.system._validate_monitored_state_in_system(output_states, context=context) + self.system._validate_monitored_states_in_system(output_states, context=context) @tc.typecheck - def assign_as_controller(self, system:System_Base, context=None): + def assign_as_controller(self, system:System_Base, context=COMMAND_LINE): """Assign ControlMechanism as `controller ` for a `System`. **system** must be a System for which the ControlMechanism should be assigned as the `controller - `; if the specified System already has a `controller `, - it will be replaced by the current one; if the current one is already the `controller ` - for another System, it will be disabled for that System. + `. + If the specified System already has a `controller `, it will be replaced by the current + one, and the current one will inherit any ControlSignals previously specified for the old controller or the + System itself. + If the current one is already the `controller ` for another System, it will be disabled + for that System. COMMENT: [TBI: The ControlMechanism's `objective_mechanism `, @@ -980,6 +984,10 @@ def assign_as_controller(self, system:System_Base, context=None): COMMENT """ + if context==COMMAND_LINE: + system.controller = self + return + # NEED TO BUFFER OBJECTIVE_MECHANISM AND CONTROL_SIGNAL ARGUMENTS FOR USE IN REINSTANTIATION HERE # DETACH AS CONTROLLER FOR ANY EXISTING SYSTEM (AND SET THAT ONE'S CONTROLLER ATTRIBUTE TO None) # DELETE ALL EXISTING OBJECTIVE_MECHANISM AND CONTROL_SIGNAL ASSIGNMENTS @@ -990,7 +998,7 @@ def assign_as_controller(self, system:System_Base, context=None): # First, validate that all of the ControlMechanism's monitored_output_states and controlled parameters # are in the new System - system._validate_monitored_state_in_system(self.monitored_output_states) + system._validate_monitored_states_in_system(self.monitored_output_states) system._validate_control_signals(self.control_signals) # Next, get any OutputStates specified in the **monitored_output_states** argument of the System's @@ -1000,10 +1008,28 @@ def assign_as_controller(self, system:System_Base, context=None): monitored_output_states = list(system._get_monitored_output_states_for_system(controller=self, context=context)) self.add_monitored_output_states(monitored_output_states) - # Then, assign it ControlSignals for any parameters in the current System specified for control - system_control_signals = system._get_control_signals_for_system(system.control_signals, context=context) + # The system does NOT already have a controller, + # so assign it ControlSignals for any parameters in the System specified for control + if system.controller is None: + system_control_signals = system._get_control_signals_for_system(system.control_signals, context=context) + # The system DOES already have a controller, + # so assign it the old controller's ControlSignals + else: + system_control_signals = system.control_signals + for control_signal in system_control_signals: + control_signal.owner = None + # Get rid of default ControlSignal if it has no ControlProjections + if (len(self.control_signals)==1 + and self.control_signals[0].name=='ControlSignal-0' + and not self.control_signals[0].efferents): + del self._output_states[0] + del self.control_signals[0] + self.allocation_policy = None + for control_signal_spec in system_control_signals: - self._instantiate_control_signal(control_signal=control_signal_spec, context=context) + control_signal = self._instantiate_control_signal(control_signal=control_signal_spec, context=context) + control_signal.owner = self + self.control_signals.append(control_signal) # If it HAS been assigned a System, make sure it is the current one if self.system and not self.system is system: @@ -1017,6 +1043,14 @@ def assign_as_controller(self, system:System_Base, context=None): # 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): try: diff --git a/psyneulink/components/mechanisms/mechanism.py b/psyneulink/components/mechanisms/mechanism.py index 3c8a6bfb6c..579fed6b1f 100644 --- a/psyneulink/components/mechanisms/mechanism.py +++ b/psyneulink/components/mechanisms/mechanism.py @@ -1761,14 +1761,17 @@ def _instantiate_attributes_after_function(self, context=None): self._instantiate_output_states(context=context) super()._instantiate_attributes_after_function(context=context) - def _instantiate_input_states(self, input_states=None, context=None): + def _instantiate_input_states(self, input_states=None, reference_value=None, context=None): """Call State._instantiate_input_states to instantiate orderedDict of InputState(s) This is a stub, implemented to allow Mechanism subclasses to override _instantiate_input_states or process InputStates before and/or after call to _instantiate_input_states """ from psyneulink.components.states.inputstate import _instantiate_input_states - return _instantiate_input_states(owner=self, input_states=input_states or self.input_states, context=context) + return _instantiate_input_states(owner=self, + input_states=input_states or self.input_states, + reference_value=reference_value, + context=context) def _instantiate_parameter_states(self, context=None): """Call State._instantiate_parameter_states to instantiate a ParameterState for each parameter in user_params diff --git a/psyneulink/components/mechanisms/processing/objectivemechanism.py b/psyneulink/components/mechanisms/processing/objectivemechanism.py index 7a17642156..eed7d1c386 100644 --- a/psyneulink/components/mechanisms/processing/objectivemechanism.py +++ b/psyneulink/components/mechanisms/processing/objectivemechanism.py @@ -38,21 +38,22 @@ ~~~~~~~~~~~~~~~~~~~~~~ The **monitored_output_states** argument of the constructor specifies the `OutputStates ` it monitors. -This takes the place of the **input_states** argument used by most other forms of `Mechanism`, and is used by the -ObjectiveMechanism to create an `InputState` for each OutputState it monitors, along with a `MappingProjection` from -the OutputState to the InputState. The **monitored_output_states** argument takes a list of items that can include -any of the `forms of specification ` used in a standard **input_states** argument. For the -**monitored_output_states** argument, this is usually a list of OutputStates to be monitored. However, as with a -standard **input_states** argument, items in the **monitored_output_states** argument can also be used to specify -attributes of the InputState and/or MappingProjection to it created by the ObjectiveMechanism to monitor the specified -OutputState. In general, the `value ` of each specified OutputState determines the format of the -`variable ` of the InputState that is created for it by the ObjectiveMechanism. However, this can -be overridden using the ObjectiveMechanism's `default_variable ` or `size -` attributes (see `Mechanism_InputState_Specification`), or by specifying a Projection from -the OutputState to the InputState (see `Input Source Specification `). -If an item in the **monitored_output_states** argument specifies an InputState for the ObjectiveMechanism, but not -the OutputState to be monitored, the InputState is created but will be ignored until an OutputState (and -MappingProjection from it) are specified for that InputState. +This takes the place of the **input_states** argument used by most other forms of `Mechanism `, and is used +by the ObjectiveMechanism to create an `InputState` for each OutputState it monitors, along with a `MappingProjection` +from the OutputState to that InputState. The **monitored_output_states** argument takes a list of items that can +include any of the `forms of specification ` used in a standard **input_states** argument. +For the **monitored_output_states** argument, this is usually a list of OutputStates to be monitored. However, +as with a standard **input_states** argument, items in the **monitored_output_states** argument can also be used to +specify attributes of the InputState and/or MappingProjection to it created that the ObjectiveMechanism creates to +monitor the specified OutputState. In general, the `value ` of each specified OutputState determines +the format of the `variable ` of the InputState that is created for it by the ObjectiveMechanism. +However, this can be overridden using the ObjectiveMechanism's `default_variable ` +or `size ` attributes (see `Mechanism InputState specification +`), or by specifying a Projection from the OutputState to the InputState (see +`Input Source Specification `). If an item in the +**monitored_output_states** argument specifies an InputState for the ObjectiveMechanism, but not the OutputState to +be monitored, the InputState is created but will be ignored until an OutputState (and MappingProjection from it) are +specified for that InputState. COMMENT: @@ -331,7 +332,10 @@ from psyneulink.components.mechanisms.processing.processingmechanism import ProcessingMechanism_Base from psyneulink.components.states.outputstate import OutputState, PRIMARY, standard_output_states from psyneulink.components.states.state import _parse_state_spec -from psyneulink.globals.keywords import CONTROL, DEFAULT_MATRIX, DEFAULT_VARIABLE, EXPONENTS, FUNCTION, INPUT_STATES, LEARNING, MATRIX, OBJECTIVE_MECHANISM, SENDER, STATE_TYPE, VARIABLE, WEIGHTS, kwPreferenceSetName +from psyneulink.globals.keywords import PARAMS, PROJECTION, PROJECTIONS, CONTROL, DEFAULT_MATRIX, DEFAULT_VARIABLE, \ + EXPONENT, EXPONENTS, FUNCTION, \ + INPUT_STATES, LEARNING, MATRIX, NAME, OBJECTIVE_MECHANISM, SENDER, STATE_TYPE, VARIABLE, WEIGHT, WEIGHTS, \ + kwPreferenceSetName from psyneulink.globals.preferences.componentpreferenceset import is_pref_set, kpReportOutputPref from psyneulink.globals.preferences.preferenceset import PreferenceEntry, PreferenceLevel from psyneulink.globals.utilities import ContentAddressableList @@ -430,7 +434,7 @@ class ObjectiveMechanism(ProcessingMechanism_Base): default_variable : number, list or np.ndarray : default monitored_output_states specifies the format of the `variable ` for the `InputStates` of the - ObjectiveMechanism (see `Mechanism_InputState_Specification` for details). + ObjectiveMechanism (see `Mechanism InputState specification ` for details). size : int, list or np.ndarray of ints specifies default_variable as array(s) of zeros if **default_variable** is not passed as an argument; @@ -642,59 +646,97 @@ def _validate_params(self, request_set, target_set=None, context=None): if MONITORED_OUTPUT_STATES in target_set and target_set[MONITORED_OUTPUT_STATES] is not None: pass + def _instantiate_input_states(self, monitored_output_states_specs=None, reference_value=None, context=None): + """Instantiate InputStates specified in **input_states** argument of constructor or each OutputState + specified in monitored_output_states_specs - def _instantiate_input_states(self, monitored_output_states_specs=None, context=None): - """Instantiate InputStates for each OutputState specified in monitored_output_states_specs + Called during initialization as well as by _add_monitored_output_states(), + so must distinguish between initialization and adding to instantiated input_states. - Called by _add_monitored_output_states as well as during initialization - (so must distinguish between initialization and adding to instantiated input_states) + During initialization, uses **input_states** as specification of InputStates to instantiate; + if none are specified, instantiates a default InputState - Parse specifications for **input_states**, using **monitored_output_states** where relevant and instantiate - input_states. - - Instantiate or extend self.instance_defaults.variable to match number of InputStates. - - Update self.input_state and self.input_states. - - Call _instantiate_monitoring_projection() to instantiate MappingProjection to InputState - if an OutputState has been specified. + Otherwise, uses monitored_output_States_specs as specification of InputStates to instantiate; + these will replace any existing InputStates (including a default one) """ from psyneulink.components.states.inputstate import InputState # If call is for initialization if self.init_status is InitStatus.UNSET: - # Pass self.input_states (containing specs from **input_states** arg of constructor) - input_states = self.input_states - else: - # If initialized, don't pass self.input_states, as this is now a list of existing InputStates - input_states = None - - # PARSE input_states (=monitored_output_states) specifications into InputState specification dictionaries - # and ASSIGN self.instance_defaults.variable - - if not input_states: - # If no input_states are specified, create a default - input_states = [{STATE_TYPE: InputState, VARIABLE: [0]}] + # Use self.input_states (containing specs from **input_states** arg of constructor) or default InputState + input_states = self.input_states or [{STATE_TYPE: InputState, VARIABLE: [0]}] + return super()._instantiate_input_states(input_states=input_states, context=context) # Instantiate InputStates corresponding to OutputStates specified in monitored_output_states - # instantiated_input_states = super()._instantiate_input_states(input_states=self.input_states, context=context) - instantiated_input_states = super()._instantiate_input_states(input_states=input_states, context=context) - # MODIFIED 10/3/17 END + # (note: these will replace any existing ones, including a default one created on initialization) + return super()._instantiate_input_states(input_states=monitored_output_states_specs, + reference_value=reference_value, + context=context) def add_monitored_output_states(self, monitored_output_states_specs, context=None): """Instantiate `OutputStates ` to be monitored by the ObjectiveMechanism. Used by other Components to add a `State` or list of States to be monitored by the ObjectiveMechanism. - The **monitored_output_states_spec** can be a `Mechanism`, `OutputState`, `tuple specification - `, `State specification dictionary `, or - list with any of these. If item is a Mechanism, its `primary OutputState ` is used. + The **monitored_output_states_spec** can be any of the following: + - MonitoredOutputStateTuple + - `Mechanism`; + - `OutputState`; + - `tuple specification `; + - `State specification dictionary `; + - list with any of the above. + If the item is a Mechanism, its `primary OutputState ` is used. """ monitored_output_states_specs = list(monitored_output_states_specs) - # FIX: NEEDS TO RETURN output_states (?IN ADDITION TO input_states) SO THAT IF CALLED BY ControlMechanism THAT - # FIX: BELONGS TO A SYSTEM, THE ControlMechanism CAN CALL System._validate_monitored_state_in_system - # FIX: ON THE output_states ADDED - return self._instantiate_input_states(monitored_output_states_specs=monitored_output_states_specs, - context=context) + # If ObjectiveMechanism has only its default InputState and it 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) + + # Get reference value + reference_value = [] + # Get value of each OutputState or, if a Projection from it is specified, then the Projection's value + for i, spec in enumerate(monitored_output_states_specs): + from psyneulink.components.states.inputstate import InputState + from psyneulink.components.system import MonitoredOutputStateTuple + + # 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) + + 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) + + input_states = self._instantiate_input_states(monitored_output_states_specs=monitored_output_states_specs, + reference_value=reference_value, + context='ADD_STATES') + output_states = [[projection.sender for projection in state.path_afferents] for state in input_states] + + self._instantiate_function_weights_and_exponents(context=context) + + return output_states def _instantiate_attributes_after_function(self, context=None): """Assign InputState weights and exponents to ObjectiveMechanism's function @@ -720,6 +762,7 @@ def _instantiate_function_weights_and_exponents(self, context=None): if hasattr(self.function_object, EXPONENTS): if any(exponent is not None for exponent in exponents): self.function_object.exponents = [exponent or DEFAULT_EXPONENT for exponent in exponents] + assert True @property def monitored_output_states(self): diff --git a/psyneulink/components/states/inputstate.py b/psyneulink/components/states/inputstate.py index 7172643f25..39c891f36d 100644 --- a/psyneulink/components/states/inputstate.py +++ b/psyneulink/components/states/inputstate.py @@ -1093,6 +1093,7 @@ def _instantiate_input_states(owner, input_states=None, reference_value=None, co context=context) # Call from Mechanism.add_states, so add to rather than assign input_states (i.e., don't replace) + # IMPLEMENTATION NOTE: USE OF CONTEXT STRING if context and 'ADD_STATES' in context: owner.input_states.extend(state_list) else: @@ -1111,12 +1112,12 @@ def _instantiate_input_states(owner, input_states=None, reference_value=None, co break if not variable_item_is_OK: - # NOTE: This block of code appears unused, and the 'for' loop appears to cause an error anyways. (7/11/17 CW) old_variable = owner.instance_defaults.variable new_variable = [] for state in owner.input_states: new_variable.append(state.value) owner.instance_defaults.variable = np.array(new_variable) + owner._update_variable(new_variable) if owner.verbosePref: warnings.warn( "Variable for {} ({}) has been adjusted to match number and format of its input_states: ({})".format( diff --git a/psyneulink/components/states/state.py b/psyneulink/components/states/state.py index d364b3ae78..de71dedbc2 100644 --- a/psyneulink/components/states/state.py +++ b/psyneulink/components/states/state.py @@ -157,9 +157,9 @@ `MappingProjection` to the Mechanism's `primary InputState ` or from its `primary OutputState `, depending upon the type of Mechanism and context of specification. It can also be accompanied by one or more State specification entries described below, to create one or more - Projections to/from those States (see `examples `). + Projections to/from those specific States (see `examples `). .. - * *:List[,...] + * :List[,...] this must accompany a *MECHANISM* entry (described above), and is used to specify its State(s) by name. Each entry must use one of the following keywords as its key, and there can be no more than one of each: - *INPUT_STATES* diff --git a/psyneulink/components/system.py b/psyneulink/components/system.py index ccd03fdb42..c7ece30465 100644 --- a/psyneulink/components/system.py +++ b/psyneulink/components/system.py @@ -71,30 +71,30 @@ * **monitor_for_control** argument -- used to specify OutputStates of Mechanisms in the System that be monitored by the `ObjectiveMechanism` associated with the System's `controller ` (see `ControlMechanism_ObjectiveMechanism`); these are used in addition to any specified for the ControlMechanism or - its ObjectiveMechanism. These can be specified in the **monitor_for_control** argument of the `System` class using + its ObjectiveMechanism. These can be specified in the **monitor_for_control** argument of the `System` using any of the ways used to specify the *monitored_output_states* argument of the constructor for an ObjectiveMechanism (see `ObjectiveMechanism_Monitored_Output_States`). In addition, the **monitor_for_control** argument supports two other forms of specification: - * **string** -- must be the name ` of an `OutputState` of a `Mechanism` in the System (see third - example under `System_Control_Examples`); any OutputState with that name, including ones with the same - name belonging to different Mechanisms within the System, will be monitored. If a OutputState of a particular + * **string** -- must be the `name ` of an `OutputState` of a `Mechanism ` in the System + (see third example under `System_Control_Examples`); any OutputState with that name, including ones with the + same name belonging to different Mechanisms within the System, will be monitored. If an OutputState of a particular Mechanism is desired, and it shares its name with ones of other Mechanisms, then it must be referenced explicitly - (see examples under `System_Control_Examples`). - + (see `InputState specification`, and examples under `System_Control_Examples`). + | * **MonitoredOutputStatesOption** -- must be a value of `MonitoredOutputStatesOption`, and must appear alone or as a single item in the list specifying the **monitor_for_control** argument; any other specification(s) included in the list will take precedence. The MonitoredOutputStatesOption applies to all of the Mechanisms in the System except its `controller ` and `LearningMechanisms `. The *PRIMARY_OUTPUT_STATES* value specifies that the `primary OutputState ` of every Mechanism be monitored, whereas *ALL_OUTPUT_STATES* specifies that *every* OutputState of every Mechanism be monitored. - + | The default for the **monitor_for_control** argument is *MonitoredOutputStatesOption.PRIMARY_OUTPUT_STATES*. The OutputStates specified in the **monitor_for_control** argument are added to any already specified for the ControlMechanism's `objective_mechanism `, and the full set is listed in the ControlMechanism's `monitored_output_states ` attribute, and its ObjectiveMechanism's `monitored_output_states ` attribute). - +.. * **control_signals** argument -- used to specify the parameters of Components in the System to be controlled. These can be specified in any of the ways used to `specify ControlSignals ` in the *control_signals* argument of a ControlMechanism. These are added to any `ControlSignals ` that have @@ -362,7 +362,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following example specifies an `EVCControlMechanism` as the controller for a System with two `Processes ` -that include two `Mechanisms ` (not shown): +that include two `Mechanisms ` (not shown):: my_system = System(processes=[TaskExecutionProcess, RewardProcess], controller=EVCControlMechanism(objective_mechanism= @@ -451,7 +451,8 @@ from psyneulink.globals.preferences.componentpreferenceset import is_pref_set from psyneulink.globals.preferences.preferenceset import PreferenceLevel from psyneulink.globals.registry import register_category -from psyneulink.globals.utilities import AutoNumber, ContentAddressableList, append_type_to_name, convert_to_np_array, iscompatible +from psyneulink.globals.utilities import AutoNumber, ContentAddressableList, append_type_to_name, \ + convert_to_np_array, iscompatible, insert_list from psyneulink.scheduling.scheduler import Scheduler from psyneulink.scheduling.time import TimeScale @@ -1926,7 +1927,7 @@ def _instantiate_controller(self, control_mech_spec, context=None): if control_mech_spec is None: return - # Warn for request to assign the ControlMechanism already assigned + # Warn for request to assign the ControlMechanism already assigned if control_mech_spec is self.controller and self.prefs.verbosePref: warnings.warn("{} has already been assigned as the {} for {}; assignment ignored". format(control_mech_spec, CONTROLLER, self.name)) @@ -1934,15 +1935,9 @@ def _instantiate_controller(self, control_mech_spec, context=None): # An existing ControlMechanism is being assigned if isinstance(control_mech_spec, ControlMechanism): + control_mech_spec.assign_as_controller(self, context=context) controller = control_mech_spec -# FIX: EVEN IF THE CONTROLLER HAS BEEN ASSIGNED TO A SYSTEM, STILL NEED TO ADD MONITORED_OUTPUT_STATES AND -# FIX: CONTROL_SIGNALS FOR NEW SYSTEM - - # If it has NOT been assigned a System or already has another controller: - if controller.system is None or not controller.system is self: - controller.assign_as_controller(self, context=context) - # A ControlMechanism class or subclass is being used to specify the controller elif inspect.isclass(control_mech_spec) and issubclass(control_mech_spec, ControlMechanism): # Instantiate controller from class specification using: @@ -1962,8 +1957,12 @@ def _instantiate_controller(self, control_mech_spec, context=None): warnings.warn("The existing {} for {} ({}) is being replaced by {}". format(CONTROLLER, self.name, self.controller.name, controller.name)) - # Make assignment + # Make assignment (and assign controller's ControlSignals to self.control_signals) self._controller = controller + if self.control_signals is None: + self.control_signals = controller.control_signals + else: + self.control_signals.append(controller.control_signals) # Add controller's ObjectiveMechanism to the System's execution_list and execution_graph self.execution_list.append(self.controller.objective_mechanism) @@ -2063,8 +2062,7 @@ def _get_monitored_output_states_for_system(self, controller=None, context=None) # - a MonitoredOutputStatesOption (parsed below); # - 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 OutputState from the *monitor_for_control* arg of System, - # which should return a reference to the OutputState when passed to _parse_state_spec + # - 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()): @@ -2081,61 +2079,81 @@ def _get_monitored_output_states_for_system(self, controller=None, context=None) # Note: assign parsed spec(s) to a list, as there may be more than one (that will be added to all_specs) monitored_output_state_tuples = [] - if (spec, (OutputState, Mechanism)): - # spec is an OutputState, so use it - if isinstance(spec, OutputState): - output_states = [spec] - # spec is Mechanism, so use the State's owner, and get the relevant OutputState(s) - elif isinstance(spec, Mechanism): - if (MONITOR_FOR_CONTROL in spec.params - and spec.params[MONITOR_FOR_CONTROL] is MonitoredOutputStatesOption.ALL_OUTPUT_STATES): - output_states = spec.output_states - else: - output_states = [spec.output_state] - for output_state in output_states: - monitored_output_state_tuples.extend([MonitoredOutputStateTuple( - output_state=output_state, - weight=DEFAULT_MONITORED_STATE_WEIGHT, - exponent=DEFAULT_MONITORED_STATE_EXPONENT, - matrix=DEFAULT_MONITORED_STATE_MATRIX)]) - - # Otherwise, use self (System) as place-marker and try to parse spec as InputState specification - # (i.e., a dict or tuple containing weight, exponent and/or matrix specs) - else: - try: - input_state_spec = _parse_state_spec(owner=self, - state_type=InputState, - state_spec=spec) - except: - raise SystemError("Specification of item in \'{}\' arg in constructor for {} is not an {} ({})". - format(MONITOR_FOR_CONTROL, self.name, OutputState.__name__, spec)) - - - # Get OutputState(s), and matrix specified for Projection to each, - # from the ProjectionTuple in the PROJECTIONS entry of the PARMS dict. - # However, use weight and exponent entries for InputState, rather than any specified for - # for each Projection (in its projection_spec). - # The InputState weight and exponent are used in the MonitoredOutputStateTuple - # as they specify the how the InputState should be weighted; - # Any weight(s) and/or exponent(s) specified in the projection_spec(s) (a ProjectionTuple) - # are used for individual Projections to the InputState when it is actually instantiated. - for projection_spec in input_state_spec[PARAMS][PROJECTIONS]: - monitored_output_state_tuples.extend([MonitoredOutputStateTuple( - output_state=projection_spec.state, - weight=input_state_spec[WEIGHT], - exponent=input_state_spec[EXPONENT], - matrix=projection_spec.matrix)]) - - # Delete original item of all_specs, and assign ones parsed into monitored_output_state_tuple(s) - del all_specs[i] - all_specs.insert(i, monitored_output_state_tuples) - - all_specs_extracted_from_tuples.extend([item.output_state for item in monitored_output_state_tuples]) - # MODIFIED 10/3/17 END - - # FIX: 10/3/17 - TURN THIS INTO A TRY AND EXCEPT: - assert(all (isinstance(item, (OutputState, MonitoredOutputStatesOption)) for item in - all_specs_extracted_from_tuples)) + weight=DEFAULT_MONITORED_STATE_WEIGHT + exponent=DEFAULT_MONITORED_STATE_EXPONENT + matrix=DEFAULT_MONITORED_STATE_MATRIX + + # spec is a tuple + # - put OutputState(s) in spec + # - assign any weight, exponent, and/or matrix specified + if isinstance(spec, tuple): + # 2-item tuple (, ) + if len(spec) == 2: + # FIX: DO ERROR CHECK ON THE FOLLOWING / ALLOW LIST OF STATES + spec = spec[1].output_states[spec[0]] + # 3-item tuple (, weight, exponent) + elif len(spec) == 3: + spec, weight, exponent = spec + # 4-item tuple (, weight, exponent, matrix) + elif len(spec) == 4: + spec, weight, exponent, matrix = spec + + if not isinstance(spec, list): + spec_list = [spec] + + for spec in spec_list: + # spec is an OutputState or Mechanism + if isinstance(spec, (OutputState, Mechanism)): + # spec is an OutputState, so use it + if isinstance(spec, OutputState): + output_states = [spec] + # spec is Mechanism, so use the State's owner, and get the relevant OutputState(s) + elif isinstance(spec, Mechanism): + if (MONITOR_FOR_CONTROL in spec.params + and spec.params[MONITOR_FOR_CONTROL] is MonitoredOutputStatesOption.ALL_OUTPUT_STATES): + output_states = spec.output_states + else: + output_states = [spec.output_state] + for output_state in output_states: + monitored_output_state_tuples.extend( + [MonitoredOutputStateTuple(output_state=output_state, + weight=weight, + exponent=exponent, + matrix=matrix)]) + # spec is a string + elif isinstance(spec, str): + # Search System for Mechanisms with OutputStates with the string as their name + for mech in self.mechanisms: + for output_state in mech.output_states: + if output_state.name is spec: + monitored_output_state_tuples.extend( + [MonitoredOutputStateTuple(output_state=output_state, + weight=weight, + exponent=exponent, + matrix=matrix)]) + + else: + raise SystemError("Specification of item in \'{}\' arg in constructor for {} ({}) " + "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_extracted_from_tuples.extend([item.output_state for item in monitored_output_state_tuples]) + + try: + all (isinstance(item, (OutputState, MonitoredOutputStatesOption)) + for item in all_specs_extracted_from_tuples) + except: + raise SystemError("PROGRAM ERROR: Fail to parse items of \'{}\' arg ({}) in constructor for {}". + format(MONITOR_FOR_CONTROL, self.name, spec, OutputState.__name__)) # Get MonitoredOutputStatesOptions if specified for controller or System, and make sure there is only one: option_specs = [item for item in all_specs_extracted_from_tuples @@ -2330,7 +2348,7 @@ def _get_monitored_output_states_for_system(self, controller=None, context=None) matrix=spec.matrix) return output_state_tuples - def _validate_monitored_state_in_system(self, monitored_states, context=None): + def _validate_monitored_states_in_system(self, monitored_states, context=None): for spec in monitored_states: # if not any((spec is mech.name or spec in mech.output_states.names) if not any((spec in {mech, mech.name} or spec in mech.output_states or spec in mech.output_states.names) @@ -2366,7 +2384,7 @@ def _validate_control_signals(self, control_signals, context=None): if control_signals: for control_signal in control_signals: for control_projection in control_signal.efferents: - if not any(control_projection.receiver in mech._parameters_states for mech in self.mechanisms): + if not any(control_projection.receiver in mech.parameter_states for mech in self.mechanisms): raise SystemError("A parameter controlled by a ControlSignal of a controller " "being assigned to {} is not in that System".format(self.name)) diff --git a/psyneulink/globals/utilities.py b/psyneulink/globals/utilities.py index 8e57a2ffe1..c23e601782 100644 --- a/psyneulink/globals/utilities.py +++ b/psyneulink/globals/utilities.py @@ -71,6 +71,7 @@ * `ContentAddressableList` * `make_readonly_property` * `get_class_attributes` +* `insert_list` """ @@ -88,7 +89,7 @@ __all__ = [ 'append_type_to_name', 'AutoNumber', 'ContentAddressableList', 'convert_to_np_array', 'convert_all_elements_to_np_array', 'get_class_attributes', 'get_modulationOperation_name', 'get_value_from_array', 'is_component', 'is_distance_metric', 'is_matrix', - 'is_matrix_spec', + 'insert_list', 'is_matrix_spec', 'is_modulation_operation', 'is_numeric', 'is_numeric_or_none', 'is_same_function_spec', 'is_unit_interval', 'is_value_spec', 'iscompatible', 'kwCompatibilityLength', 'kwCompatibilityNumeric', 'kwCompatibilityType', 'make_readonly_property', 'merge_param_dicts', 'Modulation', 'MODULATION_ADD', 'MODULATION_MULTIPLY', @@ -1079,3 +1080,8 @@ def convert_all_elements_to_np_array(arr): elementwise_subarr[i] = subarr[i] return elementwise_subarr + + +def insert_list(list1, position, list2): + """Insert list2 into list1 at position""" + return list1[:position] + list2 + list1[position:] diff --git a/psyneulink/library/subsystems/evc/evccontrolmechanism.py b/psyneulink/library/subsystems/evc/evccontrolmechanism.py index a3047e1dc5..c36a2fef53 100644 --- a/psyneulink/library/subsystems/evc/evccontrolmechanism.py +++ b/psyneulink/library/subsystems/evc/evccontrolmechanism.py @@ -336,7 +336,8 @@ from psyneulink.components.projections.pathway.mappingprojection import MappingProjection from psyneulink.components.shellclasses import Function, System_Base from psyneulink.globals.defaults import defaultControlAllocation -from psyneulink.globals.keywords import CONTROL, COST_FUNCTION, EVC_MECHANISM, FUNCTION, INITIALIZING, INIT_FUNCTION_METHOD_ONLY, PARAMETER_STATES, PREDICTION_MECHANISM, PREDICTION_MECHANISM_PARAMS, PREDICTION_MECHANISM_TYPE, SUM +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 from psyneulink.globals.preferences.componentpreferenceset import is_pref_set from psyneulink.globals.preferences.preferenceset import PreferenceLevel from psyneulink.globals.utilities import ContentAddressableList @@ -747,8 +748,8 @@ def _instantiate_prediction_mechanisms(self, context=None): Instantiate prediction_mechanisms for `ORIGIN` Mechanisms in self.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 + - 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) For each `ORIGIN` Mechanism in self.system: @@ -759,7 +760,7 @@ def _instantiate_prediction_mechanisms(self, context=None): Instantiate self.predicted_input dict: - key for each entry is an `ORIGIN` Mechanism of the 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 + each value is a 2d array, each item of which is the value of an InputState of the predictionMechanism Args: context: @@ -781,7 +782,7 @@ def _instantiate_prediction_mechanisms(self, context=None): 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 = [] @@ -869,9 +870,19 @@ def _instantiate_attributes_after_function(self, context=None): num_control_projections)) @tc.typecheck - def assign_as_controller(self, system:System_Base, context=None): + 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) super().assign_as_controller(system=system, context=context) - self._instantiate_prediction_mechanisms(context=context) + # MODIFIED 1/15/18 END def _execute(self, variable=None,