From 7b097af0620fd14c2c670c951e36cbea423617e0 Mon Sep 17 00:00:00 2001 From: jdcpni Date: Thu, 30 Nov 2017 14:09:14 -0500 Subject: [PATCH] Feat/system/show graph (#555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * - * - * - * • Library: - docs: "Base class" -> "Core class" * • CONVENTIONS: - added section on organization of repo and base vs. core classes * • System - fixed bug in show_graph producing empty image for graphs with just one Mechanism * • System - show_graph(): fixed bug producing empty image for graphs with just one Mechanism added auto-recurrent projections * • System - show_graph(): fixed bug producing empty image for graphs with just one Mechanism added auto-recurrent projections * • System - show_graph(): shows dimensions for Mechanisms and Projections * • System - show_graph(): shows dimensions for Mechanisms and Projections * • System, Process - minor modes to enabling of learning to prevent crash when learning is specified in constructor but no learnable elements are included in Process * • System, Process - minor modes to enabling of learning to prevent crash when learning is specified in constructor but no learnable elements are included in Process --- CONVENTIONS.md | 60 +++++- Scripts/Examples/EVC-Gratton.py | 1 + Scripts/Examples/Multilayer-Learning.py | 3 +- Scripts/Scratch Pad.py | 19 +- docs/source/ControlMechanisms.rst | 2 +- docs/source/ControlProjections.rst | 2 +- docs/source/GatingMechanisms.rst | 2 +- docs/source/GatingProjections.rst | 2 +- docs/source/IntegratorMechanisms.rst | 2 +- docs/source/LearningMechanisms.rst | 2 +- docs/source/LearningProjections.rst | 2 +- docs/source/ObjectiveMechanisms.rst | 2 +- docs/source/TransferMechanisms.rst | 2 +- psyneulink/components/component.py | 26 +-- psyneulink/components/process.py | 31 ++- .../components/projections/projection.py | 106 ++--------- .../components/states/parameterstate.py | 60 +----- psyneulink/components/states/state.py | 32 ++-- psyneulink/components/system.py | 178 +++++++++++++----- 19 files changed, 274 insertions(+), 260 deletions(-) diff --git a/CONVENTIONS.md b/CONVENTIONS.md index cf44a5e8b67..d822eb1cba2 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -1,6 +1,62 @@ -# PsyNeuLink Coding and Documentation Conventions - +# PsyNeuLink Organization, Coding, and Documentation Conventions + +## REPOSITORY ORGANIZATION: + +### Core: +Made up of two types of classes: +- *abstract base classes* (italicized) - cannot be instantiated. +- **core classes** (bold) - most basic (abstract) level of objects that can be instantiated. + +#### Components +"Building blocks" +- *Mechanism* + - *ProcessingMechanism* + - **TransferMechanism** + - **IntegratorMechanism** + - **ObjectiveMechanism** + - *AdaptiveMechanism* + - **LearningMechanism** + - **ControlMechanism** + - **GatingMechanism** +- *Projection* + - *PathwayProjection* + - **MappingProjection** + - *ModulatoryProjection* + - **LearningProjection** + - **ControlProjection** + - **GatingProjection** +- *State* + - **InputState** + - **ParameterState** + - **OutputState** + - *ModulatorySignal* + - **LearningSignal** + - **ControlSignal** + - **GatingSignal** +- *Function* + - *TransferFunction* + - *CombinationFunction* + - *IntegratorFunction* + - *DistributionFunction* + - *LearningFunction* + +#### Composisitons +Objects that compose building blocks and control their execution. +- *Composition* + - **System** + - **Process** + +#### Scheduler +Objects used by Compositions to control the execution of Components and Compositions. +- **Scheduler** +- **Condition** + +### Library +Extensions of Core objects +- *Components:* classes derived from Core objects +- *Compositions:* models +- *Models:* published, implemented models ### NAMING: diff --git a/Scripts/Examples/EVC-Gratton.py b/Scripts/Examples/EVC-Gratton.py index 24d9fb322d5..d073af429b6 100644 --- a/Scripts/Examples/EVC-Gratton.py +++ b/Scripts/Examples/EVC-Gratton.py @@ -152,6 +152,7 @@ def test_outcome_function(**kwargs): # 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/Scripts/Examples/Multilayer-Learning.py b/Scripts/Examples/Multilayer-Learning.py index 30b1c5b2833..8bac1e5ec69 100644 --- a/Scripts/Examples/Multilayer-Learning.py +++ b/Scripts/Examples/Multilayer-Learning.py @@ -104,7 +104,7 @@ def show_target(system): ) mySystem.reportOutputPref = True -mySystem.show_graph(show_learning=pnl.ALL) +mySystem.show_graph(show_learning=pnl.ALL, show_dimensions=pnl.ALL) # mySystem.show_graph() stim_list = {Input_Layer: [[-1, 30]]} @@ -119,4 +119,3 @@ def show_target(system): termination_processing={pnl.TimeScale.TRIAL: pnl.AfterNCalls(Output_Layer, 1)} ) -mySystem.show_graph(show_learning=pnl.ALL) diff --git a/Scripts/Scratch Pad.py b/Scripts/Scratch Pad.py index f6a76006b30..f73e1c75c78 100644 --- a/Scripts/Scratch Pad.py +++ b/Scripts/Scratch Pad.py @@ -716,7 +716,6 @@ def __init__(self, error_value): #endregion - # region TEST MODULATORY SPECS # print ("TEST MODULATORY SPECS") # @@ -811,7 +810,6 @@ def __init__(self, error_value): #endregion - #region TEST DOCUMENTATION # print ("TEST DOCUMENTATION") @@ -881,6 +879,23 @@ def __init__(self, error_value): #endregion +#region TEST System Graph with AutoAssociativeMechanism +print("TEST System Graph with AutoAssociativeMechanism") + +a = pnl.DDM(name='MY DDM') +# a = pnl.RecurrentTransferMechanism(name='Autoassociator') +p = pnl.Process(pathway=[a], + learning=pnl.ENABLED + ) +s = pnl.System(processes=[p]) +s.show_graph(show_learning=pnl.ALL, + show_dimensions=pnl.ALL + ) + + +#endregion + + #region TEST INSTANTATION OF Cyclic and Acyclic Systems @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # diff --git a/docs/source/ControlMechanisms.rst b/docs/source/ControlMechanisms.rst index 57b229fba96..102827f4baf 100644 --- a/docs/source/ControlMechanisms.rst +++ b/docs/source/ControlMechanisms.rst @@ -1,7 +1,7 @@ ControlMechanisms ================= -**Base class**: +**Core class**: * `ControlMechanism` diff --git a/docs/source/ControlProjections.rst b/docs/source/ControlProjections.rst index 0aa6da3d303..7be94f7ba55 100644 --- a/docs/source/ControlProjections.rst +++ b/docs/source/ControlProjections.rst @@ -1,7 +1,7 @@ Control Projections =================== -**Base class**: +**Core class**: * `ControlProjection` diff --git a/docs/source/GatingMechanisms.rst b/docs/source/GatingMechanisms.rst index 3026b95c7a4..1bc24fbac92 100644 --- a/docs/source/GatingMechanisms.rst +++ b/docs/source/GatingMechanisms.rst @@ -1,7 +1,7 @@ GatingMechanisms ================ -**Base class**: +**Core class**: * `GatingMechanism` diff --git a/docs/source/GatingProjections.rst b/docs/source/GatingProjections.rst index 32a622d440e..e74e921c8f7 100644 --- a/docs/source/GatingProjections.rst +++ b/docs/source/GatingProjections.rst @@ -1,7 +1,7 @@ GatingProjections ================= -**Base class**: +**Core class**: * `GatingProjection` diff --git a/docs/source/IntegratorMechanisms.rst b/docs/source/IntegratorMechanisms.rst index 4723d2ba154..e6f20d593c8 100644 --- a/docs/source/IntegratorMechanisms.rst +++ b/docs/source/IntegratorMechanisms.rst @@ -1,7 +1,7 @@ IntegratorMechanisms ==================== -**Base class**: +**Core class**: * `IntegratorMechanism` diff --git a/docs/source/LearningMechanisms.rst b/docs/source/LearningMechanisms.rst index 7afb44bb01d..9b6686dfb68 100644 --- a/docs/source/LearningMechanisms.rst +++ b/docs/source/LearningMechanisms.rst @@ -1,7 +1,7 @@ Learning Mechanisms =================== -**Base class**: +**Core class**: * `LearningMechanism` diff --git a/docs/source/LearningProjections.rst b/docs/source/LearningProjections.rst index 0ce77c425c9..a850a99cd07 100644 --- a/docs/source/LearningProjections.rst +++ b/docs/source/LearningProjections.rst @@ -1,7 +1,7 @@ Learning Projections ==================== -**Base class**: +**Core class**: * `LearningProjection` diff --git a/docs/source/ObjectiveMechanisms.rst b/docs/source/ObjectiveMechanisms.rst index dd300e2ce8e..7cc687f13a3 100644 --- a/docs/source/ObjectiveMechanisms.rst +++ b/docs/source/ObjectiveMechanisms.rst @@ -1,7 +1,7 @@ Objective Mechanisms ==================== -**Base class**: +**Core class**: * `ObjectiveMechanism` diff --git a/docs/source/TransferMechanisms.rst b/docs/source/TransferMechanisms.rst index 611cb956225..76ae47adb59 100644 --- a/docs/source/TransferMechanisms.rst +++ b/docs/source/TransferMechanisms.rst @@ -1,7 +1,7 @@ Transfer Mechanisms =================== -**Base class**: +**Core class**: * `TransferMechanism` diff --git a/psyneulink/components/component.py b/psyneulink/components/component.py index a29d285b6f5..07da7278c1e 100644 --- a/psyneulink/components/component.py +++ b/psyneulink/components/component.py @@ -1765,7 +1765,6 @@ def _instantiate_defaults(self, # so that latter are evaluated in context of former for param_name, param_value in sorted(default_set.items()): - # MODIFIED 11/30/16 NEW: # FUNCTION class has changed, so replace rather than update FUNCTION_PARAMS if param_name is FUNCTION: try: @@ -1778,27 +1777,22 @@ def _instantiate_defaults(self, except UnboundLocalError: pass # FIX: MAY NEED TO ALSO ALLOW assign_default_FUNCTION_PARAMS FOR COMMAND_LINE IN CONTEXT - # MODIFIED 11/30/16 END if param_name is FUNCTION_PARAMS and not self.assign_default_FUNCTION_PARAMS: continue - # MODIFIED 11/29/16 NEW: # Don't replace requested entry with default if param_name in request_set: continue - # MODIFIED 11/29/16 END # Add to request_set any entries it is missing fron the default_set request_set.setdefault(param_name, param_value) # Update any values in a dict if isinstance(param_value, dict): for dict_entry_name, dict_entry_value in param_value.items(): - # MODIFIED 11/29/16 NEW: # Don't replace requested entries if dict_entry_name in request_set[param_name]: continue - # MODIFIED 11/29/16 END request_set[param_name].setdefault(dict_entry_name, dict_entry_value) # VALIDATE PARAMS @@ -1813,11 +1807,6 @@ def _instantiate_defaults(self, # any override of _validate_params, which (should not, but) may process params # before calling super()._validate_params for param_name, param_value in request_set.items(): - # # MODIFIED 11/25/17 OLD: - # if isinstance(param_value, tuple): - # param_value = self._get_param_value_from_tuple(param_value) - # request_set[param_name] = param_value - # MODIFIED 11/25/17 NEW: if isinstance(param_value, tuple): param_value = self._get_param_value_from_tuple(param_value) elif isinstance(param_value, (str, Component, type)): @@ -1826,7 +1815,6 @@ def _instantiate_defaults(self, else: continue request_set[param_name] = param_value - # MODIFIED 11/25/17 END: try: self._validate_params(variable=variable, @@ -1887,11 +1875,7 @@ def _assign_params(self, request_set:tc.optional(dict)=None, context=None): self._instantiate_defaults(request_set=request_set, target_set=validated_set, - # # MODIFIED 4/14/17 OLD: - # assign_missing=False, - # MODIFIED 4/14/17 NEW: - assign_missing=False, - # MODIFIED 4/14/17 END + assign_missing=False, context=context) self.paramInstanceDefaults.update(validated_set) @@ -2086,7 +2070,6 @@ def _validate_params(self, request_set, target_set=None, context=None): raise ComponentError("{0} is not a valid parameter for {1}".format(param_name, self.__class__.__name__)) # The value of the param is None in paramClassDefaults: suppress type checking - # DOCUMENT: # IMPLEMENTATION NOTE: this can be used for params with multiple possible types, # until type lists are implemented (see below) if self.paramClassDefaults[param_name] is None or self.paramClassDefaults[param_name] is NotImplemented: @@ -2100,18 +2083,14 @@ def _validate_params(self, request_set, target_set=None, context=None): # If the value in paramClassDefault is a type, check if param value is an instance of it if inspect.isclass(self.paramClassDefaults[param_name]): if isinstance(param_value, self.paramClassDefaults[param_name]): - # MODIFIED 2/14/17 NEW: target_set[param_name] = param_value - # MODIFIED 2/14/17 END continue # If the value is a Function class, allow any instance of Function class from psyneulink.components.functions.function import Function_Base if issubclass(self.paramClassDefaults[param_name], Function_Base): # if isinstance(param_value, (function_type, Function_Base)): <- would allow function of any kind if isinstance(param_value, Function_Base): - # MODIFIED 2/14/17 NEW: target_set[param_name] = param_value - # MODIFIED 2/14/17 END continue # If the value in paramClassDefault is an object, check if param value is the corresponding class @@ -2247,7 +2226,6 @@ def _validate_params(self, request_set, target_set=None, context=None): raise ComponentError("Value of {} param for {} ({}) is not compatible with {}". format(param_name, self.name, param_value, type_name)) - # MODIFIED 11/25/17 NEW: def _get_param_value_for_modulatory_spec(self, param_name, param_value): from psyneulink.globals.keywords import MODULATORY_SPEC_KEYWORDS if isinstance(param_value, str): @@ -2273,8 +2251,6 @@ def _get_param_value_for_modulatory_spec(self, param_name, param_value): raise ComponentError("PROGRAM ERROR: Could not get default value for {} of {} (to replace spec as {})". format(param_name, self.name, param_value)) - # MODIFIED 11/25/17 END: - def _get_param_value_from_tuple(self, param_spec): """Returns param value (first item) of a (value, projection) tuple; """ diff --git a/psyneulink/components/process.py b/psyneulink/components/process.py index 44a4c7df9dd..0ab13010f5d 100644 --- a/psyneulink/components/process.py +++ b/psyneulink/components/process.py @@ -1029,10 +1029,12 @@ def _instantiate_pathway(self, context): self._instantiate__deferred_inits(context=context) if self.learning: - self._check_for_target_mechanisms() - if self._target_mechs: - self._instantiate_target_input(context=context) - self._learning_enabled = True + if self._check_for_target_mechanisms(): + if self._target_mechs: + self._instantiate_target_input(context=context) + self._learning_enabled = True + else: + self._learning_enabled = False else: self._learning_enabled = False @@ -1934,6 +1936,8 @@ def _check_for_target_mechanisms(self): Identify TARGET Mechanisms and assign to self.target_mechanisms, assign self to each TARGET Mechanism and report assignment if verbose + + Returns True of TARGET Mechanisms are found and/or assigned, else False """ from psyneulink.components.mechanisms.processing.objectivemechanism import ObjectiveMechanism @@ -1970,8 +1974,18 @@ def trace_learning_objective_mechanism_projections(mech): if (isinstance(object_item, ObjectiveMechanism) and object_item._learning_role is TARGET)) - if not target_mechs: + if target_mechs: + + # self.target_mechanisms = target_mechs + self._target_mechs = target_mechs + if self.prefs.verbosePref: + print("\'{}\' assigned as TARGET Mechanism(s) for \'{}\'". + format([mech.name for mech in self._target_mechs], self.name)) + return True + + # No target_mechs already specified, so get from learning_mechanism + elif self._learning_mechs: last_learning_mech = self._learning_mechs[0] # Trace projections to first learning ObjectiveMechanism, which is for the last mechanism in the process, @@ -2005,13 +2019,10 @@ def trace_learning_objective_mechanism_projections(mech): raise ProcessError("PROGRAM ERROR: {} has a learning specification ({}) " "but no TARGET ObjectiveMechanism".format(self.name, self.learning)) + return True else: - # self.target_mechanisms = target_mechs - self._target_mechs = target_mechs - if self.prefs.verbosePref: - print("\'{}\' assigned as TARGET Mechanism(s) for \'{}\'". - format([mech.name for mech in self._target_mechs], self.name)) + return False def _instantiate_target_input(self, context=None): diff --git a/psyneulink/components/projections/projection.py b/psyneulink/components/projections/projection.py index 5703474cfe0..3fea4f78d75 100644 --- a/psyneulink/components/projections/projection.py +++ b/psyneulink/components/projections/projection.py @@ -1176,27 +1176,8 @@ def _parse_projection_keyword(projection_spec:str): return projection_type -# MODIFIED 9/30/17 NEW: -# FIX: NEED TO ADD RECOGNITION OF PROJECTION AS THE STATE SPECIFICATION ITSELF (OR JUST USE PROJECTION SPEC) -# FIX: REPLACE "PROJECTIONS" WITH "CONNECTIONS" -# FIX: IN RECURSIVE CALLS TO _parse_state_spec, SPECIFY THAT IT HAS TO RETURN AN INSTANTIATED STATE -# FIX: MAKE SURE IT IS OK TO USE DICT PASSED IN (as params) AND NOT INADVERTENTLY OVERWRITING STUFF HERE -# FIX: ADD FACILITY TO SPECIFY WEIGHTS AND/OR EXPONENTS AND PROJECTION_SPEC FOR EACH connectsWith ITEM: -# CHANGE *PROJECTIONS* to *CONNECTS_WITH* -# MAKE EACH ENTRY OF CONNECTS_WITH A DICT OR TUPLE: -# DICT ENTRIES: *STATE*, *WEIGHT*, *EXPONENT*, *PROJECTION* -# TUPLE: (State, weight, exponent, projection_spec) -# PURPOSE: Resolve to set of specs that can be handed to Composition to instantiate -# PROJECT SHOULD BE USED TO INSTANTIATE THE PROJECTION TO/FROM THE SPECIFIED STATE -# WEIGHTS AND EXPONENTS SHOULD BE USED BY THE InputState's LinearCombination Function -# (AKIN TO HOW THE MECHANISM'S FUNCTION COMBINES InputState VALUES) -# (NOTE: THESE ARE DISTINCT FROM THE WEIGHT AND EXPONENT FOR THE InputState ITSELF) -# THIS WOULD ALLOW TWO LEVELS OF HIEARCHICAL NESTING OF ALGEBRAIC COMBINATIONS OF INPUT VALUES TO A MECHANISM -# @tc.typecheck -# def _parse_connection_specs(connectee_state_type:_is_state_class, def _parse_connection_specs(connectee_state_type, owner, - # connections:tc.any(State, Mechanism, dict, tuple, ProjectionTuple)): connections): """Parse specification(s) for States to/from which the connectee_state_type should be connected @@ -1204,9 +1185,8 @@ def _parse_connection_specs(connectee_state_type, "CONNECTION" is used instead of "PROJECTION" because: - the method abstracts over type and direction of Projection, so it is ambiguous whether the projection involved is to or from connectee_state_type; however, can always say it "connects with" - - specification is not always (in fact, usually is not) in the form of a Projection; - usually it is a Mechanism or State to/from which the connectee_state_type should send/receive the Projection, - so calling the method "_parse_projections" would be misleading. + - specification is not always (in fact, usually is not) in the form of a Projection; usually it is a + Mechanism or State to/from which the connectee_state_type should send/receive the Projection Connection attributes declared for each type (subclass) of State that are used here: connectsWith : State @@ -1218,11 +1198,11 @@ def _parse_connection_specs(connectee_state_type, modulators : ModulatorySignal - class of ModulatorySignal that can send ModulatoryProjection to the connectee_state_type - This method deals with CONNECTION specifications that are made in one of the following places/ways: - - *PROJECTIONS* entry of a State specification dict [SYNONYM: *PROJECTIONS* - for backward compatiability]; + This method deals with connection specifications that are made in one of the following places/ways: + - *PROJECTIONS* entry of a State specification dict; - last item of a State specification tuple. - In both cases, the CONNECTION specification can be a single (stand-alone) item or a list of them. + In both cases, the connection specification can be a single (stand-alone) item or a list of them. Projection(s) in connection(s) can be specified in any of the ways a Projection can be specified; * Mechanism specifications are resolved to a primary InputState or OutputState, as appropriate @@ -1231,9 +1211,9 @@ def _parse_connection_specs(connectee_state_type, * keyword specifications are resolved to corresponding Projection class * Class assignments are checked for compatiblity with connectee_state_type and connect_with State - Each CONNECTION specification can, itself, be one of the following: - * State - must be an instantiated State; - * Mechanism - primary State is used, if applicable, otherwise an exception is generated; + Each connection specification can, itself, be one of the following: + * State object or class; + * Mechanism object or class - primary State is used, if applicable, otherwise an exception is generated; * dict - must have the first and can have any of the additional entries below: *STATE*: - required; must resolve to an instantiated state; can use any of the following: State - the State is used; @@ -1262,24 +1242,6 @@ def _parse_connection_specs(connectee_state_type, (state_spec, projection_spec) or (state_spec, weight, exponent, projection_spec) - **DEPRECATED** [SHOULD ONLY MATTER FOR OBJECTIVE MECHANISMS]: - # If params is a dict: - # entry key can be any of the following, with the corresponding value: - # Mechanism: or [connection_spec<, connection_spec..>] - # - generates projection for each specified connectsWith State - # MECHANISMS: or [Mechanism<, Mechanism>] - # - generates projection for primary connectsWith State of each Mechanism - # - # If params is a tuple: - # - the first must be a BaseSpec specification (processed by _parse_state_spec, not here) - # - if it has two items, the second must resolve to a connectsWith - # (parsed in a recursive call to _parse_state_specific_entries) - # - if it has three or four items: - # - the second is a weight specification - # - the third is an exponent specification - # - the fourth (optional) must resolve to an connectsWith specification - # (parsed in a recursive call to _parse_state_specific_entries) - Returns list of ProjectionTuples, each of which specifies: - the state to be connected with - weight and exponent for that connection (assigned to the projection) @@ -1330,9 +1292,6 @@ def _parse_connection_specs(connectee_state_type, for connection in connections: - # FIX: 10/3/17 - IF IT IS ALREADY A PROJECTION OF THE CORRECT TYPE FOR THE CONNECTEE: - # FIX: ?? RETURN AS IS, AND/OR PARSE INTO DICT?? - # If a Mechanism, State, or State type is used to specify the connection on its own (i.e., w/o dict or tuple) # put in ProjectionTuple as both State spec and Projection spec (to get Projection for that State) # along with defaults for weight and exponent, and call _parse_connection_specs recursively @@ -1350,19 +1309,18 @@ def _parse_connection_specs(connectee_state_type, or isinstance(connectee_state_type, type) and issubclass(connectee_state_type, (InputState, OutputState, ParameterState))) and _is_modulatory_spec(connection)): - # MODIFIED 11/29/17 NEW: - # Convert AdaptiveMechanism specs to corresponding ModulatorySignal spec + + # Convert AdaptiveMechanism spec to corresponding ModulatorySignal spec if isinstance(connection, type) and issubclass(connection, AdaptiveMechanism_Base): connection = connection.outputStateType elif isinstance(connection, AdaptiveMechanism_Base): connection = connection.output_state - # MODIFIED 11/29/17 END projection_spec = connection else: projection_spec = connectee_state_type - # MODIFIED 11/28/17 END: + projection_tuple = (connection, DEFAULT_WEIGHT, DEFAULT_EXPONENT, projection_spec) connect_with_states.extend(_parse_connection_specs(connectee_state_type, owner, projection_tuple)) @@ -1371,12 +1329,6 @@ def _parse_connection_specs(connectee_state_type, # but also leave it is as the connection specification (it will get resolved to a State reference when the # tuple is created in the recursive call to _parse_connection_specs below). elif _is_projection_spec(connection, include_matrix_spec=False): - # FIX: 10/24/17 - IF connectee_state_type IS LearningSignal AND projection_spec is A MappingProjection - # FIX: THEN THE LATTER SHOULD BE TREATED AS A DESTINATION RATHER THAN AN ACTUAL projection_spec - # FIX: OR connection SHOULD BE LearningSignal AND projection_spec SHOULD BE THE DESITNATION - # FIX: 11/4/17 - IF connectee_state_type IS OutputState AND projection_spec is A GatingProjection - # FIX: THEN THE LATTER SHOULD BE TREATED AS A SOURCE RATHER THAN AN ACTUAL projection_spec - # FIX: OR connection SHOULD BE GatingSignal AND projection_spec SHOULD BE THE DESITNATION projection_spec = connection projection_tuple = (connection, DEFAULT_WEIGHT, DEFAULT_EXPONENT, projection_spec) @@ -1512,10 +1464,6 @@ def _parse_connection_specs(connectee_state_type, # (, ) if is_numeric(first_item): projection_spec = first_item - # FIX: 11/18/17 TRY DELETING THIS: - # Ignore first_item (assume it was processed by _parse_state_specific_specs), - # and use projection_spec as state_spec - state_spec = projection_spec # elif is_matrix(first_item): # projection_spec = last_item @@ -1576,26 +1524,15 @@ def _parse_connection_specs(connectee_state_type, # Validate state specification, and get actual state referenced if it has been instantiated try: - # state_types = connects_with - # mech_state_attribute=connect_with_attr - # MODIFIED 11/29/17 NEW: # FIX: 11/28/17 HACK TO DEAL WITH GatingSignal Projection to OutputState if (_is_gating_spec(first_item) and (isinstance(last_item, OutputState) or last_item == OutputState)): projection_socket = SENDER state_types = [OutputState] mech_state_attribute = [OUTPUT_STATES] - - # elif (isinstance(first_item, AdaptiveMechanism_Base) - # and _is_gating_spec(first_item) and last_item == first_item): - # projection_socket = SENDER - # state_types = [connectee_state_type] - # mech_state_attribute = [INPUT_STATES] - else: state_types = connects_with mech_state_attribute=connect_with_attr - # MODIFIED 11/29/17 END state = _get_state_for_socket(owner=owner, state_spec=state_spec, @@ -1727,16 +1664,7 @@ def _validate_connection_request( # Used below def _validate_projection_type(projection_class): # Validate that Projection's type can connect with a class in connect_with_states - if any(state.__name__ in getattr(projection_class.sockets, projection_socket) for state in connect_with_states): - # FIX: 10/3/17 - GETS CALLED BY ComparatorMechanism.__init__ BEFORE IT CALLS SUPER, SO - # FIX: SO ITS verbosePref AND name ATTRIBUTES HAVE NOT BEEN ASSIGNED - # if owner.verbosePref: - # warnings.warn("{0} specified to be connected with{1} {2} is compatible with the {3} of the " - # "specified {4} ({5}), but the initialization of the {4} is not yet complete so " - # "compatibility can't be fully confirmed". - # format(State.__name__, connectee_str, owner.name, - # projection_socket, Projection.__name__, projection_spec)) return True else: return False @@ -1772,7 +1700,7 @@ def _validate_projection_type(projection_class): # Projection has been instantiated else: # Determine whether the State to which the Projection's socket has been assigned is in connect_with_states - # FIX: 11/4/17 - THIS IS A MAJOR HACK TO DEAL WITH THE CASE IN WHICH THE connectee_state IS AN OutputState + # FIX: 11/4/17 - THIS IS A HACK TO DEAL WITH THE CASE IN WHICH THE connectee_state IS AN OutputState # FIX: THE projection_socket FOR WHICH IS USUALLY A RECEIVER; # FIX: HOWEVER, IF THE projection_spec IS A GatingSignal # FIX: THEN THE projection_socket MUST BE SENDER @@ -1782,7 +1710,6 @@ def _validate_projection_type(projection_class): projection_socket = SENDER projection_socket_state = getattr(projection_spec, projection_socket) if issubclass(projection_socket_state.__class__, connect_with_states): - # if issubclass(projection_socket_state.__class__, connectee_state): return True # None of the above worked, so must be incompatible @@ -1841,15 +1768,6 @@ def _validate_projection_type(projection_class): # Projection spec is too abstract to validate here # (e.g., value or a name that will be used in context to instantiate it) - # FIX: 10/3/17 - GETS CALLED BY ComparatorMechanism.__init__ BEFORE IT CALLS SUPER, SO - # FIX: SO ITS verbosePref AND name ATTRIBUTES HAVE NOT BEEN ASSIGNED - # if owner.verbosePref: - # warnings.warn("Specification of {} ({}) for connection between {} and{} {} " - # "cannot be fully validated.".format(Projection.__class__.__name__, - # projection_spec, - # connect_with_state_names, - # connectee_str, - # owner.name)) return False diff --git a/psyneulink/components/states/parameterstate.py b/psyneulink/components/states/parameterstate.py index 7019acda541..6cfff076781 100644 --- a/psyneulink/components/states/parameterstate.py +++ b/psyneulink/components/states/parameterstate.py @@ -704,16 +704,14 @@ def _parse_state_specific_specs(self, owner, state_dict, state_specific_spec): for projection_spec in params_dict[PROJECTIONS]: if state_dict[REFERENCE_VALUE] is None: - # FIX: 10/3/17 - PUTTING THIS HERE IS A HACK... - # FIX: MOVE TO _parse_state_spec UNDER PROCESSING OF ProjectionTuple SPEC - # FIX: USING _get_state_for_socket + # FIX: - PUTTING THIS HERE IS A HACK... + # FIX: MOVE TO _parse_state_spec UNDER PROCESSING OF ProjectionTuple SPEC + # FIX: USING _get_state_for_socket # from psyneulink.components.projections.projection import _parse_projection_spec mod_signal_value = projection_spec.state.value \ if isinstance(projection_spec.state, State_Base) else None - - # MODIFIED 11/25/17 OLD: mod_projection = projection_spec.projection if isinstance(mod_projection, dict): if mod_projection[PROJECTION_TYPE] not in {ControlProjection, LearningProjection}: @@ -798,13 +796,6 @@ def _execute(self, function_params, context): value = self.function(variable=param_value, params=function_params, context=context) - - # TEST PRINT - # TEST DEBUG MULTILAYER - # if MATRIX == self.name: - # print("\n{}\n@@@ WEIGHT CHANGES FOR {} TRIAL {}:\n{}". - # format(self.__class__.__name__.upper(), self.owner.name, CentralClock.trial, value)) - return value @property @@ -922,20 +913,16 @@ def _get_tuple_for_single_item_modulatory_spec(obj, name, value): else: return - # MODIFIED 11/25/17 NEW: elif _is_modulatory_spec(param_value, include_matrix_spec=False) and not isinstance(param_value, tuple): # If parameter is a single Modulatory specification (e.g., ControlSignal, or CONTROL, etc.) - # (note: exclude matrix since it is allowed as a value specification vs. a projection reference) - # Try to place it in a tuple (for interpretation by _parse_state_spec) using default value as 1st item - param_value = _get_tuple_for_single_item_modulatory_spec(owner, param_name, param_value) - # MODIFIED 11/25/17 END: + # try to place it in a tuple (for interpretation by _parse_state_spec) using default value as 1st item + # (note: exclude matrix since it is allowed as a value specification but not a projection reference) + param_value = _get_tuple_for_single_item_modulatory_spec(owner, param_name, param_value) # Allow tuples (could be spec that includes a Projection or Modulation) elif isinstance(param_value, tuple): - # # MODIFIED 4/18/17 NEW: # # FIX: EXTRACT VALUE HERE (AS IN Component.__init__?? [4/18/17] # param_value = owner._get_param_value_from_tuple(param_value) - # # MODIFIED 4/18/17 END pass # Allow if it is a keyword for a parameter elif isinstance(param_value, str) and param_value in parameter_keywords: @@ -985,16 +972,14 @@ def _get_tuple_for_single_item_modulatory_spec(obj, name, value): "with the same name as a parameter of the component itself". format(function_name, owner.name, function_param_name)) - # MODIFIED 11/25/17 NEW: elif (_is_modulatory_spec(function_param_value, include_matrix_spec=False) and not isinstance(function_param_value, tuple)): # If parameter is a single Modulatory specification (e.g., ControlSignal, or CONTROL, etc.) + # try to place it in a tuple (for interpretation by _parse_state_spec) using default value as 1st item # (note: exclude matrix since it is allowed as a value specification vs. a projection reference) - # Try to place it in a tuple (for interpretation by _parse_state_spec) using default value as 1st item function_param_value = _get_tuple_for_single_item_modulatory_spec(owner.function, function_param_name, function_param_value) - # MODIFIED 11/25/17 END: # # FIX: 10/3/17 - ??MOVE THIS TO _parse_state_specific_specs ---------------- @@ -1013,11 +998,6 @@ def _get_tuple_for_single_item_modulatory_spec(obj, name, value): from copy import deepcopy reference_value = deepcopy(function_param_value) - - - - # # FIX: ---------------------------------------------------------------------- - # Assign parameterState for function_param to the component state = _instantiate_state(owner=owner, state_type=ParameterState, @@ -1065,10 +1045,8 @@ def _is_legal_param_value(owner, value): if isinstance(value, dict) and VALUE in value: return True - # MODIFIED 11/25/17 NEW: if _is_control_spec(value) or _is_gating_spec(value): return True - # MODIFIED 11/25/17 END # keyword that resolves to one of the above if get_param_value_for_keyword(owner, value) is not None: @@ -1101,27 +1079,3 @@ def _get_parameter_state(sender_owner, sender_type, param_name, component): raise ParameterStateError("There is no ParameterState for the parameter ({}) of {} " "specified in {} for {}". format(param_name, component.name, sender_type, sender_owner.name)) - -# def _assign_default_value(): -# -# elif _is_modulatory_spec(param_value, include_matrix_spec=False): -# # If parameter is a single Modulatory specification (e.g., ControlSignal, or CONTROL, etc.) -# # (note: exclude matrix since it is allowed as a value specification vs. a projection reference) -# if not isinstance(param_value, tuple): -# # Try to place it in a tuple (for interpretation by _parse_state_spec) using default value as 1st item -# try: -# param_default_value = owner.paramClassDefaults[param_name] -# # Only assign default value if it is not None -# if param_default_value is not None: -# param_value = (param_default_value, param_value) -# try: -# # Set actual param (ownner's attribute) to assigned value -# setattr(owner, param_name, param_default_value) -# except: -# raise ParameterStateError("Unable to assign {} as value for {} paramater of {}". -# format(param_value, param_name, owner.name)) -# except ParameterStateError as e: -# raise ParameterStateError(e) -# except: -# raise ParameterStateError("Unrecognized specification for {} paramater of {} ({})". -# format(param_name, owner.name, param_value)) diff --git a/psyneulink/components/states/state.py b/psyneulink/components/states/state.py index 8be6ca086f6..755a91263ac 100644 --- a/psyneulink/components/states/state.py +++ b/psyneulink/components/states/state.py @@ -1133,8 +1133,8 @@ def __init__(self, prefs=prefs, context=context.__class__.__name__) + # IMPLEMENTATION NOTE: MOVE TO COMPOSITION ONCE THAT IS IMPLEMENTED # INSTANTIATE PROJECTIONS SPECIFIED IN projections ARG OR params[PROJECTIONS:<>] - # FIX: 10/3/17 - ??MOVE TO COMPOSITION THAT IS IMPELEMENTED (INSTEAD OF BEING HANDLED BY STATE ITSELF) if PROJECTIONS in self.paramsCurrent and self.paramsCurrent[PROJECTIONS]: self._instantiate_projections(self.paramsCurrent[PROJECTIONS], context=context) else: @@ -1237,14 +1237,13 @@ def _validate_params(self, request_set, target_set=None, context=None): # + FUNCTION_PARAMS: , every entry of which must be one of the following: # ParameterState, projection, 2-item tuple or value """ - - # FIX: 10/3/17 SHOULD ADD CHECK THAT PROJECTION_TYPE IS CONSISTENT WITH TYPE SPECIFIED BY THE - # FIX: RECEIVER/SENDER SOCKET SPECIFICATIONS OF CORRESPONDING PROJECTION TYPES (FOR API) + # FIX: PROJECTION_REFACTOR + # SHOULD ADD CHECK THAT PROJECTION_TYPE IS CONSISTENT WITH TYPE SPECIFIED BY THE + # RECEIVER/SENDER SOCKET SPECIFICATIONS OF CORRESPONDING PROJECTION TYPES (FOR API) if PROJECTIONS in request_set and request_set[PROJECTIONS] is not None: # if projection specification is an object or class reference, needs to be wrapped in a list - # - to be consistent with paramClassDefaults - # - for consistency of treatment below + # to be consistent with paramClassDefaults and for consistency of treatment below projections = request_set[PROJECTIONS] if not isinstance(projections, list): projections = [projections] @@ -1331,12 +1330,13 @@ def _instantiate_function(self, context=None): ) ) - # FIX: 10/3/17 - MOVE THESE TO Projection, WITH self (State) AS ADDED ARG - # FIX: BOTH _instantiate_projections_to_state AND _instantiate_projections_to_state - # FIX: CAN USE self AS connectee STATE, since _parse_connection_specs USES SOCKET TO RESOLVE - # FIX: ALTERNATIVE: BREAK STATE FIELD OF ProjectionTuple INTO sender AND receiver FIELDS, THEN COMBINE - # FIX: _instantiate_projections_to_state AND _instantiate_projections_to_state INTO ONE METHOD - # FIX: MAKING CORRESPONDING ASSIGNMENTS TO send AND receiver FIELDS (WOULD BE CLEARER) + # FIX: PROJECTION_REFACTOR + # - MOVE THESE TO Projection, WITH self (State) AS ADDED ARG + # BOTH _instantiate_projections_to_state AND _instantiate_projections_from_state + # CAN USE self AS connectee STATE, since _parse_connection_specs USES SOCKET TO RESOLVE + # - ALTERNATIVE: BREAK STATE FIELD OF ProjectionTuple INTO sender AND receiver FIELDS, THEN COMBINE + # _instantiate_projections_to_state AND _instantiate_projections_to_state INTO ONE METHOD + # MAKING CORRESPONDING ASSIGNMENTS TO send AND receiver FIELDS (WOULD BE CLEARER) def _instantiate_projections(self, projections, context=None): """Implement any Projection(s) to/from State specified in PROJECTIONS entry of params arg @@ -1357,8 +1357,7 @@ def _instantiate_projections(self, projections, context=None): format(self.__class__.__name__, self.name)) - # IMPLEMENTATION NOTE: MOVE TO COMPOSITION ONCE THAT IS IMPLEMENTED?? - # FIX: 10/3/17 - ADD senders ARG THAT IS THEN PROCESSED BY _parse_connections_specs + # IMPLEMENTATION NOTE: MOVE TO COMPOSITION ONCE THAT IS IMPLEMENTED def _instantiate_projections_to_state(self, projections, context=None): """Instantiate projections to a State and assign them to self.path_afferents @@ -1403,7 +1402,7 @@ def _instantiate_projections_to_state(self, projections, context=None): # For Projection in each ProjectionTuple: # - instantiate the Projection if necessary, and initialize if possible - # - insure its value is compatible with self.value FIX: 10/3/17 ??and variable is compatible with sender's value + # - insure its value is compatible with self.value FIX: ??and variable is compatible with sender's value # - assign it to self.path_afferents or .mod_afferents for connection in projection_tuples: @@ -1615,9 +1614,6 @@ def _instantiate_projection_from_state(self, projection_spec, receiver=None, con # VALIDATE CONNECTION AND RECEIVER SPECS - # FIX: 10/3/17 - # FIX: CLEAN UP THE FOLLOWING WITH REGARD TO RECEIVER, CONNECTION, RECEIVER_STATE, CONNECTION_STATE ETC. - # Validate that State to be connected to specified in receiver is same as any one specified in connection def _get_receiver_state(spec): """Get state specification from ProjectionTuple, which itself may be a ProjectionTuple""" diff --git a/psyneulink/components/system.py b/psyneulink/components/system.py index cb448c590cf..daf6eb35755 100644 --- a/psyneulink/components/system.py +++ b/psyneulink/components/system.py @@ -1052,7 +1052,7 @@ def _instantiate_processes(self, input=None, context=None): # Assign the Process a reference to this System process.systems.append(self) - if process.learning: + if process._learning_enabled: self.learning = True # Get max of Process phaseSpecs @@ -3185,6 +3185,7 @@ def show_graph(self, direction = 'BT', show_learning = False, show_control = False, + show_dimensions = False, origin_color = 'green', terminal_color = 'red', origin_and_terminal_color = 'brown', @@ -3194,6 +3195,10 @@ def show_graph(self, ): """Generate a display of the graph structure of mechanisms and projections in the system. + .. note:: + This method relies on `graphviz `_, which must be installed and imported + (standard with PsyNeuLink pip install) + Displays a graph showing the structure of the System (based on the `System's graph `). By default, only the primary processing Components are shown. However,the **show_learning** and **show_control** arguments can be used to also show the Components associated with `learning @@ -3204,6 +3209,12 @@ def show_graph(self, `LearningProjection` are displayed as diamond-shaped nodes. The numbers in parentheses within a Mechanism node indicate its dimensionality. + COMMENT: + node shapes: https://graphviz.gitlab.io/_pages/doc/info/shapes.html + arrow shapes: https://graphviz.gitlab.io/_pages/doc/info/arrows.html + colors: https://graphviz.gitlab.io/_pages/doc/info/colors.html + COMMENT + Arguments --------- @@ -3214,7 +3225,7 @@ def show_graph(self, specifies whether or not to show the learning components of the system; they will all be displayed in the color specified for **learning_color**. Projections that receive a `LearningProjection` will be shown as a diamond-shaped node. - if set to `ALL`, all Projections associated with learning will be shown: the LearningProjections + if set to *ALL*, all Projections associated with learning will be shown: the LearningProjections as well as from `ProcessingMechanisms ` to `LearningMechanisms ` that convey error and activation information; if set to `True`, only the LearningPojections are shown. @@ -3222,6 +3233,22 @@ def show_graph(self, specifies whether or not to show the control components of the system; they will all be displayed in the color specified for **control_color**. + show_dimensions : bool or ALL or MECHANISMS or PROJECTIONS: default False + specifies whether or not to show dimemsions of Mechanisms (and/or MappingProjections when show_learning is + `True`); can have the following settings: + + * *ALL* -- shows dimensions for both Mechanisms and Projections (see below for formats). + | + * *MECHANISMS* -- shows `Mechanism` input and output dimensions. Input dimensions are shown in parentheses + below the name of the Mechanism; each number represents the dimension of the `variable + ` for each `InputState` of the Mechanism; Output dimensions are shown above + the name of the Mechanism; each number represents the dimension for `value ` of each + of `OutputState` of the Mechanism; + | + * *PROJECTIONS* -- shows `MappingProjection` `matrix ` dimensions. Each is + shown in (x...) format; for standard 2x2 "weight" matrix, the first entry is the number of + rows (input dimension) and the second the number of columns (output dimension). + origin_color : keyword : default 'green', specifies the color in which the `ORIGIN` Mechanisms of the System are displayed. @@ -3257,52 +3284,115 @@ def show_graph(self, """ - from psyneulink.components.mechanisms.processing.objectivemechanism \ - import ObjectiveMechanism - from psyneulink.components.mechanisms.adaptive.learning.learningmechanism \ - import LearningMechanism + from psyneulink.components.mechanisms.processing.objectivemechanism import ObjectiveMechanism + from psyneulink.components.mechanisms.adaptive.learning.learningmechanism import LearningMechanism from psyneulink.components.projections.pathway.mappingprojection import MappingProjection + from psyneulink.components.projections.projection import Projection import graphviz as gv system_graph = self.graph learning_graph=self.learningGraph + if show_dimensions == True: + show_dimensions = ALL default_node_color = 'black' + mechanism_shape = 'oval' + projection_shape = 'diamond' + # projection_shape = 'Mdiamond' + # projection_shape = 'hexagon' + + def _get_label(item): + + + # For Mechanisms, show length of each InputState and OutputState + if isinstance(item, Mechanism): + if show_dimensions in {ALL, MECHANISMS}: + input_str = "in ({})".format(",".join(str(len(input_state.variable)) + for input_state in item.input_states)) + output_str = "out ({})".format(",".join(str(len(output_state.value)) + for output_state in item.output_states)) + return "{}\n{}\n{}".format(output_str, item.name, input_str) + else: + return item.name + + # For Projection, show dimensions of matrix + elif isinstance(item, Projection): + if show_dimensions in {ALL, PROJECTIONS}: + value = np.array(item.matrix) + dim_string = "({})".format("x".join([str(i) for i in value.shape])) + return "{}\n{}".format(item.name, dim_string) + else: + return item.name + + else: + raise SystemError("Unrecognized node type ({}) in graph for {}".format(item, self.name)) + + # build graph and configure visualisation settings G = gv.Digraph(engine = "dot", - node_attr = {'fontsize':'12', 'fontname':'arial', 'shape':'oval', 'color':default_node_color}, - edge_attr = {'arrowhead':'halfopen', 'fontsize': '10', 'fontname': 'arial'}, - graph_attr = {"rankdir" : direction} ) + node_attr = { + 'fontsize':'12', + 'fontname':'arial', + 'shape':mechanism_shape, + 'color':default_node_color + }, + edge_attr = { + # 'arrowhead':'halfopen', + 'fontsize': '10', + 'fontname': 'arial' + }, + graph_attr = { + "rankdir" : direction + } ) # work with system graph rcvrs = list(system_graph.keys()) # loop through receivers for rcvr in rcvrs: - rcvr_name = rcvr.name + rcvr_name = _get_label(rcvr) # rcvr_shape = rcvr.instance_defaults.variable.shape[1] rcvr_label = rcvr_name + G.node(rcvr_label, shape=mechanism_shape) + # handle auto-recurrent projections + for input_state in rcvr.input_states: + for proj in input_state.path_afferents: + if proj.sender.owner is not rcvr: + continue + edge_label = _get_label(proj) + try: + has_learning = proj.has_learning_projection + except AttributeError: + has_learning = None + if show_learning and has_learning: + G.node(edge_label, shape=projection_shape) + G.edge(rcvr_label, edge_label, arrowhead='none') + G.edge(edge_label, rcvr_label) + else: + # render normally + G.edge(rcvr_label, rcvr_label, label=edge_label) # loop through senders sndrs = system_graph[rcvr] for sndr in sndrs: - sndr_name = sndr.name + sndr_name = _get_label(sndr) # sndr_shape = sndr.instance_defaults.variable.shape[1] sndr_label = sndr_name # find edge name - projs = sndr.output_state.efferents - for proj in projs: - if proj.receiver.owner == rcvr: - edge_name = proj.name - # edge_shape = proj.matrix.shape - try: - has_learning = proj.has_learning_projection - except AttributeError: - has_learning = None + for output_state in sndr.output_states: + projs = output_state.efferents + for proj in projs: + if proj.receiver.owner == rcvr: + edge_name = _get_label(proj) + # edge_shape = proj.matrix.shape + try: + has_learning = proj.has_learning_projection + except AttributeError: + has_learning = None edge_label = edge_name # if rcvr is learning mechanism, draw arrow with learning color @@ -3314,9 +3404,9 @@ def show_graph(self, arrow_color="black" if show_learning and has_learning: # expand - G.node(sndr_label, shape="oval") - G.node(edge_label, shape="diamond") - G.node(rcvr_label, shape="oval") + G.node(sndr_label, shape=mechanism_shape) + G.node(edge_label, shape=projection_shape) + G.node(rcvr_label, shape=mechanism_shape) G.edge(sndr_label, edge_label, arrowhead='none') G.edge(edge_label, rcvr_label) else: @@ -3341,36 +3431,34 @@ def show_graph(self, sndrs = learning_graph[rcvr] for sndr in sndrs: edge_label = rcvr._parameter_states['matrix'].mod_afferents[0].name - G.edge(sndr.name, rcvr.name, color=learning_color, label = edge_label) + G.edge(_get_label(sndr), _get_label(rcvr), color=learning_color, label = edge_label) else: - # FIX THIS TO INCLUDE Projections FROM ProcessingMechanisms TO LearningMechanisms # Implement edges for Projections to each LearningMechanism from other LearningMechanisms - # Show Projections to LearningComponents + # and from ProcessingMechanisms if 'ALL' is set for input_state in rcvr.input_states: for proj in input_state.path_afferents: sndr = proj.sender.owner - G.node(rcvr.name, color=learning_color) - # If Projection is not from another learning component, - # don't color and only show if ALL is set + G.node(_get_label(rcvr), color=learning_color) + # If Projection is not from another learning component + # only show if ALL is set, and don't color if (isinstance(sndr, LearningMechanism) or (isinstance(sndr, ObjectiveMechanism) and sndr._role is LEARNING)): - G.node(sndr.name, color=learning_color) + G.node(_get_label(sndr), color=learning_color) else: if show_learning is True: continue - G.edge(sndr.name, rcvr.name, color=learning_color, label=proj.name) + G.edge(_get_label(sndr), _get_label(rcvr), color=learning_color, label=proj.name) # Get Projections to ComparatorMechanism as well if isinstance(sndr, ObjectiveMechanism) and sndr._role is LEARNING and show_learning is ALL: for input_state in sndr.input_states: for proj in input_state.path_afferents: - # Skip any Projections from ProcesInputStates - if isinstance(proj.sender.owner, Process): + # Skip any Projections from ProcesInputStates or SystemInputStates + if isinstance(proj.sender.owner, (Process, System)): continue output_mech = proj.sender.owner - G.edge(output_mech.name, sndr.name, color=learning_color, label=proj.name) - - + G.edge(_get_label(output_mech), _get_label(sndr), color=learning_color, + label=proj.name) # add control graph if show_control @@ -3386,9 +3474,9 @@ def show_graph(self, objmech = connector.sender.owner # main edge - G.node(controller.name, color=control_color) - G.node(objmech.name, color=control_color) - G.edge(objmech.name, controller.name, label=connector.name, color=control_color) + G.node(_get_label(controller), color=control_color) + G.node(_get_label(objmech), color=control_color) + G.edge(_get_label(objmech), _get_label(controller), label=connector.name, color=control_color) # outgoing edges for output_state in controller.control_signals: @@ -3396,22 +3484,22 @@ def show_graph(self, # MODIFIED 7/21/17 CW: this edge_name statement below didn't do anything and caused errors, so # I commented it out. # edge_name - rcvr_name = projection.receiver.owner.name - G.edge(controller.name, rcvr_name, label=projection.name, color=control_color) + rcvr_name = _get_label(projection.receiver.owner) + G.edge(_get_label(controller), rcvr_name, label=projection.name, color=control_color) # incoming edges for istate in objmech.input_states: for proj in istate.path_afferents: - sndr_name = proj.sender.owner.name - G.edge(sndr_name, objmech.name, label=proj.name, color=control_color) + sndr_name = _get_label(proj.sender.owner) + G.edge(sndr_name, _get_label(objmech), label=proj.name, color=control_color) # prediction mechanisms for object_item in self.execution_list: mech = object_item if mech._role is CONTROL and hasattr(mech, 'origin_mech'): - G.node(mech.name, color='purple') + G.node(_get_label(mech), color='purple') recvr = mech.origin_mech - G.edge(mech.name, recvr.name, label=' prediction assignment', color='purple') + G.edge(_get_label(mech), _get_label(recvr), label=' prediction assignment', color='purple') pass # return