• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PrincetonUniversity / PsyNeuLink / 15917088825

05 Jun 2025 04:18AM UTC coverage: 84.482% (+0.5%) from 84.017%
15917088825

push

github

web-flow
Merge pull request #3271 from PrincetonUniversity/devel

Devel

9909 of 12966 branches covered (76.42%)

Branch coverage included in aggregate %.

1708 of 1908 new or added lines in 54 files covered. (89.52%)

25 existing lines in 14 files now uncovered.

34484 of 39581 relevant lines covered (87.12%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.62
/psyneulink/core/components/component.py
1
# Princeton University licenses this file to You under the Apache License, Version 2.0 (the "License");
2
# you may not use this file except in compliance with the License.  You may obtain a copy of the License at:
3
#     http://www.apache.org/licenses/LICENSE-2.0
4
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
5
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
6
# See the License for the specific language governing permissions and limitations under the License.
7

8

9
# ********************************************** Component  ************************************************************
10

11

12
"""
13
Contents
14
--------
15

16
* `Component_Overview`
17
* `Component_Creation`
18
    * `Component_Deferred_Init`
19
* `Component_Structure`
20
    * `Component_Structural_Attributes`
21
        * `Variable <Component_Variable>`
22
        * `Function <Component_Function>`
23
        * `Value <Component_Value>`
24
        * `Log <Component_Log>`
25
        * `Name <Component_Name>`
26
        * `Preferences <Component_Prefs>`
27
    * `User_Modifiable_Parameters`
28
    COMMENT:
29
    * `Methods <Component_Methods>`
30
    COMMENT
31
* `Component_Execution`
32
    * `Component_Execution_Initialization`
33
    * `Component_Execution_Termination`
34
    * `Component_Execution_Count_and_Time`
35
* `Component_Class_Reference`
36

37

38
.. _Component_Overview:
39

40
Overview
41
--------
42

43
Component is the base class for all of the objects used to create `Compositions <Composition>` in PsyNeuLink.
44
It defines a common set of attributes possessed, and methods used by all Component objects.
45

46
.. _Component_Creation:
47

48
Creating a Component
49
--------------------
50

51
A Component is never created by calling the constructor for the Component base class.  However, its ``__init__()``
52
method is always called when a Component subclass is instantiated; that, in turn, calls a standard set of methods
53
(listed `below <Component_Methods>`) as part of the initialization procedure.  Every Component has a core set of
54
`configurable parameters <Parameters>` that can be specified in the arguments of the constructor, as well
55
as additional parameters and attributes that may be specific to particular Components, many of which can be modified
56
by the user, and some of which provide useful information about the Component (see `User_Modifiable_Parameters`
57
and `Informational Attributes` below).
58

59
.. _Component_Deferred_Init:
60

61
*Deferred Initialization*
62
~~~~~~~~~~~~~~~~~~~~~~~~~
63

64
If information necessary to complete initialization is not specified in the constructor (e.g, the **owner** for a
65
`Port <Port_Base.owner>`, or the **sender** or **receiver** for a `Projection <Projection_Structure>`), then its
66
full initialization is deferred until its the information is available (e.g., the `Port <Port>` is assigned to a
67
`Mechanism <Mechanism>`, or a `Projection <Projection>` is assigned its `sender <Projection_Base.sender>` and `receiver
68
<Projection_Base.receiver>`).  This allows Components to be created before all of the information they require is
69
available (e.g., at the beginning of a script). However, for the Component to be operational, its initialization must
70
be completed by a call to it `deferred_init` method.  This is usually done automatically when the Component is
71
assigned to another Component to which it belongs (e.g., assigning a Port to a Mechanism) or to a Composition (e.g.,
72
a Projection to the `pathway <Process.pahtway>`) of a `Process`), as appropriate.
73

74
.. _Component_Structure:
75

76
Component Structure
77
-------------------
78

79
.. _Component_Structural_Attributes:
80

81
*Core Structural Attributes*
82
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83

84
Every Component has the following set of core structural attributes that can be specified in its constructor using the
85
arguments listed below. These attributes are not meant to be changed by the user once the component is constructed,
86
with the one exception of `prefs <Component_Prefs>`.
87

88
.. _Component_Type:
89

90
* **componentType** - species the type of Component.
91

92
.. _Component_Variable:
93

94
* **variable** - used as the input to its `function <Component_Function>`.  Specification of the **default_variable**
95
  argument in the constructor for a Component determines both its format (e.g., whether its value is numeric, its
96
  dimensionality and shape if it is an array, etc.) as well as its `default_value <Component.defaults>` (the value
97
  used when the Component is executed and no input is provided).
98
  It may alternatively be specified by `input_shapes <Component_Input_Shapes>`.
99

100
  .. technical_note::
101
    Internally, the attribute **variable** is not directly used as input to functions, to allow for parallelization.
102
    The attribute is maintained as a way for the user to monitor variable along the execution chain.
103
    During parallelization however, the attribute may not accurately represent the most current value of variable
104
    being used, due to asynchrony inherent to parallelization.
105

106
.. _Component_Input_Shapes:
107

108
* **input_shapes** - the numpy shape or iterable of shapes matching the
109
  `variable <Component.variable>` attribute. The **input_shapes** argument of
110
  the
111
  constructor for a Component can be used as a convenient method for specifying the `variable <Component_Variable>`,
112
  attribute in which case it will be assigned as an array of zeros of
113
  the specified shape. When **input_shapes** is an iterable, each item in the
114
  iterable is treated as a single shape, and the entire iterable is then
115
  assigned as an array. When **input_shapes** is an integer, it is treated the
116
  same as a one-item iterable containing that integer. For example,
117
  setting  **input_shapes** = 3 is equivalent to setting
118
  **variable** = [[0, 0, 0]] and setting **input_shapes** = [4, 3] is equivalent
119
  to setting **variable** = [[0, 0, 0, 0], [0, 0, 0]].
120

121
  .. note::
122
     The input_shapes attribute serves a role similar to
123
     `shape in Numpy <https://numpy.org/doc/stable/reference/generated/numpy.shape.html>`_, with the difference that
124
     input_shapes permits the specification of `ragged arrays <https://en.wikipedia.org/wiki/Jagged_array>`_ -- that is, ones
125
     that have elements of varying lengths, such as [[1,2],[3,4,5]].
126

127
.. _Component_Function:
128

129
* **function** - determines the computation that a Component carries out. It is always a PsyNeuLink `Function
130
  <Function>` object (itself also a PsyNeuLink Component).
131

132
  .. note::
133
     The `function <Component.function>` of a Component can be assigned either a `Function` object or any other
134
     callable object in python.  If the latter is assigned, it is "wrapped" in a `UserDefinedFunction`.
135

136
  All Components have a default `function <Component.function>` (with a default set of parameters), that is used if it
137
  is not otherwise specified.  The `function <Component.function>` can be specified in the
138
  **function** argument of the constructor for the Component, using one of the following:
139

140
    * **class** - this must be a subclass of `Function <Function>`, as in the following example::
141

142
        my_component = SomeComponent(function=SomeFunction)
143

144
      This will create a default instance of the specified subclass, using default values for its parameters.
145

146
    * **Function** - this can be either an existing `Function <Function>` object or the constructor for one, as in the
147
      following examples::
148

149
        my_component = SomeComponent(function=SomeFunction)
150

151
        or
152

153
        some_function = SomeFunction(some_param=1)
154
        my_component = SomeComponent(some_function)
155

156
      The specified Function will be used as a template to create a new Function object that is assigned to the
157
      `function` attribute of the Component.
158

159
      .. note::
160

161
        In the current implementation of PsyNeuLink, if a `Function <Function>` object (or the constructor for one) is
162
        used to specify the `function <Component.function>` attribute of a Component, the Function object specified (or
163
        created) will only *itself* be assigned to the Component if it does not already belong to another Component.
164
        Otherwise, it is copied, and the copy is assigned to the Component.
165
        This is so that `Functions <Function>` can be used as templates for
166
        more than one Component, without being assigned simultaneously to multiple Components.
167

168
  A `function <Component.function>` can also be specified in an entry of a
169
  `parameter specification dictionary <ParameterPort_Specification>` assigned to the
170
  **params** argument of the constructor for the Component, with the keyword *FUNCTION* as its key, and one of the
171
  specifications above as its value, as in the following example::
172

173
        my_component = SomeComponent(params={FUNCTION:SomeFunction(some_param=1)})
174

175
.. _Component_Value:
176

177
* **value** - the `value <Component.value>` attribute generally contains the result (return value) of the Component's
178
  `function <Component.function>` after the function is called, though some Components may override this to return
179
  other values.
180

181
      .. technical_note::
182
         In general, Components that have an execute() method may use this to assign the `value` of the Component
183
         (e.g., `Mechanism_Base`; see `OptimizationControlMechanism` and `LCControlMechanism` for examples).
184
..
185

186
.. _Component_Log:
187

188
* **log** - the `log <Component.log>` attribute contains the Component's `Log`, that can be used to record its `value
189
  <Component.value>`, as well as that of Components that belong to it, during initialization, validation, execution
190
  and learning.  It also has four convenience methods -- `loggable_items <Log.loggable_items>`, `set_log_conditions
191
  <Log.set_log_conditions>`, `log_values <Log.log_values>` and `logged_items <Log.logged_items>` -- that provide
192
  access to the corresponding methods of its Log, used to identify, configure and track items for logging.
193
..
194

195
.. _Component_Name:
196

197
* **name** - the `name <Component.name>` attribute contains the name assigned to the Component when it was created.
198
  If it was not specified, a default is assigned by the `registry <Registry>` for subclass (see `Registry_Naming` for
199
  conventions used in assigning default names and handling of duplicate names).
200
..
201

202
.. _Component_Prefs:
203

204
* **prefs** - the `prefs <Component.prefs>` attribute contains the `PreferenceSet` assigned to the Component when
205
  it was created.  If it was not specified, a default is assigned using `classPreferences` defined in
206
  COMMENT:
207
  THIS SEEMS TO BE INCORRECT:
208
  ``__init__.py``
209
  COMMENT
210
  `BasePreferences`
211
  Each individual preference is accessible as an attribute of the Component, the name of which is the name of the
212
  preference (see `Preferences` for details).
213

214

215
.. _User_Modifiable_Parameters:
216

217
*Parameters*
218
~~~~~~~~~~~~
219

220
.. _Component_Parameters:
221

222
A Component defines its `parameters <Parameters>` in its *parameters* attribute, which contains a collection of
223
`Parameter` objects, each of which stores a Parameter's values, `default values <Component.defaults>`, and various
224
`properties <Parameter_Attributes_Table>` of the parameter.
225

226
* `Parameters <Component.Parameters>` - a `Parameters class <Parameters>` defining parameters and their default values
227
   that are used for all Components, unless overridden.
228

229
   All of the parameters listed in the *parameters* class can be modified by the user (as described above).  Some
230
   can also be modified by `ControlSignals <ControlSignal>` when a `Composition executes <Composition_Execution>`.
231
   In general, only parameters that take numerical values and/or do not affect the structure, mode of operation,
232
   or format of the values associated with a Component can be subject to modulation.  For example, for a
233
   `TransferMechanism`, `clip <TransferMechanism.clip>`, `initial_value <TransferMechanism.initial_value>`,
234
   `integrator_mode <TransferMechanism.integrator_mode>`, `input_ports <Mechanism_Base.input_ports>`,
235
   `output_ports`, and `function <Mechanism_Base.function>`, are all listed in parameters, and are user-modifiable,
236
   but are not subject to modulation; whereas `noise <TransferMechanism.noise>` and `integration_rate
237
   <TransferMechanism.integration_rate>` can all be subject to modulation. Parameters that are subject to modulation
238
   have the `modulable <Parameter.modulable>` attribute set to True and are associated with a `ParameterPort` to which
239
   the ControlSignals can project (by way of a `ControlProjection`).
240

241
  COMMENT:
242
      FIX: ADD DISCUSSION ABOUT HOW TO ASSIGN DEFAULTS HERE 5/8/20
243
  COMMENT
244

245
.. _Component_Function_Params:
246

247
* **initial_shared_parameters** - the `initial_shared_parameters <Component.function>` attribute contains a
248
  dictionary of any parameters for the Component's functions or attributes, to be used to
249
  instantiate the corresponding object.  Each entry is the name of a parameter, and its value is the value of that parameter.
250
  The parameters for a function can be specified when the Component is created in one of the following ways:
251

252
      * in an argument of the **Component's constructor** -- if all of the allowable functions for a Component's
253
        `function <Component.function>` share some or all of their parameters in common, the shared paramters may appear
254
        as arguments in the constructor of the Component itself, which can be used to set their values.
255

256
      * in an entry of a `parameter specification dictionary <ParameterPort_Specification>` assigned to the
257
        **params** argument of the constructor for the Component.  The entry must use the keyword
258
        FUNCTION_PARAMS as its key, and its value must be a dictionary containing the parameters and their values.
259
        The key for each entry in the FUNCTION_PARAMS dictionary must be the name of a parameter, and its value the
260
        parameter's value, as in the example below::
261

262
            my_component = SomeComponent(function=SomeFunction
263
                                         params={FUNCTION_PARAMS:{SOME_PARAM=1, SOME_OTHER_PARAM=2}})
264

265
  The parameters of functions for some Components may allow other forms of specification (see
266
  `ParameterPort_Specification` for details concerning different ways in which the value of a
267
  parameter can be specified).
268

269
COMMENT:
270
    FIX: STATEMENT ABOVE ABOUT MODIFYING EXECUTION COUNT VIOLATES THIS DEFINITION, AS PROBABLY DO OTHER ATTRIBUTES
271
      * parameters are things that govern the operation of the Mechanism (including its function) and/or can be
272
        modified/modulated
273
      * attributes include parameters, but also read-only attributes that reflect but do not determine the operation
274
        (e.g., EXECUTION_COUNT)
275
COMMENT
276

277
.. _Component_Stateful_Parameters:
278

279
* **stateful_parameters** - a list containing all of the Component's `stateful parameters <Parameter_Statefulness>`.
280
  COMMENT:
281
     DESCRIPTION HERE
282
  COMMENT
283

284

285
COMMENT:
286
.. _Component_Methods:
287

288
*Component Methods*
289
~~~~~~~~~~~~~~~~~~~
290

291
   FOR DEVELOPERS:
292

293
    There are two sets of methods that belong to every Component: one set that is called when it is initialized; and
294
    another set that can be called to perform various operations common to all Components.  Each of these is described
295
    briefly below.  All of these methods can be overridden by subclasses to implement customized operations, however
296
    it is strongly recommended that the method be called on super() at some point, so that the standard operations are
297
    carried out.  Whether customization operations should be performed before or after the call to super is discussed in
298
    the descriptions below where relevant.
299

300
    .. _Component_Initialization_Methods:
301

302
    Initialization Methods
303
    ^^^^^^^^^^^^^^^^^^^^^^
304

305
    These methods can be overridden by the subclass to customize the initialization process, but should always call the
306
    corresponding method of the Component base class (using ``super``) to insure full initialization.  There are two
307
    categories of initializion methods:  validation and instantiation.
308

309

310
    .. _Component_Validation_Methods:
311

312
    * **Validation methods** perform a strictly *syntactic* check, to determine if a value being validated conforms
313
    to the format expected for it by the Component (i.e., the type of the value and, if it is iterable, the type its
314
    elements and/or its length).  The value itself is not checked in any other way (e.g., whether it equals a particular
315
    value or falls in a specified range).  If the validation fails, and exception is raised.  Validation methods never
316
    make changes the actual value of an attribute, but they may change its format (e.g., from a list to an ndarray) to
317
    comply with requirements of the Component.
318

319
      * `_validate_variable <Component._validate_variable>` validates the value provided to the keyword:`variable`
320
        argument in the constructor for the Component.  If it is overridden, customized validation should generally
321
        performed *prior* to the call to super(), to allow final processing by the Component base class.
322

323
      * `_validate_params <Component._validate_params>` validates the value of any parameters specified in the
324
        constructor for the Component (whether they are made directly in the argument for a parameter, or in a
325
        `parameter specification dictionary <ParameterPort_Specification>`.  If it is overridden by a subclass,
326
        customized validation should generally be performed *after* the call to super().
327

328
    * **Instantiation methods** create, assign, and/or perform *semantic* checks on the values of Component attributes.
329
      Semantic checks may include value and/or range checks, as well as checks of formatting and/or value
330
      compatibility with other attributes of the Component and/or the attributes of other Components (for example, the
331
      _instantiate_function method checks that the input of the Component's `function <Comonent.function>` is compatible
332
      with its `variable <Component.variable>`).
333

334
      * `_handle_input_shapes <Component._handle_input_shapes>` attempts to infer
335
        `variable <Component.variable>` from the **input_shapes** argument if
336
        **variable** is not passed as an argument.
337
        The _handle_input_shapes method then checks that the **input_shapes** and **variable** arguments are compatible.
338

339
      * `_instantiate_defaults <Component._instantiate_defaults>` first calls the validation methods, and then
340
        assigns the default values for all of the attributes of the instance of the Component being created.
341

342
        _instantiate_attributes_before_function
343
        _instantiate_function
344
        _instantiate_attributes_after_function
345

346
    .. _Component_Callable_Methods:
347

348
    Callable Methods
349
    ^^^^^^^^^^^^^^^^
350

351
    initialize
352
COMMENT
353

354
.. _Component_Assign_Params:
355

356
* **reset_params** - reset the value of all parameters to a set of default values as specified in its **mode**
357
  argument, using a value of `ResetMode <Component_ResetMode>`.
358

359
.. _Component_Execution:
360

361
Execution
362
---------
363

364
A Component is executed when its `execute <Component.execute>` method is called, which in turn calls its `function
365
<Component_Function>`.
366

367
.. _Component_Lazy_Updating:
368

369
*Lazy Updating*
370
~~~~~~~~~~~~~~~
371

372
In general, only `Compositions <Composition>` are executed from the command line (i.e., from the console or in a
373
script).  `Mechanisms <Mechanism>` can also be executed, although this is usually just for the purposes of demonstration
374
or debugging, and `Functions <Function>` can only be executed if they are standalone (that is, they do not belong to
375
another Component).  All other Components are executed only a Component that depends on them to do so.  This can be
376
one to which a Components belongs (such as the Mechanism to which a `Port` belongs) or that otherwise requires it to
377
execute (for example, a updating a `Port` requires its `afferent Projections <Port_Projections>` to `execute
378
<Port_Execution>`).  This is referred to as "lazy updating", since it means that most Components don't execute unless
379
and until they are required to do so.  While this reduces unecessary computation, it can sometimes be confusing. For
380
example, when `learning <Composition_Learning>` occurs in a Composition, the modification to the `matrix
381
<MappingProjection.matrix>` parameter of a `MappingProjection` that occurs on a given `TRIAL <TimeScale.TRIAL>`
382
does not acutally appear in its `value <ParameterPort>` until the next `TRIAL <TimeScale.TRIAL>`, since it requires
383
that the ParameterPort for the `matrix <MappingProjection.matrix>` be executed, which does not occur until the next
384
time the MappingProjection is executed (i.e., in the next `TRIAL <TimeScale.TRIAL>`).  Therefore, in tracking the
385
`value <Component.value>` of Components during execution, it is important to carefully consider the state of
386
execution of the Components to which they belong or on which they depend for execution.
387

388
The following attributes and methods control and provide information about the execution of a Component:
389

390
.. _Component_Execution_Initialization:
391

392
*Initialization*
393
~~~~~~~~~~~~~~~~
394

395
.. _Component_Reset_Stateful_Function_When:
396

397
* **reset_stateful_function_when** -- a `Condition` that determines when the Component's `reset <Component.reset>`
398
  method is called.  The `reset <Component.reset>` method and `reset_stateful_function_when
399
  <Component.reset_stateful_function_when>` attribute only exist for Mechanisms that have `stateful
400
  <Parameter.stateful>` `Parameters`, or that have a `function <Mechanism_Base.function>` with `stateful
401
  <Parameter.stateful>` Parameters.  When the `reset <Component.reset>` method is called, this is done without any
402
  arguments, so that the relevant `initializer <IntegratorFunction.initializer>` attributes (or their equivalents
403
  -- initialization attributes vary among functions) are used for reinitialization.
404
  COMMENT:
405
      WHAT ABOUT initializer ATTRIBUTE FOR NON-INTEGRATOR FUNCTIONS, AND FOR STATEFUL PARAMETERS ON MECHANISMS?
406
      WHY IS THIS ATTRIBUTE ON COMPONENT RATHER THAN MECHANISM?
407
  COMMENT
408

409
  .. note::
410

411
     `Mechanisms <Mechanism>` are the only type of Component that reset when the `reset_stateful_function_when
412
     <Component.reset_stateful_function_when>` `Condition` is satisfied. Other Component types do not reset,
413
     although `Composition` has a `reset <Composition.reset>` method that can be used to reset all of its eligible
414
     Mechanisms (see `Composition_Reset`)
415

416
.. _Component_Execution_Termination:
417

418
*Termination*
419
~~~~~~~~~~~~~
420

421
.. _Component_Is_Finished:
422

423
* **is_finished()** -- method that determines whether execution of the Component is complete for a `TRIAL
424
  <TimeScale.TRIAL>`;  it is only used if `execute_until_finished <Component_Execute_Until_Finished>` is True.
425

426
.. _Component_Execute_Until_Finished:
427

428
* **execute_until_finished** -- determines whether the Component executes until its `is_finished` method returns True.
429
  If it is False, then the Component executes only once per call to its `execute <Component.execute>` method,
430
  irrespective of its `is_finished` method;  if it is True then, depending on how its class implements and handles its
431
  `is_finished` method, the Component may execute more than once per call to its `execute <Component.execute>` method.
432

433
.. _Component_Num_Executions_Before_Finished:
434

435
* **num_executions_before_finished** -- contains the number of times the Component has executed prior to finishing
436
  (and since it last finished);  depending upon the class, these may all be within a single call to the Component's
437
  `execute <Component.execute>` method, or extend over several calls.  It is set to 0 each time `is_finished` evaluates
438
  to True. Note that this is distinct from the `execution_count <Component_Execution_Count>` and `num_executions
439
  <Component_Num_Executions>` attributes.
440

441
.. _Component_Max_Executions_Before_Finished:
442

443
* **max_executions_before_finished** -- determines the maximum number of executions allowed before finishing
444
  (i.e., the maximum allowable value of `num_executions_before_finished <Component.num_executions_before_finished>`).
445
  If it is exceeded, a warning message is generated.  Note that this only pertains to `num_executions_before_finished
446
  <Component_Num_Executions_Before_Finished>`, and not its `execution_count <Component_Execution_Count>`, which can be
447
  unlimited.
448

449
.. _Component_Execution_Count_and_Time:
450

451
*Count and Time*
452
~~~~~~~~~~~~~~~~
453

454
.. _Component_Execution_Count:
455

456
* **execution_count** -- maintains a record of the number of times a Component has executed since it was constructed,
457
  *excluding*  executions carried out during initialization and validation, but including all others whether they are
458
  of the Component on its own are as part of a `Composition`, and irrespective of the `context <Context>` in which
459
  they are occur. The value can be changed "manually" or programmatically by assigning an integer
460
  value directly to the attribute.  Note that this is the distinct from the `num_executions <Component_Num_Executions>`
461
  and `num_executions_before_finished <Component_Num_Executions_Before_Finished>` attributes.
462

463
.. _Component_Num_Executions:
464

465
* **num_executions** -- maintains a record, in a `Time` object, of the number of times a Component has executed in a
466
  particular `context <Context>` and at different `TimeScales <TimeScale>`. The value cannot be changed. Note that this
467
  is the distinct from the `execution_count <Component_Execution_Count>` and `num_executions_before_finished
468
  <Component_Num_Executions_Before_Finished>` attributes.
469

470
.. _Component_Current_Execution_Time:
471

472
* **current_execution_time** -- maintains the `Time` of the last execution of the Component in the context of the
473
  `Composition`'s current `scheduler <Composition.scheduler`, and is stored as a `time
474
  <Context.time>` tuple of values indicating the `TimeScale.TRIAL`,  `TimeScale.PASS`, and `TimeScale.TIME_STEP` of the
475
  last execution.
476

477

478
.. _Component_Class_Reference:
479

480
Class Reference
481
---------------
482

483
COMMENT:
484

485
This module defines the Component abstract class
486

487
It also contains:
488

489
- arg_name definitions for primary Component categories:
490
    Process
491
    Mechanism
492
        types:
493
            DDM
494
            [PDP]
495
    Projection
496
        types:
497
            MappingProjection
498
            ControlProjection
499
            LearningProjection
500
    Function
501
COMMENT
502

503
"""
504
import base64
1✔
505
import collections
1✔
506
import copy
1✔
507
import functools
1✔
508
import inspect
1✔
509
import itertools
1✔
510
import logging
1✔
511
import numbers
1✔
512
import re
1✔
513
import types
1✔
514
import typing
1✔
515
import warnings
1✔
516
import weakref
1✔
517
from abc import ABCMeta
1✔
518
from collections.abc import Iterable
1✔
519
from enum import Enum, IntEnum
1✔
520

521
import dill
1✔
522
import graph_scheduler
1✔
523
import numpy as np
1✔
524

525
from psyneulink._typing import Iterable, Union
1✔
526
from psyneulink.core import llvm as pnlvm
1✔
527
from psyneulink.core.globals.context import \
1✔
528
    Context, ContextError, ContextFlags, INITIALIZATION_STATUS_FLAGS, _get_time, handle_external_context
529
from psyneulink.core.globals.mdf import MDFSerializable
1✔
530
from psyneulink.core.globals.keywords import (
1✔
531
    CONTEXT,
532
    CONTROL_PROJECTION,
533
    DEFERRED_INITIALIZATION,
534
    DETERMINISTIC,
535
    EXECUTE_UNTIL_FINISHED,
536
    FUNCTION,
537
    FUNCTION_PARAMS,
538
    INIT_FULL_EXECUTE_METHOD,
539
    INPUT_PORTS,
540
    LEARNING,
541
    LEARNING_PROJECTION,
542
    MATRIX,
543
    MAX_EXECUTIONS_BEFORE_FINISHED,
544
    MODEL_SPEC_ID_PSYNEULINK,
545
    MODEL_SPEC_ID_METADATA,
546
    MODEL_SPEC_ID_INPUT_PORTS,
547
    MODEL_SPEC_ID_OUTPUT_PORTS,
548
    MODEL_SPEC_ID_MDF_VARIABLE,
549
    MODULATORY_SPEC_KEYWORDS,
550
    NAME,
551
    OUTPUT_PORTS,
552
    OWNER,
553
    PARAMS,
554
    PREFS_ARG,
555
    RANDOM,
556
    RESET_STATEFUL_FUNCTION_WHEN,
557
    INPUT_SHAPES,
558
    VALUE,
559
    VARIABLE,
560
    SHARED_COMPONENT_TYPES,
561
    DEFAULT,
562
)
563
from psyneulink.core.globals.log import LogCondition
1✔
564
from psyneulink.core.globals.parameters import (
1✔
565
    Defaults,
566
    Parameter,
567
    ParameterAlias,
568
    ParameterError,
569
    ParameterInvalidSourceError,
570
    ParametersBase,
571
    SharedParameter,
572
    check_user_specified,
573
    copy_parameter_value,
574
    is_array_like,
575
)
576
from psyneulink.core.globals.preferences.basepreferenceset import BasePreferenceSet, VERBOSE_PREF
1✔
577
from psyneulink.core.globals.preferences.preferenceset import \
1✔
578
    PreferenceLevel, PreferenceSet, _assign_prefs
579
from psyneulink.core.globals.registry import register_category, _get_auto_name_prefix
1✔
580
from psyneulink.core.globals.sampleiterator import SampleIterator
1✔
581
from psyneulink.core.globals.utilities import \
1✔
582
    ContentAddressableList, convert_all_elements_to_np_array, convert_to_np_array, get_deepcopy_with_shared, \
583
    is_instance_or_subclass, is_matrix, iscompatible, kwCompatibilityLength, \
584
    get_all_explicit_arguments, is_numeric, call_with_pruned_args, safe_equals, safe_len, parse_valid_identifier, try_extract_0d_array_item, contains_type, is_iterable
585
from psyneulink.core.scheduling.condition import Never
1✔
586
from psyneulink.core.scheduling.time import Time, TimeScale
1✔
587

588
__all__ = [
1✔
589
    'Component', 'COMPONENT_BASE_CLASS', 'component_keywords', 'ComponentError', 'ComponentLog',
590
    'DefaultsFlexibility', 'DeferredInitRegistry', 'parameter_keywords', 'ResetMode',
591
]
592

593
logger = logging.getLogger(__name__)
1✔
594

595
component_keywords = {NAME, VARIABLE, VALUE, FUNCTION, FUNCTION_PARAMS, PARAMS, PREFS_ARG, CONTEXT}
1✔
596

597
DeferredInitRegistry = {}
1✔
598

599

600
class ResetMode(Enum):
1✔
601
    """
602

603
    .. _Component_ResetMode:
604

605
    ResetModes used for **reset_params**:
606

607
    .. _CURRENT_TO_INSTANCE_DEFAULTS:
608

609
    *CURRENT_TO_INSTANCE_DEFAULTS*
610
      • resets all current values to instance default values.
611

612
    .. _INSTANCE_TO_CLASS:
613

614
    *INSTANCE_TO_CLASS*
615
      • resets all instance default values to class default values.
616

617
    .. _ALL_TO_CLASS_DEFAULTS:
618

619
    *ALL_TO_CLASS_DEFAULTS*
620
      • resets all current values and instance default values to \
621
      class default values
622

623
    """
624
    CURRENT_TO_INSTANCE_DEFAULTS = 0
1✔
625
    INSTANCE_TO_CLASS = 1
1✔
626
    ALL_TO_CLASS_DEFAULTS = 2
1✔
627

628

629
class DefaultsFlexibility(Enum):
1✔
630
    """
631
        Denotes how rigid an assignment to a default is. That is, how much it can be modified, if at all,
632
        to suit the purpose of a method/owner/etc.
633

634
        e.g. when assigning a Function to a Mechanism:
635

636
            ``pnl.TransferMechanism(default_variable=[0, 0], function=pnl.Linear())``
637

638
            the Linear function is assigned a default variable ([0]) based on it's ClassDefault,
639
            which conflicts with the default variable specified by its future owner ([0, 0]). Since
640
            the default for Linear was not explicitly stated, we allow the TransferMechanism to
641
            reassign the Linear's default variable as needed (`FLEXIBLE`)
642

643
    Attributes
644
    ----------
645

646
    FLEXIBLE
647
        can be modified in any way.
648

649
    RIGID
650
        cannot be modifed in any way.
651

652
    INCREASE_DIMENSION
653
        can be wrapped in a single extra dimension.
654

655
    """
656
    FLEXIBLE = 0
1✔
657
    RIGID = 1
1✔
658
    INCREASE_DIMENSION = 2
1✔
659

660

661
parameter_keywords = set()
1✔
662

663
# suppress_validation_preference_set = BasePreferenceSet(prefs = {
664
#     PARAM_VALIDATION_PREF: PreferenceEntry(False,PreferenceLevel.INSTANCE),
665
#     VERBOSE_PREF: PreferenceEntry(False,PreferenceLevel.INSTANCE),
666
#     REPORT_OUTPUT_PREF: PreferenceEntry(True,PreferenceLevel.INSTANCE)})
667

668

669
class ComponentLog(IntEnum):
1✔
670
    NONE            = 0
1✔
671
    ALL = 0
1✔
672
    DEFAULTS = NONE
1✔
673

674

675
class ComponentError(Exception):
1✔
676
    def __init__(self, message, component=None):
1✔
677
        try:
1✔
678
            component_str = component.name
1✔
679
            try:
1✔
680
                if component.owner is not None:
1✔
681
                    component_str = f'{component_str} (owned by {component.owner.name})'
1✔
682
            except AttributeError:
1✔
683
                pass
1✔
684
        except AttributeError:
1✔
685
            component_str = None
1✔
686

687
        if component_str is not None:
1✔
688
            message = f'{component_str}: {message}'
1✔
689

690
        super().__init__(message)
1✔
691

692

693
def _get_parametervalue_attr(param):
1✔
694
    return f'_{param.name}'
1✔
695

696

697
def make_parameter_property(param):
1✔
698
    def getter(self):
1✔
699
        p = getattr(self.parameters, param.name)
1✔
700

701
        if p.port is not None:
1✔
702
            assert p.modulable
1✔
703
            return getattr(self, _get_parametervalue_attr(p))
1✔
704
        else:
705
            # _get does handle stateful case, but checking here avoids
706
            # extra overhead for most_recent_context and external get.
707
            # external get is being used so that dot-notation returns a
708
            # copy of stored numpy arrays. dot-notation is also often
709
            # used internally for non-stateful parameters, like
710
            # function, input_ports, output_ports, etc.
711
            if not p.stateful:
1✔
712
                return p._get()
1✔
713
            else:
714
                return p.get(self.most_recent_context)
1✔
715

716
    def setter(self, value):
1✔
717
        p = getattr(self.parameters, param.name)
1✔
718
        if p.port is not None:
1!
719
            assert p.modulable
×
720
            warnings.warn(
×
721
                'Setting parameter values directly using dot notation'
722
                ' may be removed in a future release. It is replaced with,'
723
                f' for example, <object>.{param.name}.base = {value}',
724
                FutureWarning,
725
            )
726
        try:
1✔
727
            getattr(self.parameters, p.name).set(value, self.most_recent_context)
1✔
728
        except ParameterError as e:
1✔
729
            if 'Pass override=True to force set.' in str(e):
×
730
                raise ParameterError(
731
                    f"Parameter '{p.name}' is read-only. Set at your own risk."
732
                    f' Use .parameters.{p.name}.set with override=True to force set.'
733
                ) from None
734

735
    return property(getter).setter(setter)
1✔
736

737

738
def _has_initializers_setter(value, owning_component=None, context=None, *, compilation_sync=False):
1✔
739
    """
740
    Assign has_initializers status to Component and any of its owners up the hierarchy.
741
    """
742
    if value and not compilation_sync:
1✔
743
        # only update owner's attribute if setting to True, because there may be
744
        # other children that have initializers
745
        try:
1✔
746
            owning_component.owner.parameters.has_initializers._set(value, context)
1✔
747
        except AttributeError:
1✔
748
            # no owner
749
            pass
1✔
750

751
    return value
1✔
752

753
# *****************************************   COMPONENT CLASS  ********************************************************
754

755
class ComponentsMeta(ABCMeta):
1✔
756
    def __init__(self, *args, **kwargs):
1✔
757
        super().__init__(*args, **kwargs)
1✔
758

759
        self.defaults = Defaults(owner=self)
1✔
760
        try:
1✔
761
            parent = self.__mro__[1].parameters
1✔
762
        except AttributeError:
1✔
763
            parent = None
1✔
764
        self.parameters = self.Parameters(owner=self, parent=parent)
1✔
765

766
        for param in self.parameters:
1✔
767
            if not hasattr(self, param.name):
1✔
768
                setattr(self, param.name, make_parameter_property(param))
1✔
769

770
            try:
1✔
771
                if param.default_value.owner is None:
1✔
772
                    param.default_value.owner = param
1✔
773
            except AttributeError:
1✔
774
                pass
1✔
775

776
    # consider removing this for explicitness
777
    # but can be useful for simplicity
778
    @property
1✔
779
    def class_defaults(self):
1✔
780
        return self.defaults
1✔
781

782

783
class Component(MDFSerializable, metaclass=ComponentsMeta):
1✔
784
    """
785
    Component(                 \
786
        default_variable=None, \
787
        input_shapes=None,     \
788
        params=None,           \
789
        name=None,             \
790
        prefs=None,            \
791
        context=None           \
792
    )
793

794
    Base class for Component.
795

796
    The arguments below are ones that can be used in the constructor for any Component subclass.
797

798
    .. note::
799
       Component is an abstract class and should *never* be instantiated by a direct call to its constructor.
800
       It should be instantiated using the constructor for a subclass.
801

802
    COMMENT:
803
    FOR API DOCUMENTATION:
804
        The Component itself can be called without any arguments (in which case it uses its instance defaults) or
805
            one or more variables (as defined by the subclass) followed by an optional params dictionary
806
        The variable(s) can be a function reference, in which case the function is called to resolve the value;
807
            however:  it must be "wrapped" as an item in a list, so that it is not called before being passed
808
                      it must of course return a variable of the type expected for the variable
809
        The input_shapes argument is an int or array of ints, which specify the input_shapes of variable and set variable to be array(s)
810
            of zeros.
811
        The default variableList is a list of default values, one for each of the variables defined in the child class
812
        The params argument is a dictionary; the key for each entry is the parameter name, associated with its value.
813
            + Component subclasses can define the param FUNCTION:<method or Function class>
814
        The instance defaults can be assigned at initialization or using the _instantiate_defaults class method;
815
            - if instance defaults are not assigned on initialization, the corresponding class defaults are assigned
816
        Each Component child class must initialize itself by calling super(childComponentName).__init__()
817
            with a default value for its variable, and optionally an instance default paramList.
818

819
        A subclass MUST either:
820
            - implement a <class>.function method OR
821
            - specify a default Function
822
            - this is checked in Component._instantiate_function()
823
            - if params[FUNCTION] is NOT specified, it is assigned to self.function (so that it can be referenced)
824
            - if params[FUNCTION] IS specified, it assigns it's value to self.function (superceding existing value):
825
                self.function is aliased to it (in Component._instantiate_function):
826
                    if FUNCTION is found on initialization:
827
                        if it is a reference to an instantiated function, self.function is pointed to it
828
                        if it is a class reference to a function:
829
                            it is instantiated using self.defaults.variable and FUNCTION_PARAMS (if they are there too)
830
                            this works, since _validate_params is always called after _validate_variable
831
                            so self.defaults.variable can be used to initialize function
832
                            to the method referenced by self.defaults.function
833
                    if self.function is found, an exception is raised
834

835
        NOTES:
836
            * In the current implementation, validation is:
837
              - top-level only (items in lists, tuples and dictionaries are not checked, nor are nested items)
838
              - for type only (it is oblivious to content)
839
              - forgiving (e.g., no distinction is made among numberical types)
840
            * However, more restrictive validation (e.g., recurisve, range checking, etc.) can be achieved
841
                by overriding the class _validate_variable and _validate_params methods
842

843
    COMMENT
844

845
    Arguments
846
    ---------
847

848
    default_variable : scalar, list or array : default [[0]]
849
        specifies template for the input to the Component's `function <Component.function>`, and the value used as the
850
        input to the Component if none is provided on execution (see `Component_Variable` for additional information).
851

852
    input_shapes : int, or Iterable of tuple or int : default None
853
        specifies default_variable as array(s) of zeros if **default_variable** is not passed as an argument;
854
        if **default_variable** is specified, it is checked for
855
        compatibility against **input_shapes** (see
856
        `input_shapes <Component_Input_Shapes>` for additonal details).
857

858
    COMMENT:
859
    param_defaults :   :  default None,
860
    COMMENT
861

862
    params : Dict[param keyword: param value] : default None
863
        a `parameter dictionary <ParameterPort_Specification>` that can be used to specify the parameters for
864
        the Component and/or a custom function and its parameters. Values specified for parameters in the dictionary
865
        override any assigned to those parameters in arguments of the constructor.
866

867
    name : str : for default see `name <Component_Name>`
868
        a string used for the name of the Component;  default is assigned by relevant `Registry` for Component
869
        (see `Registry_Naming` for conventions used for default and duplicate names).
870

871
    prefs : PreferenceSet or specification dict : default Component.classPreferences
872
        specifies the `PreferenceSet` for the Component (see `prefs <Component_Base.prefs>` for details).
873

874
    context : Context : default None
875
        specifies `context <Context>` in which Component is being initialized or executed.
876

877

878
    Attributes
879
    ----------
880

881
    variable : 2d np.array
882
        see `variable <Component_Variable>`
883

884
    input_shapes : Union[int, Iterable[Union[int, tuple]]]
885
        see `input_shapes <Component_Input_Shapes>`
886

887
    function : Function, function or method
888
        see `function <Component_Function>`
889

890
    value : 2d np.array
891
        see `value <Component_Value>`
892

893
    log : Log
894
        see `log <Component_Log>`
895

896
    execution_count : int
897
        see `execution_count <Component_Execution_Count>`
898

899
    num_executions : Time
900
        see `num_executions <_Component_Num_Executions>`
901

902
    current_execution_time : tuple(`Time.RUN`, `Time.TRIAL`, `Time.PASS`, `Time.TIME_STEP`)
903
        see `current_execution_time <Component_Current_Execution_Time>`
904

905
    execute_until_finished : bool
906
        see `execute_until_finished <Component_Execute_Until_Finished>`
907

908
    num_executions_before_finished : int
909
        see `num_executions_before_finished <Component_Num_Executions_Before_Finished>`
910

911
    max_executions_before_finished : bool
912
        see `max_executions_before_finished <Component_Max_Executions_Before_Finished>`
913

914
    stateful_parameters : list
915
        see `stateful_parameters <Component_Stateful_Parameters>`
916

917
    reset_stateful_function_when : `Condition`
918
        see `reset_stateful_function_when <Component_reset_stateful_function_when>`
919

920
    name : str
921
        see `name <Component_Name>`
922

923
    prefs : PreferenceSet
924
        see `prefs <Component_Prefs>`
925

926
    parameters :  Parameters
927
        see `parameters <Component_Parameters>` and `Parameters` for additional information.
928

929
    defaults : Defaults
930
        an object that provides access to the default values of a `Component's` `parameters`;
931
        see `parameter defaults <Parameter_Defaults>` for additional information.
932

933
    initialization_status : field of flags attribute
934
        indicates the state of initialization of the Component;
935
        one and only one of the following flags is always set:
936

937
            * `DEFERRED_INIT <ContextFlags.DEFERRED_INIT>`
938
            * `INITIALIZING <ContextFlags.INITIALIZING>`
939
            * `VALIDATING <ContextFlags.VALIDATING>`
940
            * `INITIALIZED <ContextFlags.INITIALIZED>`
941
            * `RESET <ContextFlags.RESET>`
942
            * `UNINITIALIZED <ContextFlags.UNINITALIZED>`
943

944
    COMMENT:
945
    FIX: THESE USED TO BE IN CONSTRUCTORS FOR ALL SUBCLASSES.  INTEGRATE WITH ABOVE
946
    params : Dict[param keyword: param value] : default None
947
        a `parameter dictionary <ParameterPort_Specification>` that can be used to specify the parameters for
948
        the InputPort or its function, and/or a custom function and its parameters. Values specified for parameters in
949
        the dictionary override any assigned to those parameters in arguments of the constructor.
950

951
    name : str : default see `name <InputPort.name>`
952
        specifies the name of the InputPort; see InputPort `name <InputPort.name>` for details.
953

954
    prefs : PreferenceSet or specification dict : default Port.classPreferences
955
        specifies the `PreferenceSet` for the InputPort; see `prefs <InputPort.prefs>` for details.
956
    COMMENT
957

958
    """
959

960
    #CLASS ATTRIBUTES
961
    className = "COMPONENT"
1✔
962
    suffix = " " + className
1✔
963
# IMPLEMENTATION NOTE:  *** CHECK THAT THIS DOES NOT CAUSE ANY CHANGES AT SUBORDNIATE LEVELS TO PROPOGATE EVERYWHERE
964
    componentCategory = None
1✔
965
    componentType = None
1✔
966

967
    standard_constructor_args = {EXECUTE_UNTIL_FINISHED, FUNCTION_PARAMS, MAX_EXECUTIONS_BEFORE_FINISHED, RESET_STATEFUL_FUNCTION_WHEN, INPUT_SHAPES}
1✔
968
    deprecated_constructor_args = {
1✔
969
        'size': 'input_shapes',
970
    }
971

972
    # helper attributes for MDF model spec
973
    _model_spec_id_parameters = 'parameters'
1✔
974
    _model_spec_id_stateful_parameters = 'stateful_parameters'
1✔
975

976
    _model_spec_generic_type_name = NotImplemented
1✔
977
    """
978
        string describing this class's generic type in universal model
979
        specification,
980
        if it exists and is different than the class name
981
    """
982

983
    _model_spec_class_name_is_generic = False
1✔
984
    """
985
        True if the class name is the class's generic type in universal model specification,
986
        False otherwise
987
    """
988

989
    _specified_variable_shape_flexibility = DefaultsFlexibility.RIGID
1✔
990
    """
991
        The `DefaultsFlexibility` ._variable_shape_flexibility takes on
992
        when variable shape was manually specified
993
    """
994

995
    class Parameters(ParametersBase):
1✔
996
        """
997
            The `Parameters` that are associated with all `Components`
998

999
            Attributes
1000
            ----------
1001

1002
                variable
1003
                    see `variable <Component_Variable>`
1004

1005
                    :default value: numpy.array([0])
1006
                    :type: ``numpy.ndarray``
1007
                    :read only: True
1008

1009
                value
1010
                    see `value <Component_Value>`
1011

1012
                    :default value: numpy.array([0])
1013
                    :type: ``numpy.ndarray``
1014
                    :read only: True
1015

1016
                execute_until_finished
1017
                    see `execute_until_finished <Component_Execute_Until_Finished>`
1018

1019
                    :default value: True
1020
                    :type: ``bool``
1021

1022
                execution_count
1023
                    see `execution_count <Component_Execution_Count>`
1024

1025
                    :default value: 0
1026
                    :type: ``int``
1027
                    :read only: True
1028

1029
                num_executions
1030
                    see `num_executions <_Component_Num_Executions>`
1031

1032
                    :default value:
1033
                    :type: ``Time``
1034
                    :read only: True
1035

1036
                has_initializers
1037
                    see `has_initializers <Component.has_initializers>`
1038

1039
                    :default value: False
1040
                    :type: ``bool``
1041

1042
                is_finished_flag
1043
                    internal parameter used by some Component types to track previous status of is_finished() method,
1044
                    or to set the status reported by the is_finished (see `is_finished <Component_Is_Finished>`
1045

1046
                    :default value: True
1047
                    :type: ``bool``
1048

1049
                max_executions_before_finished
1050
                    see `max_executions_before_finished <Component_Max_Executions_Before_Finished>`
1051

1052
                    :default value: 1000
1053
                    :type: ``int``
1054

1055
                num_executions_before_finished
1056
                    see `num_executions_before_finished <Component_Num_Executions_Before_Finished>`
1057

1058
                    :default value: 0
1059
                    :type: ``int``
1060
                    :read only: True
1061
        """
1062
        variable = Parameter(np.array([0]), read_only=True, pnl_internal=True, constructor_argument='default_variable')
1✔
1063
        value = Parameter(np.array([0]), read_only=True, pnl_internal=True)
1✔
1064
        has_initializers = Parameter(False, setter=_has_initializers_setter, pnl_internal=True)
1✔
1065
        # execution_count is not stateful because it is a global counter;
1066
        #    for context-specific counts should use schedulers which store this info
1067
        execution_count = Parameter(0,
1✔
1068
                                    read_only=True,
1069
                                    loggable=False,
1070
                                    stateful=False,
1071
                                    fallback_value=DEFAULT,
1072
                                    pnl_internal=True)
1073
        is_finished_flag = Parameter(True, loggable=False, stateful=True)
1✔
1074
        execute_until_finished = Parameter(True, pnl_internal=True)
1✔
1075
        num_executions = Parameter(
1✔
1076
            Time(),
1077
            read_only=True,
1078
            modulable=False,
1079
            loggable=False,
1080
            fallback_value=DEFAULT,
1081
            pnl_internal=True
1082
        )
1083
        num_executions_before_finished = Parameter(0, read_only=True, modulable=False, pnl_internal=True)
1✔
1084
        max_executions_before_finished = Parameter(1000, modulable=False, pnl_internal=True)
1✔
1085

1086
        def _parse_variable(self, variable):
1✔
1087
            if variable is None:
1✔
1088
                return variable
1✔
1089

1090
            try:
1✔
1091
                return convert_to_np_array(variable)
1✔
1092
            except ValueError:
×
1093
                return convert_all_elements_to_np_array(variable)
×
1094

1095
        def _validate_variable(self, variable):
1✔
1096
            return None
1✔
1097

1098
        def _parse_modulable(self, param_name, param_value):
1✔
1099
            from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism import ModulatoryMechanism_Base
1✔
1100
            from psyneulink.core.components.ports.modulatorysignals import ModulatorySignal
1✔
1101
            from psyneulink.core.components.projections.modulatory.modulatoryprojection import ModulatoryProjection_Base
1✔
1102

1103
            # assume 2-tuple with class/instance as second item is a proper
1104
            # modulatory spec, can possibly add in a flag on acceptable
1105
            # classes in the future
1106
            if (
1✔
1107
                isinstance(param_value, tuple)
1108
                and len(param_value) == 2
1109
                and (
1110
                    is_instance_or_subclass(param_value[1], Component)
1111
                    or (
1112
                        isinstance(param_value[1], str)
1113
                        and param_value[1] in MODULATORY_SPEC_KEYWORDS
1114
                    )
1115
                )
1116
            ):
1117
                value = param_value[0]
1✔
1118
            elif (
1✔
1119
                is_instance_or_subclass(
1120
                    param_value,
1121
                    (ModulatoryMechanism_Base, ModulatorySignal, ModulatoryProjection_Base)
1122
                )
1123
                or (
1124
                    isinstance(param_value, str)
1125
                    and param_value in MODULATORY_SPEC_KEYWORDS
1126
                )
1127
            ):
1128
                value = getattr(self, param_name).default_value
1✔
1129
            else:
1130
                value = param_value
1✔
1131

1132
            if isinstance(value, list):
1✔
1133
                value = np.asarray(value)
1✔
1134

1135
            return value
1✔
1136

1137
    initMethod = INIT_FULL_EXECUTE_METHOD
1✔
1138

1139
    classPreferenceLevel = PreferenceLevel.COMPOSITION
1✔
1140
    # Any preferences specified below will override those specified in COMPOSITION_DEFAULT_PREFERENCES
1141
    # Note: only need to specify setting;  level will be assigned to COMPOSITION automatically
1142
    # classPreferences = {
1143
    #     PREFERENCE_SET_NAME: 'ComponentCustomClassPreferences',
1144
    #     PREFERENCE_KEYWORD<pref>: <setting>...}
1145

1146
    exclude_from_parameter_ports = [INPUT_PORTS, OUTPUT_PORTS]
1✔
1147

1148
    # IMPLEMENTATION NOTE: This is needed so that the Port class can be used with ContentAddressableList,
1149
    #                      which requires that the attribute used for addressing is on the class;
1150
    #                      it is also declared as a property, so that any assignments are validated to be strings,
1151
    #                      insuring that assignment by one instance will not affect the value of others.
1152
    name = None
1✔
1153

1154
    _deepcopy_shared_keys = frozenset(['owner'])
1✔
1155

1156
    @check_user_specified
1✔
1157
    def __init__(self,
1✔
1158
                 default_variable,
1159
                 param_defaults,
1160
                 input_shapes=None,
1161
                 function=None,
1162
                 name=None,
1163
                 reset_stateful_function_when=None,
1164
                 prefs=None,
1165
                 function_params=None,
1166
                 **kwargs):
1167
        """Assign default preferences; enforce required params; validate and instantiate params and execute method
1168

1169
        Initialization arguments:
1170
        - default_variable (anything): establishes type for the variable, used for validation
1171
        - input_shapes (int or list/array of ints): if specified, establishes variable if variable was not already specified
1172
        - params_default (dict): assigned as default
1173
        Note: if parameter_validation is off, validation is suppressed (for efficiency) (Component class default = on)
1174

1175
        """
1176
        context = Context(
1✔
1177
            source=ContextFlags.CONSTRUCTOR,
1178
            execution_phase=ContextFlags.IDLE,
1179
            execution_id=None,
1180
        )
1181

1182

1183

1184
        if reset_stateful_function_when is not None:
1✔
1185
            self.reset_stateful_function_when = reset_stateful_function_when
1✔
1186
        else:
1187
            self.reset_stateful_function_when = Never()
1✔
1188

1189
        parameter_values, function_params = self._parse_arguments(
1✔
1190
            default_variable, param_defaults, input_shapes, function, function_params, kwargs
1191
        )
1192

1193
        self._initialize_parameters(
1✔
1194
            context=context,
1195
            **parameter_values
1196
        )
1197

1198
        var = call_with_pruned_args(
1✔
1199
            self._handle_default_variable,
1200
            **parameter_values
1201
        )
1202
        if var is None:
1✔
1203
            default_variable = self.defaults.variable
1✔
1204
        else:
1205
            default_variable = var
1✔
1206
            self.parameters.variable._user_specified = True
1✔
1207

1208
        self.defaults.variable = copy.deepcopy(default_variable)
1✔
1209

1210
        self.parameters.variable._set(
1✔
1211
            copy_parameter_value(default_variable),
1212
            context=context,
1213
            skip_log=True,
1214
            skip_history=True,
1215
            override=True
1216
        )
1217

1218
        # ASSIGN PREFS
1219
        _assign_prefs(self, prefs, BasePreferenceSet)
1✔
1220

1221
        # VALIDATE VARIABLE AND PARAMS, AND ASSIGN DEFAULTS
1222

1223
        # TODO: the below overrides setting default values to None context,
1224
        # at least in stateless parameters. Possibly more. Below should be
1225
        # removed eventually
1226

1227
        # Validate the set passed in
1228
        self._instantiate_defaults(variable=default_variable,
1✔
1229
                                   request_set={k: v for (k, v) in self.defaults.values().items() if k in parameter_values},  # requested set
1230
               assign_missing=True,                   # assign missing params from classPreferences to instanceDefaults
1231
               target_set=self.defaults.values(), # destination set to which params are being assigned
1232
               default_set=self.class_defaults.values(),   # source set from which missing params are assigned
1233
               context=context,
1234
               )
1235

1236
        self.initial_shared_parameters = collections.defaultdict(dict)
1✔
1237

1238
        for param_name, param in self.parameters.values(show_all=True).items():
1✔
1239
            if (
1✔
1240
                isinstance(param, SharedParameter)
1241
                and not isinstance(param.source, ParameterAlias)
1242
            ):
1243
                try:
1✔
1244
                    if parameter_values[param_name] is not None:
1✔
1245
                        isp_val = parameter_values[param_name]
1✔
1246
                    else:
1247
                        isp_val = copy.deepcopy(param.default_value)
1✔
1248
                except KeyError:
1✔
1249
                    isp_val = copy.deepcopy(param.default_value)
1✔
1250

1251
                if isp_val is not None:
1✔
1252
                    self.initial_shared_parameters[param.attribute_name][param.shared_parameter_name] = isp_val
1✔
1253

1254
        # we must know the final variable shape before setting up parameter
1255
        # Functions or they will mismatch
1256
        self._instantiate_parameter_classes(context)
1✔
1257

1258
        # self.componentName = self.componentType
1259
        try:
1✔
1260
            self.componentName = self.componentName or self.componentType
1✔
1261
        except AttributeError:
1✔
1262
            self.componentName = self.componentType
1✔
1263

1264
        # ENFORCE REGISRY
1265
        if self.__class__.__bases__[0].__bases__[0].__bases__[0].__name__ == 'ShellClass':
1✔
1266
            try:
1✔
1267
                self.__class__.__bases__[0].registry
1✔
1268
            except AttributeError:
×
1269
                raise ComponentError("{0} is a category class and so must implement a registry".
1270
                                    format(self.__class__.__bases__[0].__name__))
1271

1272
        # ASSIGN LOG
1273
        from psyneulink.core.globals.log import Log
1✔
1274
        self.log = Log(owner=self)
1✔
1275
        # Used by run to store return value of execute
1276
        self.results = []
1✔
1277

1278
        if function is None:
1✔
1279
            if (
1✔
1280
                param_defaults is not None
1281
                and FUNCTION in param_defaults
1282
                and param_defaults[FUNCTION] is not None
1283
            ):
1284
                function = param_defaults[FUNCTION]
1✔
1285
            else:
1286
                try:
1✔
1287
                    function = self.class_defaults.function
1✔
1288
                except AttributeError:
1✔
1289
                    # assume function is a method on self
1290
                    pass
1✔
1291

1292
        self._runtime_params_reset = {}
1✔
1293

1294
        # KDM 11/12/19: this exists to deal with currently unknown attribute
1295
        # setting - if not set these will be included in logs as COMMAND_LINE
1296
        # settings. Remove this eventually
1297
        self.most_recent_context = context
1✔
1298

1299
        # INSTANTIATE ATTRIBUTES BEFORE FUNCTION
1300
        # Stub for methods that need to be executed before instantiating function
1301
        #    (e.g., _instantiate_sender and _instantiate_receiver in Projection)
1302
        # Allow _instantiate_attributes_before_function of subclass
1303
        #    to modify/replace function arg provided in constructor (e.g. TransferWithCosts)
1304
        function = self._instantiate_attributes_before_function(function=function, context=context) or function
1✔
1305

1306
        # INSTANTIATE FUNCTION
1307
        #    - assign initial function parameter values from ParameterPorts,
1308
        #    - assign function's output to self.defaults.value (based on call of self.execute)
1309
        self._instantiate_function(function=function, function_params=function_params, context=context)
1✔
1310

1311
        self._instantiate_value(context=context)
1✔
1312

1313
        # INSTANTIATE ATTRIBUTES AFTER FUNCTION
1314
        # Stub for methods that need to be executed after instantiating function
1315
        #    (e.g., instantiate_output_port in Mechanism)
1316
        self._instantiate_attributes_after_function(context=context)
1✔
1317

1318
        self._validate(context=context)
1✔
1319

1320
        self.initialization_status = ContextFlags.INITIALIZED
1✔
1321

1322
        self._update_parameter_components(context)
1✔
1323

1324
        self.compositions = weakref.WeakSet()
1✔
1325

1326
        # Delete the _user_specified_args attribute, we don't need it anymore
1327
        del self._user_specified_args
1✔
1328

1329
    def __repr__(self):
1330
        return '({0} {1})'.format(type(self).__name__, self.name)
1331
        #return '{1}'.format(type(self).__name__, self.name)
1332

1333
    def __lt__(self, other):
1✔
1334
        return self.name < other.name
1✔
1335

1336
    def __deepcopy__(self, memo):
1✔
1337
        if SHARED_COMPONENT_TYPES in memo:
1✔
1338
            if (
1✔
1339
                memo[SHARED_COMPONENT_TYPES]
1340
                and isinstance(self, memo[SHARED_COMPONENT_TYPES])
1341
            ):
1342
                return self
1✔
1343
        else:
1344
            memo[SHARED_COMPONENT_TYPES] = (Component,)
1✔
1345

1346
        fun = get_deepcopy_with_shared(self._deepcopy_shared_keys)
1✔
1347
        newone = fun(self, memo)
1✔
1348
        memo[id(self)] = newone
1✔
1349

1350
        if newone.parameters is not newone.class_parameters:
1✔
1351
            # may be in DEFERRED INIT, so parameters/defaults belongs to class
1352
            newone.parameters._owner = newone
1✔
1353
            newone.defaults._owner = newone
1✔
1354

1355
            for p in newone.parameters:
1✔
1356
                p._owner = newone.parameters
1✔
1357

1358
        # by copying, this instance is no longer "inherent" to a single
1359
        # 'import psyneulink' call
1360
        newone._is_pnl_inherent = False
1✔
1361

1362
        return newone
1✔
1363

1364
    # ------------------------------------------------------------------------------------------------------------------
1365
    # Compilation support
1366
    # ------------------------------------------------------------------------------------------------------------------
1367
    def _is_compilable_param(self, p):
1✔
1368

1369
        # User only parameters are not compiled.
1370
        if p.read_only and p.getter is not None:
1✔
1371
            return False
1✔
1372

1373
        # Shared and aliased parameters are for user conveniecne and not compiled.
1374
        if isinstance(p, (ParameterAlias, SharedParameter)):
1✔
1375
            return False
1✔
1376

1377
        # TODO this should use default value
1378
        val = p.get()
1✔
1379

1380
        # Strings, builtins, functions, and methods are not compilable
1381
        return not isinstance(val, (str,
1✔
1382
                                    type(max),
1383
                                    type(np.sum),
1384
                                    type(make_parameter_property),
1385
                                    type(self._get_compilation_params)))
1386

1387

1388
    def _get_compilation_state(self):
1✔
1389
        # FIXME: MAGIC LIST, Use stateful tag for this
1390
        whitelist = {"previous_time", "previous_value", "previous_v",
1✔
1391
                     "previous_w", "random_state",
1392
                     "input_ports", "output_ports",
1393
                     "adjustment_cost", "intensity_cost", "duration_cost",
1394
                     "intensity"}
1395

1396
        # Prune subcomponents (which are enabled by type rather than a list)
1397
        # that should be omitted
1398
        blacklist = { "objective_mechanism", "agent_rep", "projections", "shadow_inputs"}
1✔
1399

1400
        # Mechanisms;
1401
        # * use "value" state
1402
        # * can execute 'until finished'
1403
        # * need to track number of executions
1404
        if hasattr(self, 'ports'):
1✔
1405
            whitelist.update({"value", "num_executions_before_finished",
1✔
1406
                              "num_executions", "is_finished_flag"})
1407

1408
            # If both the mechanism and its function use random_state.
1409
            # it's DDM with integrator function.
1410
            # The mechanism's random_state is not used.
1411
            if hasattr(self.parameters, 'random_state') and hasattr(self.function.parameters, 'random_state'):
1✔
1412
                whitelist.remove('random_state')
1✔
1413

1414

1415
        # Compositions need to track number of executions
1416
        if hasattr(self, 'nodes'):
1✔
1417
            whitelist.add("num_executions")
1✔
1418

1419
        # Drop combination function params from RTM if not needed
1420
        if getattr(self.parameters, 'has_recurrent_input_port', False):
1✔
1421
            blacklist.add('combination_function')
1✔
1422

1423
        # Drop integrator function if integrator_mode is not enabled
1424
        if not getattr(self, 'integrator_mode', False):
1✔
1425
            blacklist.add('integrator_function')
1✔
1426

1427
        # Drop unused cost functions
1428
        cost_functions = getattr(self, 'enabled_cost_functions', None)
1✔
1429
        if cost_functions is not None:
1✔
1430
            if cost_functions.INTENSITY not in cost_functions:
1✔
1431
                blacklist.add('intensity_cost_fct')
1✔
1432
            if cost_functions.ADJUSTMENT not in cost_functions:
1✔
1433
                blacklist.add('adjustment_cost_fct')
1✔
1434
            if cost_functions.DURATION not in cost_functions:
1✔
1435
                blacklist.add('duration_cost_fct')
1✔
1436

1437
        if getattr(self, "mode", None) == DETERMINISTIC and getattr(self, "tie", None) != RANDOM:
1✔
1438
            whitelist.remove('random_state')
1✔
1439

1440
        # Drop previous_value from MemoryFunctions
1441
        if hasattr(self.parameters, 'duplicate_keys'):
1✔
1442
            blacklist.add("previous_value")
1✔
1443

1444
        # Matrices of learnable projections are stateful
1445
        if getattr(self, 'owner', None) and getattr(self.owner, 'learnable', False):
1✔
1446
            whitelist.add('matrix')
1✔
1447

1448
        def _is_compilation_state(p):
1✔
1449
            # FIXME: This should use defaults instead of 'p.get'
1450
            return (
1✔
1451
                p.name not in blacklist
1452
                # check before p.get to avoid possible SharedParameter exceptions
1453
                and self._is_compilable_param(p)
1454
                and (p.name in whitelist or isinstance(p.get(), Component))
1455
            )
1456

1457
        return filter(_is_compilation_state, self.parameters)
1✔
1458

1459
    def _get_state_ids(self):
1✔
1460
        return [sp.name for sp in self._get_compilation_state()]
1✔
1461

1462
    @functools.cached_property
1✔
1463
    def llvm_state_ids(self):
1✔
1464
        return self._get_state_ids()
1✔
1465

1466
    def _get_state_initializer(self, context):
1✔
1467
        def _convert(p):
1✔
1468
            x = p.get(context)
1✔
1469
            if p.name == 'matrix': # Flatten matrix
1✔
1470
                val = tuple(np.asarray(x, dtype=float).ravel())
1✔
1471
            elif isinstance(x, np.random.RandomState):
1✔
1472
                state = x.get_state(legacy=False)
1✔
1473

1474
                # Keep the indices in sync with bultins.py:get_mersenne_twister_state_struct
1475
                val = pnlvm._tupleize((state['state']['key'],
1✔
1476
                                       state['gauss'],
1477
                                       state['state']['pos'],
1478
                                       state['has_gauss'],
1479
                                       x.used_seed[0]))
1480
            elif isinstance(x, np.random.Generator):
1✔
1481
                state = x.bit_generator.state
1✔
1482

1483
                # Keep the indices in sync with bultins.py:get_philox_state_struct
1484
                val = pnlvm._tupleize((state['state']['counter'],
1✔
1485
                                       state['state']['key'],
1486
                                       state['buffer'],
1487
                                       state['uinteger'],
1488
                                       state['buffer_pos'],
1489
                                       state['has_uint32'],
1490
                                       x.used_seed[0]))
1491
            elif isinstance(x, Time):
1✔
1492
                val = tuple(x._get_by_time_scale(t) for t in TimeScale)
1✔
1493
            elif isinstance(x, Component):
1✔
1494
                return x._get_state_initializer(context)
1✔
1495
            elif isinstance(x, ContentAddressableList):
1✔
1496
                return tuple(p._get_state_initializer(context) for p in x)
1✔
1497
            else:
1498
                val = pnlvm._tupleize(x)
1✔
1499

1500
            return tuple(val for _ in range(p.history_min_length + 1))
1✔
1501

1502
        return tuple(map(_convert, self._get_compilation_state()))
1✔
1503

1504
    def _get_compilation_params(self):
1✔
1505
        # FIXME: MAGIC LIST, detect used parameters automatically
1506
        blacklist = {# Stateful parameters
1✔
1507
                     "previous_time", "previous_value", "previous_v",
1508
                     "previous_w", "random_state", "is_finished_flag",
1509
                     "num_executions_before_finished", "num_executions",
1510
                     "variable", "value", "saved_values", "saved_samples",
1511
                     "integrator_function_value", "termination_measure_value",
1512
                     "execution_count", "intensity", "combined_costs",
1513
                     "adjustment_cost", "intensity_cost", "duration_cost",
1514
                     # Invalid types
1515
                     "input_port_variables", "results", "simulation_results",
1516
                     "monitor_for_control", "state_feature_values", "simulation_ids",
1517
                     "input_labels_dict", "output_labels_dict", "num_estimates",
1518
                     "modulated_mechanisms", "grid", "control_signal_params",
1519
                     "activation_derivative_fct", "input_specification",
1520
                     "state_feature_specs",
1521
                     # Reference to other components
1522
                     "objective_mechanism", "agent_rep", "projections",
1523
                     "outcome_input_ports", "state_input_ports",
1524
                     # autodiff specific types
1525
                     "pytorch_representation", "optimizer", "synch_projection_matrices_with_torch",
1526
                     # duplicate
1527
                     "allocation_samples", "control_allocation_search_space",
1528
                     # not used in computation
1529
                     "auto", "hetero", "cost", "costs",
1530
                     "control_signal", "competition",
1531
                     "has_recurrent_input_port", "enable_learning",
1532
                     "enable_output_type_conversion", "changes_shape",
1533
                     "output_type", "range", "internal_only",
1534
                     "require_projection_in_composition", "default_input",
1535
                     "shadow_inputs", "compute_reconfiguration_cost",
1536
                     "reconfiguration_cost", "net_outcome", "outcome",
1537
                     "enabled_cost_functions", "control_signal_costs",
1538
                     "default_allocation", "same_seed_for_all_allocations",
1539
                     "search_statefulness", "initial_seed", "combine",
1540
                     "random_variables", "smoothing_factor", "per_item",
1541
                     "key_size", "val_size", "max_entries", "random_draw",
1542
                     "randomization_dimension", "save_values", "save_samples",
1543
                     "max_iterations", "duplicate_keys",
1544
                     "search_termination_function", "state_feature_function",
1545
                     "search_function", "weight", "exponent", "gating_signal_params",
1546
                     "retain_old_simulation_data",
1547
                     # "input_size", "hidden_size", "output_size", "bias", "gru_mech",
1548
                     # not used in compiled learning
1549
                     "learning_results", "learning_signal", "learning_signals",
1550
                     "error_matrix", "error_signal", "activation_input",
1551
                     "activation_output", "error_sources", "covariates_sources",
1552
                     "target", "sample", "learning_function",
1553
                     "minibatch_size", "optimizations_per_minibatch", "device",
1554
                     "retain_torch_trained_outputs", "retain_torch_targets", "retain_torch_losses"
1555
                     "torch_trained_outputs", "torch_targets", "torch_losses",
1556
                     # "input_weights_learning_rate", "hidden_weights_learning_rate", "output_weights_learning_rate",
1557
                     # "input_biases_learning_rate", "hidden_biases_learning_rate", "output_biases_learning_rate",
1558
                     # should be added to relevant _gen_llvm_function... when aug:
1559
                     # SoftMax:
1560
                     'mask_threshold', 'adapt_scale', 'adapt_base', 'adapt_entropy_weighting',
1561
                     # LCAMechanism
1562
                     "mask"
1563
                     }
1564

1565
        # OneHot:
1566
        # * runtime abs_val and indicator are only used in deterministic mode.
1567
        # * random_state and seed are only used in RANDOM tie resolution.
1568
        if getattr(self, "mode", None) != DETERMINISTIC:
1✔
1569
            blacklist.update(['abs_val', 'indicator'])
1✔
1570
        elif getattr(self, "tie", None) != RANDOM:
1✔
1571
            blacklist.add("seed")
1✔
1572

1573
        # Mechanism's need few extra entries:
1574
        # * matrix -- is never used directly, and is flatened below
1575
        # * integration_rate -- shape mismatch with param port input
1576
        # * initializer -- only present on DDM and never used
1577
        # * search_space -- duplicated between OCM and its function
1578
        if hasattr(self, 'ports'):
1✔
1579
            blacklist.update(["matrix", "integration_rate", "initializer", "search_space"])
1✔
1580
        else:
1581
            # Execute until finished is only used by mechanisms
1582
            blacklist.update(["execute_until_finished", "max_executions_before_finished"])
1✔
1583

1584
            # "has_initializers" is only used by RTM
1585
            blacklist.add('has_initializers')
1✔
1586

1587
        # Drop combination function params from RTM if not needed
1588
        if getattr(self.parameters, 'has_recurrent_input_port', False):
1✔
1589
            blacklist.add('combination_function')
1✔
1590

1591
        # Drop integrator function if integrator_mode is not enabled
1592
        if not getattr(self, 'integrator_mode', False):
1✔
1593
            blacklist.add('integrator_function')
1✔
1594

1595
        # Drop unused cost functions
1596
        cost_functions = getattr(self, 'enabled_cost_functions', None)
1✔
1597
        if cost_functions is not None:
1✔
1598
            if cost_functions.INTENSITY not in cost_functions:
1✔
1599
                blacklist.add('intensity_cost_fct')
1✔
1600
            if cost_functions.ADJUSTMENT not in cost_functions:
1✔
1601
                blacklist.add('adjustment_cost_fct')
1✔
1602
            if cost_functions.DURATION not in cost_functions:
1✔
1603
                blacklist.add('duration_cost_fct')
1✔
1604

1605
        # Matrices of learnable projections are stateful
1606
        if getattr(self, 'owner', None) and getattr(self.owner, 'learnable', False):
1✔
1607
            blacklist.add('matrix')
1✔
1608

1609
        def _is_compilation_param(p):
1✔
1610
            return p.name not in blacklist and self._is_compilable_param(p)
1✔
1611

1612
        return filter(_is_compilation_param, self.parameters)
1✔
1613

1614
    def _get_param_ids(self):
1✔
1615
        return [p.name for p in self._get_compilation_params()]
1✔
1616

1617
    @functools.cached_property
1✔
1618
    def llvm_param_ids(self):
1✔
1619
        return self._get_param_ids()
1✔
1620

1621
    def _is_param_modulated(self, p):
1✔
1622
        try:
1✔
1623
            if p in self.owner.parameter_ports:
1✔
1624
                return True
1✔
1625
        except AttributeError:
1✔
1626
            pass
1✔
1627
        try:
1✔
1628
            if p in self.parameter_ports:
1✔
1629
                return True
1✔
1630
        except AttributeError:
1✔
1631
            pass
1✔
1632
        try:
1✔
1633
            modulated_params = (
1✔
1634
                getattr(self.parameters, p.sender.modulation).source
1635
                for p in self.owner.mod_afferents)
1636
            if p in modulated_params:
1✔
1637
                return True
1✔
1638
        except AttributeError:
1✔
1639
            pass
1✔
1640

1641
        return False
1✔
1642

1643
    def _get_param_initializer(self, context):
1✔
1644
        def _convert(x):
1✔
1645
            if isinstance(x, Enum):
1✔
1646
                return x.value
1✔
1647
            elif isinstance(x, SampleIterator):
1✔
1648
                if isinstance(x.generator, list):
1✔
1649
                    return tuple(v for v in x.generator)
1✔
1650
                else:
1651
                    return (x.start, x.step, x.num)
1✔
1652
            elif isinstance(x, Component):
1✔
1653
                return x._get_param_initializer(context)
1✔
1654

1655
            try:
1✔
1656
                # This can't use tupleize and needs to recurse to handle
1657
                # 'search_space' list of SampleIterators
1658
                return tuple(_convert(i) for i in x)
1✔
1659
            except TypeError:
1✔
1660
                return x if x is not None else tuple()
1✔
1661

1662
        def _get_values(p):
1✔
1663
            param = p.get(context)
1✔
1664
            # Modulated parameters change shape to array
1665
            if np.ndim(param) == 0 and self._is_param_modulated(p):
1✔
1666
                return (param,)
1✔
1667
            elif p.name == 'num_trials_per_estimate': # Should always be int
1✔
1668
                return 0 if param is None else int(param)
1✔
1669
            elif p.name == 'matrix': # Flatten matrix
1✔
1670
                return tuple(np.asarray(param, dtype=float).ravel())
1✔
1671
            return _convert(param)
1✔
1672

1673
        return tuple(map(_get_values, self._get_compilation_params()))
1✔
1674

1675
    def _gen_llvm_function_reset(self, ctx, builder, *_, tags):
1✔
1676
        assert "reset" in tags
1✔
1677
        return builder
1✔
1678

1679
    def _gen_llvm_function(self, *, ctx:pnlvm.LLVMBuilderContext,
1✔
1680
                                    extra_args=[], tags:frozenset):
1681
        args = [ctx.get_param_struct_type(self).as_pointer(),
1✔
1682
                ctx.get_state_struct_type(self).as_pointer(),
1683
                ctx.get_input_struct_type(self).as_pointer(),
1684
                ctx.get_output_struct_type(self).as_pointer()]
1685
        builder = ctx.create_llvm_function(args + extra_args, self, tags=tags)
1✔
1686

1687
        params, state, arg_in, arg_out = builder.function.args[:len(args)]
1✔
1688
        if len(extra_args) == 0:
1✔
1689
            for p in params, state, arg_in, arg_out:
1✔
1690
                p.attributes.add('noalias')
1✔
1691

1692
        if "reset" in tags:
1✔
1693
            builder = self._gen_llvm_function_reset(ctx, builder, params, state,
1✔
1694
                                                    arg_in, arg_out, tags=tags)
1695
        else:
1696
            builder = self._gen_llvm_function_body(ctx, builder, params, state,
1✔
1697
                                                   arg_in, arg_out, tags=tags)
1698
        builder.ret_void()
1✔
1699
        return builder.function
1✔
1700

1701
    # ------------------------------------------------------------------------------------------------------------------
1702
    # Handlers
1703
    # ------------------------------------------------------------------------------------------------------------------
1704

1705
    def _handle_default_variable(self, default_variable=None, input_shapes=None):
1✔
1706
        """
1707
            Finds whether default_variable can be determined using **default_variable** and **input_shapes**
1708
            arguments.
1709

1710
            Returns
1711
            -------
1712
                a default variable if possible
1713
                None otherwise
1714
        """
1715
        default_variable = self._parse_arg_variable(default_variable)
1✔
1716
        default_variable = self._handle_input_shapes(input_shapes, default_variable)
1✔
1717

1718
        if default_variable is None or default_variable is NotImplemented:
1✔
1719
            return None
1✔
1720
        else:
1721
            self._variable_shape_flexibility = self._specified_variable_shape_flexibility
1✔
1722

1723
        return convert_to_np_array(default_variable, dimension=1)
1✔
1724

1725
    def _parse_input_shapes(
1✔
1726
        self, input_shapes: Union[int, Iterable[Union[int, tuple]]]
1727
    ) -> np.ndarray:
1728
        """
1729
        Returns the equivalent 'variable' array specified by **input_shapes**
1730

1731
        Args:
1732
            input_shapes (Union[int, Iterable[Union[int, tuple]]])
1733

1734
        Returns:
1735
            np.ndarray
1736
        """
1737
        def get_input_shapes_elem(s, idx=None):
1✔
1738
            try:
1✔
1739
                return np.zeros(s)
1✔
1740
            except (TypeError, ValueError) as e:
1✔
1741
                if idx is not None:
1!
1742
                    idx_str = f' at index {idx}'
×
1743
                else:
1744
                    idx_str = ''
1✔
1745

1746
                raise ComponentError(
1747
                    f'Invalid input_shapes argument of {self}{idx_str}. input_shapes must be a'
1748
                    ' valid numpy shape or a list of shapes for use with'
1749
                    f' numpy.zeros: {e}'
1750
                ) from e
1751

1752
        if not is_iterable(input_shapes, exclude_str=True):
1✔
1753
            variable_from_input_shapes = np.asarray([get_input_shapes_elem(input_shapes)])
1✔
1754
        else:
1755
            if len(input_shapes) == 0:
1✔
1756
                raise ComponentError(
1757
                    f'Invalid input_shapes argument of {self}. input_shapes must not be an empty list'
1758
                )
1759
            variable_from_input_shapes = []
1✔
1760
            for i, s in enumerate(input_shapes):
1✔
1761
                variable_from_input_shapes.append(get_input_shapes_elem(s, i))
1✔
1762
            variable_from_input_shapes = convert_all_elements_to_np_array(variable_from_input_shapes)
1✔
1763

1764
        return variable_from_input_shapes
1✔
1765

1766
    # ELIMINATE SYSTEM
1767
    # IMPLEMENTATION NOTE: (7/7/17 CW) Due to System and Process being initialized with input_shapes at the moment (which will
1768
    # be removed later), I’m keeping _handle_input_shapes in Component.py. I’ll move the bulk of the function to Mechanism
1769
    # through an override, when Composition is done. For now, only Port.py overwrites _handle_input_shapes().
1770
    def _handle_input_shapes(self, input_shapes, variable):
1✔
1771
        """If variable is None, _handle_input_shapes tries to infer variable based on the **input_shapes** argument to the
1772
            __init__() function. If input_shapes is None (usually in the case of
1773
            Projections/Functions), then this function passes without
1774
            doing anything. If both input_shapes and variable are not None, a
1775
            ComponentError is thrown if they are not compatible.
1776
        """
1777
        if input_shapes is not None:
1✔
1778
            self._variable_shape_flexibility = self._specified_variable_shape_flexibility
1✔
1779
            # region Fill in and infer variable and input_shapes if they aren't specified in args
1780
            # if variable is None and input_shapes is None:
1781
            #     variable = self.class_defaults.variable
1782
            # 6/30/17 now handled in the individual subclasses' __init__() methods because each subclass has different
1783
            # expected behavior when variable is None and input_shapes is None.
1784

1785
            # implementation note: for good coding practices, perhaps add setting to enable easy change of the default
1786
            # value of variable (though it's an unlikely use case), which is an array of zeros at the moment
1787

1788
            def conflict_error(reason=None):
1✔
1789
                if reason is not None:
1!
1790
                    reason_str = f': {reason}'
1✔
1791
                else:
1792
                    reason_str = ''
×
1793

1794
                return ComponentError(
1✔
1795
                    f'input_shapes and default_variable arguments of {self} conflict{reason_str}'
1796
                )
1797

1798
            variable_from_input_shapes = self._parse_input_shapes(input_shapes)
1✔
1799

1800
            if variable is None:
1✔
1801
                return variable_from_input_shapes
1✔
1802

1803
            if is_iterable(input_shapes, exclude_str=True):
1✔
1804
                assert len(input_shapes) == len(variable_from_input_shapes)
1✔
1805

1806
                if variable.ndim == 0:
1!
1807
                    raise conflict_error(
×
1808
                        'input_shapes gives a list of items but default_variable is 0d'
1809
                    )
1810
                elif len(input_shapes) != len(variable):
1✔
1811
                    raise conflict_error(
1✔
1812
                        f'len(input_shapes) is {len(input_shapes)};'
1813
                        f' len(default_variable) is {len(variable)}'
1814
                    )
1815
                else:
1816
                    for i in range(len(input_shapes)):
1✔
1817
                        if variable_from_input_shapes[i].shape != variable[i].shape:
1✔
1818
                            raise conflict_error(
1✔
1819
                                f'input_shapes[{i}].shape: {variable_from_input_shapes[i].shape};'
1820
                                f' default_variable[{i}].shape: {variable[i].shape}'
1821
                            )
1822
            else:
1823
                if variable_from_input_shapes.shape != variable.shape:
1✔
1824
                    raise conflict_error(
1✔
1825
                        f'input_shapes.shape: {variable_from_input_shapes.shape};'
1826
                        f' default_variable.shape: {variable.shape}'
1827
                    )
1828

1829
        # if variable_from_input_shapes is created an error has not been thrown
1830
        # so far, variable is equal
1831
        return variable
1✔
1832

1833
    def _get_allowed_arguments(self) -> set:
1✔
1834
        """
1835
        Returns a set of argument names that can be passed into
1836
        __init__, directly or through params dictionaries
1837

1838
        Includes:
1839
            - all Parameter constructor_argument names
1840
            - all Parameter names except for those that have a
1841
              constructor_argument
1842
            - all ParameterAlias names
1843
            - all other explicitly specified named arguments in __init__
1844
        """
1845
        allowed_kwargs = self.standard_constructor_args.union(
1✔
1846
            get_all_explicit_arguments(self.__class__, '__init__')
1847
        )
1848
        for p in self.parameters:
1✔
1849
            # restrict to constructor argument, if both are desired, use alias
1850
            if p.constructor_argument is not None and p.constructor_argument != p.name:
1✔
1851
                allowed_kwargs.add(p.constructor_argument)
1✔
1852
                try:
1✔
1853
                    allowed_kwargs.remove(p.name)
1✔
1854
                except KeyError:
1✔
1855
                    pass
1✔
1856
            else:
1857
                allowed_kwargs.add(p.name)
1✔
1858

1859
            if p.aliases is not None:
1✔
1860
                if isinstance(p.aliases, str):
1!
1861
                    allowed_kwargs.add(p.aliases)
×
1862
                else:
1863
                    allowed_kwargs = allowed_kwargs.union(p.aliases)
1✔
1864

1865
        return allowed_kwargs
1✔
1866

1867
    def _get_illegal_arguments(self, **kwargs) -> set:
1✔
1868
        allowed_kwargs = self._get_allowed_arguments()
1✔
1869
        return {
1✔
1870
            k for k in kwargs
1871
            if k not in allowed_kwargs and k in self._user_specified_args
1872
        }
1873

1874
    # breaking self convention here because when storing the args,
1875
    # "self" is often among them. To avoid needing to preprocess to
1876
    # avoid argument duplication, use "self_" in this method signature
1877
    def _store_deferred_init_args(self_, **kwargs):
1✔
1878
        self = self_
1✔
1879

1880
        try:
1✔
1881
            del kwargs['self']
1✔
1882
        except KeyError:
×
1883
            pass
×
1884

1885
        # add unspecified kwargs
1886
        kwargs_names = [
1✔
1887
            k
1888
            for k, v in inspect.signature(self.__init__).parameters.items()
1889
            if v.kind is inspect.Parameter.VAR_KEYWORD
1890
        ]
1891

1892
        self._init_args = {
1✔
1893
            k: v
1894
            for k, v in kwargs.items()
1895
            if (
1896
                k in get_all_explicit_arguments(self.__class__, '__init__')
1897
                or k in kwargs_names
1898
            )
1899
        }
1900
        try:
1✔
1901
            self._init_args.update(self._init_args['kwargs'])
1✔
1902
            del self._init_args['kwargs']
1✔
1903
        except KeyError:
×
1904
            pass
×
1905

1906
    def _deferred_init(self, **kwargs):
1✔
1907
        """Use in subclasses that require deferred initialization
1908
        """
1909
        if self.initialization_status == ContextFlags.DEFERRED_INIT:
1!
1910

1911
            # Flag that object is now being initialized
1912
            #       (usually in _instantiate_function)
1913
            self.initialization_status = ContextFlags.INITIALIZING
1✔
1914

1915
            self._init_args.update(kwargs)
1✔
1916

1917
            # Complete initialization
1918
            super(self.__class__,self).__init__(**self._init_args)
1✔
1919

1920
            # If name was assigned, "[DEFERRED INITIALIZATION]" was appended to it, so remove it
1921
            if DEFERRED_INITIALIZATION in self.name:
1!
1922
                self.name = self.name.replace("[" + DEFERRED_INITIALIZATION + "]", "")
×
1923
            # Otherwise, allow class to replace std default name with class-specific one if it has a method for doing so
1924
            else:
1925
                self._assign_default_name()
1✔
1926

1927
            del self._init_args
1✔
1928

1929
    def _assign_deferred_init_name(self, name):
1✔
1930

1931
        name = "{} [{}]".format(name, DEFERRED_INITIALIZATION) if name \
1✔
1932
          else "{}{} {}".format(_get_auto_name_prefix(), DEFERRED_INITIALIZATION, self.__class__.__name__)
1933

1934
        # Register with ProjectionRegistry or create one
1935
        register_category(entry=self,
1✔
1936
                          base_class=Component,
1937
                          name=name,
1938
                          registry=DeferredInitRegistry,
1939
                          )
1940

1941
    def _assign_default_name(self, **kwargs):
1✔
1942
        return
1✔
1943

1944
    def _set_parameter_value(self, param, val, context=None):
1✔
1945
        param = getattr(self.parameters, param)
1✔
1946
        param._set(val, context)
1✔
1947
        if hasattr(self, "parameter_ports"):
1✔
1948
            if param in self.parameter_ports:
1✔
1949
                new_port_value = self.parameter_ports[param].execute(context=context)
1✔
1950
                self.parameter_ports[param].parameters.value._set(new_port_value, context)
1✔
1951
        elif hasattr(self, "owner"):
1!
1952
            if hasattr(self.owner, "parameter_ports"):
1✔
1953
                # skip Components, assume they are to be run to provide the
1954
                # value instead of given as a variable to a parameter port
1955
                if param in self.owner.parameter_ports:
1✔
1956
                    try:
1✔
1957
                        if any([isinstance(v, Component) for v in val]):
1!
1958
                            return
×
1959
                    except TypeError:
1✔
1960
                        if isinstance(val, Component):
1!
1961
                            return
×
1962

1963
                    new_port_value = self.owner.parameter_ports[param].execute(context=context)
1✔
1964
                    self.owner.parameter_ports[param].parameters.value._set(new_port_value, context)
1✔
1965

1966
    def _check_args(self, variable=None, params=None, context=None, target_set=None):
1✔
1967
        """validate variable and params, instantiate variable (if necessary) and assign any runtime params.
1968

1969
        Called by functions to validate variable and params
1970
        Validation can be suppressed by turning parameter_validation attribute off
1971
        target_set is a params dictionary to which params should be assigned;
1972

1973
        Does the following:
1974
        - instantiate variable (if missing or callable)
1975
        - validate variable if PARAM_VALIDATION is set
1976
        - resets leftover runtime params back to original values (only if execute method was called directly)
1977
        - sets runtime params
1978
        - validate params if PARAM_VALIDATION is set
1979

1980
        :param variable: (anything but a dict) - variable to validate
1981
        :param params: (dict) - params to validate
1982
        :target_set: (dict) - set to which params should be assigned
1983
        :return:
1984
        """
1985
        # VARIABLE ------------------------------------------------------------
1986

1987
        # If function is called without any arguments, get default for variable
1988
        if variable is None:
1✔
1989
            variable = self.defaults.variable
1✔
1990

1991
            variable = copy_parameter_value(variable)
1✔
1992

1993
        # If the variable is a function, call it
1994
        if callable(variable):
1!
1995
            variable = variable()
×
1996

1997
        # Validate variable if parameter_validation is set and the function was called with a variable
1998
        if self.prefs.paramValidationPref and variable is not None:
1!
1999
            variable = self._validate_variable(variable, context=context)
1✔
2000

2001
        # PARAMS ------------------------------------------------------------
2002

2003
        # If params have been passed, treat as runtime params
2004
        self._validate_and_assign_runtime_params(params, context)
1✔
2005

2006
        self.parameters.variable._set(variable, context=context)
1✔
2007
        return variable
1✔
2008

2009
    def _validate_and_assign_runtime_params(self, runtime_params, context):
1✔
2010
        """Validate runtime_params, cache for reset, and assign values
2011

2012
        Check that all params belong either to Component or its function (raise error if any are found that don't)
2013
        Cache params to reset in _runtime_params_reset
2014
        """
2015

2016
        # # MODIFIED 5/8/20 OLD:
2017
        # # reset any runtime params that were leftover from a direct call to .execute (atypical)
2018
        # if context.execution_id in self._runtime_params_reset:
2019
        #     for key in self._runtime_params_reset[context.execution_id]:
2020
        #         self._set_parameter_value(key, self._runtime_params_reset[context.execution_id][key], context)
2021
        # self._runtime_params_reset[context.execution_id] = {}
2022
        # MODIFIED 5/8/20 END
2023

2024
        from psyneulink.core.components.functions.function import is_function_type, FunctionError
1✔
2025
        def generate_error(param_name):
1✔
2026
            owner_name = ""
1✔
2027
            if hasattr(self, OWNER) and self.owner:
1✔
2028
                owner_name = f" of {self.owner.name}"
1✔
2029
                if hasattr(self.owner, OWNER) and self.owner.owner:
1!
2030
                    owner_name = f"{owner_name} of {self.owner.owner.name}"
×
2031
            err_msg=f"Invalid specification in runtime_params arg for {self.name}{owner_name}: '{param_name}'."
1✔
2032
            if is_function_type(self):
1✔
2033
                raise FunctionError(err_msg)
2034
            else:
2035
                raise ComponentError(err_msg)
2036

2037
        if isinstance(runtime_params, dict):
1✔
2038
            runtime_params = copy_parameter_value(runtime_params)
1✔
2039
            for param_name in runtime_params:
1✔
2040
                if not isinstance(param_name, str):
1!
2041
                    generate_error(param_name)
×
2042
                elif param_name in self.parameters:
1✔
2043
                    if param_name in {FUNCTION, INPUT_PORTS, OUTPUT_PORTS}:
1!
2044
                        generate_error(param_name)
×
2045
                    if context.execution_id not in self._runtime_params_reset:
1✔
2046
                        self._runtime_params_reset[context.execution_id] = {}
1✔
2047
                    self._runtime_params_reset[context.execution_id][param_name] = copy_parameter_value(
1✔
2048
                        getattr(self.parameters, param_name)._get(context)
2049
                    )
2050
                    if is_numeric(runtime_params[param_name]):
1✔
2051
                        runtime_value = convert_all_elements_to_np_array(runtime_params[param_name])
1✔
2052
                    else:
2053
                        runtime_value = runtime_params[param_name]
1✔
2054

2055
                    self._set_parameter_value(param_name, runtime_value, context)
1✔
2056
                # Any remaining params should either belong to the Component's function
2057
                #    or, if the Component is a Function, to it or its owner
2058
                elif ( # If Component is not a function, and its function doesn't have the parameter or
1✔
2059
                        (not is_function_type(self) and param_name not in self.function.parameters)
2060
                       # the Component is a standalone function:
2061
                       or (is_function_type(self) and not self.owner)):
2062
                    generate_error(param_name)
1✔
2063

2064
        elif runtime_params:    # not None
1✔
2065
            raise ComponentError(f"Invalid specification of runtime parameters for {self.name}: {runtime_params}.")
2066

2067
    @handle_external_context()
1✔
2068
    def _instantiate_defaults(self,
1✔
2069
                        variable=None,
2070
                        request_set=None,
2071
                        assign_missing=True,
2072
                        target_set=None,
2073
                        default_set=None,
2074
                        context=None
2075
                        ):
2076
        """Validate variable and/or param defaults in requested set and assign values to params in target set
2077

2078
          Variable can be any type other than a dictionary (reserved for use as params)
2079
          request_set must contain a dict of params to be assigned to target_set
2080
          If assign_missing option is set, then any params defined for the class
2081
              but not included in the requested set are assigned values from the default_set;
2082
              if request_set is None, then all values in the target_set are assigned from the default_set
2083
          Class defaults can not be passed as target_set
2084
              IMPLEMENTATION NOTE:  for now, treating class defaults as hard coded;
2085
                                    could be changed in the future simply by commenting out code below
2086

2087
          If not context:  instantiates function and any ports specified in request set
2088
                           (if they have changed from the previous value(s))
2089

2090
        :param variable: (anything but a dict (variable) - value to assign as defaults.variable
2091
        :param request_set: (dict) - params to be assigned
2092
        :param assign_missing: (bool) - controls whether missing params are set to default_set values (default: False)
2093
        :param target_set: (dict) - param set to which assignments should be made
2094
        :param default_set: (dict) - values used for params missing from request_set (only if assign_missing is True)
2095
        :return:
2096
        """
2097

2098
        # Make sure all args are legal
2099
        if variable is not None:
1!
2100
            if isinstance(variable,dict):
1✔
2101
                raise ComponentError("Dictionary passed as variable; probably trying to use param set as 1st argument")
2102
        if request_set:
1✔
2103
            if not isinstance(request_set, dict):
1✔
2104
                raise ComponentError("requested parameter set must be a dictionary")
2105
        if target_set:
1✔
2106
            if not isinstance(target_set, dict):
1✔
2107
                raise ComponentError("target parameter set must be a dictionary")
2108
        if default_set:
1✔
2109
            if not isinstance(default_set, dict):
1✔
2110
                raise ComponentError("default parameter set must be a dictionary")
2111

2112

2113
        # FIX: 6/3/19 [JDC] SHOULD DEAL WITH THIS AND SHAPE BELOW
2114
        # # GET VARIABLE FROM PARAM DICT IF SPECIFIED
2115
        # #    (give precedence to that over variable arg specification)
2116
        # if VARIABLE in request_set and request_set[VARIABLE] is not None:
2117
        #     variable = request_set[VARIABLE]
2118

2119
        # ASSIGN SHAPE TO VARIABLE if specified
2120

2121
        if hasattr(self, 'shape') and self.shape is not None:
1!
2122
            # IMPLEMENTATION NOTE 6/23/17 (CW): this test is currently unused by all components. To confirm this, we
2123
            # may add an exception here (raise ComponentError("Oops this is actually used")), then run all tests.
2124
            # thus, we should consider deleting this validation
2125

2126
            # Both variable and shape are specified
2127
            if variable is not None:
×
2128
                # If they conflict, raise exception, otherwise use variable (it specifies both shape and content)
2129
                if self.shape != np.array(variable).shape:
×
2130
                    raise ComponentError(
2131
                        "The shape arg of {} ({}) conflicts with the shape of its variable arg ({})".
2132
                        format(self.name, self.shape, np.array(variable).shape))
2133
            # Variable is not specified, so set to array of zeros with specified shape
2134
            else:
2135
                variable = np.zeros(self.shape)
×
2136

2137
        # VALIDATE VARIABLE
2138

2139
        if context.source is not ContextFlags.COMMAND_LINE:
1!
2140
            # if variable has been passed then validate and, if OK, assign as self.defaults.variable
2141
            variable = self._validate_variable(variable, context=context)
1✔
2142

2143
        # If no params were passed, then done
2144
        if request_set is None and target_set is None and default_set is None:
1✔
2145
            return
1✔
2146

2147
        # VALIDATE PARAMS
2148

2149
        # if request_set has been passed or created then validate and, if OK, assign params to target_set
2150
        if request_set:
1!
2151
            try:
1✔
2152
                self._validate_params(variable=variable,
1✔
2153
                                      request_set=request_set,
2154
                                      target_set=target_set,
2155
                                      context=context)
2156
            # variable not implemented by Mechanism subclass, so validate without it
2157
            except TypeError:
1✔
2158
                self._validate_params(request_set=request_set,
1✔
2159
                                      target_set=target_set,
2160
                                      context=context)
2161

2162
    def _validate_arguments(self, parameter_values):
1✔
2163
        """
2164
        Raises errors for illegal specifications of arguments to
2165
        __init__:
2166
            - original Parameter name when Parameter has a
2167
              constructor_argument
2168
            - arguments that don't correspond to Parameters or other
2169
              arguments to __init__
2170
            - non-equal values of a Parameter and a corresponding
2171
              ParameterAlias
2172
        """
2173
        def create_illegal_argument_error(illegal_arg_strs):
1✔
2174
            plural = 's' if len(illegal_arg_strs) > 1 else ''
1✔
2175
            base_err = f"Illegal argument{plural} in constructor (type: {type(self).__name__}):"
1✔
2176
            return ComponentError('\n\t'.join([base_err] + illegal_arg_strs), component=self)
1✔
2177

2178
        def alias_conflicts(alias, passed_name):
1✔
2179
            # some aliases share name with constructor_argument
2180
            if alias.name == passed_name:
1✔
2181
                return False
1✔
2182

2183
            try:
1✔
2184
                a_val = parameter_values[alias.name]
1✔
2185
                s_val = parameter_values[passed_name]
1✔
2186
            except KeyError:
1✔
2187
                return False
1✔
2188

2189
            if safe_equals(a_val, s_val):
1✔
2190
                return False
1✔
2191

2192
            # both specified, not equal -> conflict
2193
            alias_specified = (
1✔
2194
                alias.name in self._user_specified_args
2195
                and (a_val is not None or alias.specify_none)
2196
            )
2197
            source_specified = (
1✔
2198
                passed_name in self._user_specified_args
2199
                and (s_val is not None or alias.source.specify_none)
2200
            )
2201
            return alias_specified and source_specified
1✔
2202

2203
        illegal_passed_args = self._get_illegal_arguments(**parameter_values)
1✔
2204

2205
        conflicting_aliases = []
1✔
2206
        unused_constructor_args = {}
1✔
2207
        deprecated_args = {}
1✔
2208
        for p in self.parameters:
1✔
2209
            if p.name in illegal_passed_args:
1✔
2210
                # p must have a constructor_argument, because otherwise
2211
                # p.name would not be in illegal_passed_args
2212
                assert p.constructor_argument is not None
1✔
2213
                unused_constructor_args[p.name] = p.constructor_argument
1✔
2214

2215
            if isinstance(p, ParameterAlias):
1✔
2216
                if p.source.constructor_argument is None:
1✔
2217
                    passed_name = p.source.name
1✔
2218
                else:
2219
                    passed_name = p.source.constructor_argument
1✔
2220

2221
                if alias_conflicts(p, passed_name):
1✔
2222
                    conflicting_aliases.append((p.source.name, passed_name, p.name))
1✔
2223

2224
        for arg in illegal_passed_args:
1✔
2225
            try:
1✔
2226
                deprecated_args[arg] = self.deprecated_constructor_args[arg]
1✔
2227
            except KeyError:
1✔
2228
                continue
1✔
2229

2230
        # raise constructor arg errors
2231
        if len(unused_constructor_args) > 0:
1✔
2232
            raise create_illegal_argument_error([
1✔
2233
                f"'{arg}': must use '{constr_arg}' instead"
2234
                for arg, constr_arg in unused_constructor_args.items()
2235
            ])
2236

2237
        # raise deprecated argument errors
2238
        if len(deprecated_args) > 0:
1✔
2239
            raise create_illegal_argument_error([
1✔
2240
                f"'{arg}' is deprecated. Use '{new_arg}' instead"
2241
                for arg, new_arg in deprecated_args.items()
2242
            ])
2243

2244
        # raise generic illegal argument error
2245
        unknown_args = illegal_passed_args.difference(unused_constructor_args)
1✔
2246
        if len(unknown_args) > 0:
1✔
2247
            raise create_illegal_argument_error([f"'{a}'" for a in unknown_args])
1✔
2248

2249
        # raise alias conflict error
2250
        # can standardize these with above error, but leaving for now for consistency
2251
        if len(conflicting_aliases) > 0:
1✔
2252
            source, passed, alias = conflicting_aliases[0]
1✔
2253
            constr_arg_str = f' ({source})' if source != passed else ''
1✔
2254
            raise ComponentError(
2255
                f"Multiple values ({alias}: {parameter_values[alias]}"
2256
                f"\t{passed}: {parameter_values[passed]}) "
2257
                f"assigned to identical Parameters. {alias} is an alias "
2258
                f"of {passed}{constr_arg_str}",
2259
                component=self,
2260
            )
2261

2262
    def _parse_arguments(
1✔
2263
        self, default_variable, param_defaults, input_shapes, function, function_params, kwargs
2264
    ):
2265
        if function_params is None:
1✔
2266
            function_params = {}
1✔
2267

2268
        # allow override of standard arguments with arguments specified
2269
        # in params (here, param_defaults) argument
2270
        # (if there are duplicates, later lines override previous)
2271
        # add named arguments here so that values from param_defaults
2272
        # can override them.
2273
        parameter_values = {
1✔
2274
            **{
2275
                'function': function,
2276
                'input_shapes': input_shapes,
2277
                'default_variable': default_variable,
2278
                'function_params': function_params
2279
            },
2280
            **kwargs,
2281
            **(param_defaults if param_defaults is not None else {}),
2282
        }
2283
        function_params = parameter_values['function_params']
1✔
2284

2285
        # if function is a standard python function or string, assume
2286
        # any unknown kwargs are for the corresponding UDF.
2287
        # Validation will happen there.
2288
        if isinstance(function, (types.FunctionType, str)):
1✔
2289
            function_params = {
1✔
2290
                **kwargs,
2291
                **function_params
2292
            }
2293
        else:
2294
            self._validate_arguments(parameter_values)
1✔
2295

2296
        # self.parameters here still references <class>.parameters, but
2297
        # only unchanging attributes are needed here
2298
        for p in self.parameters:
1✔
2299
            if p.constructor_argument is not None and p.constructor_argument != p.name:
1✔
2300
                try:
1✔
2301
                    parameter_values[p.name] = parameter_values[p.constructor_argument]
1✔
2302
                except KeyError:
1✔
2303
                    pass
1✔
2304
                else:
2305
                    # the value itself isn't used elsewhere
2306
                    self._user_specified_args[p.name] = f'FROM_{p.constructor_argument}'
1✔
2307

2308
            if isinstance(p, ParameterAlias):
1✔
2309
                if p.source.name not in self._user_specified_args:
1✔
2310
                    # if alias conflicts with source, error thrown in _validate_arguments
2311
                    try:
1✔
2312
                        parameter_values[p.source.name] = parameter_values[p.name]
1✔
2313
                    except KeyError:
1✔
2314
                        pass
1✔
2315
                    else:
2316
                        self._user_specified_args[p.source.name] = f'FROM_{p.name}'
1✔
2317

2318
        return parameter_values, function_params
1✔
2319

2320
    def _initialize_parameters(self, context=None, **param_defaults):
1✔
2321
        """
2322
        Args:
2323
            **param_defaults: maps Parameter names to their default
2324
            values. Sets instance-level Parameters dynamically for any
2325
            name that maps to a Parameter object.
2326
        """
2327
        from psyneulink.core.components.shellclasses import (
1✔
2328
            Composition_Base, Function, Mechanism, Port, Process_Base,
2329
            Projection, System_Base
2330
        )
2331

2332
        # excludes Function
2333
        shared_types = (
1✔
2334
            Mechanism,
2335
            Port,
2336
            Projection,
2337
            System_Base,
2338
            Process_Base,
2339
            Composition_Base,
2340
            ComponentsMeta,
2341
            types.MethodType,
2342
            types.ModuleType,
2343
            functools.partial,
2344
        )
2345
        alias_names = {p.name for p in self.class_parameters if isinstance(p, ParameterAlias)}
1✔
2346

2347
        self.parameters = self.Parameters(owner=self, parent=self.class_parameters)
1✔
2348

2349
        # assign defaults based on pass in params and class defaults
2350
        defaults = {
1✔
2351
            k: v for (k, v) in self.class_defaults.values(show_all=True).items()
2352
            if k not in alias_names
2353
        }
2354

2355
        if param_defaults is not None:
1!
2356
            for name, value in copy.copy(param_defaults).items():
1✔
2357
                if name in alias_names:
1✔
2358
                    continue
1✔
2359

2360
                if isinstance(value, Parameter):
1✔
2361
                    setattr(self.parameters, name, value)
1✔
2362
                    try:
1✔
2363
                        value = copy.copy(value.default_value)
1✔
2364
                    except TypeError:
1✔
2365
                        value = value.default_value
1✔
2366
                    param_defaults[name] = value
1✔
2367

2368
                if name in self.parameters._params:
1!
2369
                    parameter_obj = getattr(self.parameters, name)
1✔
2370
                else:
2371
                    # name in param_defaults does not correspond to a Parameter
2372
                    continue
×
2373

2374
                if (
1✔
2375
                    name not in self._user_specified_args
2376
                    and parameter_obj.constructor_argument not in self._user_specified_args
2377
                ):
2378
                    continue
1✔
2379
                elif value is not None or parameter_obj.specify_none:
1✔
2380
                    parameter_obj._user_specified = True
1✔
2381

2382
                if parameter_obj.structural:
1✔
2383
                    parameter_obj.spec = value
1✔
2384

2385
                if parameter_obj.modulable:
1✔
2386
                    # later, validate this
2387
                    modulable_param_parser = self.parameters._get_parse_method('modulable')
1✔
2388
                    if modulable_param_parser is not None:
1!
2389
                        parsed = modulable_param_parser(name, value)
1✔
2390

2391
                        if parsed is not value:
1✔
2392
                            # we have a modulable param spec
2393
                            parameter_obj.spec = value
1✔
2394
                            value = parsed
1✔
2395
                            param_defaults[name] = parsed
1✔
2396

2397
                if value is not None or parameter_obj.specify_none:
1✔
2398
                    defaults[name] = value
1✔
2399

2400
        self.defaults = Defaults(owner=self)
1✔
2401
        for k in sorted(defaults, key=self.parameters._dependency_order_key(names=True)):
1✔
2402
            if defaults[k] is not None:
1✔
2403
                defaults[k] = copy_parameter_value(
1✔
2404
                    defaults[k],
2405
                    shared_types=shared_types
2406
                )
2407
            parameter_obj = getattr(self.parameters, k)
1✔
2408
            parameter_obj._set_default_value(defaults[k], check_scalar=parameter_obj._user_specified)
1✔
2409

2410
        for p in filter(lambda x: not isinstance(x, (ParameterAlias, SharedParameter)), self.parameters._in_dependency_order):
1✔
2411
            # copy spec so it is not overwritten later
2412
            # TODO: check if this is necessary
2413
            p.spec = copy_parameter_value(p.spec, shared_types=shared_types)
1✔
2414

2415
            # set default to None context to ensure it exists
2416
            if (
1✔
2417
                p._get(context, fallback_value=None) is None and p.getter is None
2418
                or context.execution_id not in p.values
2419
            ):
2420
                if p._user_specified:
1✔
2421
                    val = param_defaults[p.name]
1✔
2422

2423
                    # ideally, this would include deepcopying any
2424
                    # Function objects with a non-None owner in val.
2425
                    # Avoiding universal deep copy for iterables
2426
                    # containing Functions here ensures that a list (ex.
2427
                    # noise) containing other objects and a Function
2428
                    # will use the actual Function passed in and not a
2429
                    # copy. Not copying - as was done prior to this
2430
                    # comment - should only be a problem if internal
2431
                    # code passes such an object that is also used
2432
                    # elsewhere
2433
                    if isinstance(val, Function):
1✔
2434
                        if val.owner is not None:
1✔
2435
                            val = copy.deepcopy(val)
1✔
2436
                    elif not contains_type(val, Function):
1✔
2437
                        val = copy_parameter_value(val, shared_types=shared_types)
1✔
2438
                else:
2439
                    val = copy_parameter_value(
1✔
2440
                        p.default_value,
2441
                        shared_types=shared_types
2442
                    )
2443

2444
                if isinstance(val, Function):
1✔
2445
                    val.owner = self
1✔
2446

2447
                val = p._parse(val)
1✔
2448
                p._validate(val)
1✔
2449
                p._set(val, context=context, skip_history=True, override=True)
1✔
2450

2451
            if isinstance(p.default_value, Function):
1✔
2452
                p.default_value.owner = p
1✔
2453

2454
        for p in self.parameters:
1✔
2455
            if p.stateful:
1✔
2456
                setattr(self, _get_parametervalue_attr(p), ParameterValue(self, p))
1✔
2457

2458
    def _get_parsed_variable(self, parameter, variable=NotImplemented, context=None):
1✔
2459
        if variable is NotImplemented:
1✔
2460
            variable = copy.deepcopy(self.defaults.variable)
1✔
2461

2462
        try:
1✔
2463
            parameter = getattr(self.parameters, parameter)
1✔
2464
        except TypeError:
1✔
2465
            pass
1✔
2466

2467
        try:
1✔
2468
            parse_variable_method = getattr(
1✔
2469
                self,
2470
                f'_parse_{parameter.name}_variable'
2471
            )
2472
            return copy.deepcopy(
1✔
2473
                call_with_pruned_args(parse_variable_method, variable, context=context)
2474
            )
2475
        except AttributeError:
1✔
2476
            # no parsing method, assume same shape as owner
2477
            return variable
1✔
2478

2479
    def _instantiate_parameter_classes(self, context=None):
1✔
2480
        """
2481
            An optional method that will take any Parameter values in
2482
            **context** that are classes/types, and instantiate them.
2483
        """
2484
        from psyneulink.core.components.shellclasses import Function
1✔
2485

2486
        # (this originally occurred in _validate_params)
2487
        for p in self.parameters._in_dependency_order:
1✔
2488
            if p.getter is None:
1✔
2489
                val = p._get(context)
1✔
2490
                if (
1✔
2491
                    p.name != FUNCTION
2492
                    and is_instance_or_subclass(val, Function)
2493
                    and not p.reference
2494
                    and not isinstance(p, SharedParameter)
2495
                ):
2496
                    function_default_variable = self._get_parsed_variable(p, context=context)
1✔
2497

2498
                    if (
1✔
2499
                        inspect.isclass(val)
2500
                        and issubclass(val, Function)
2501
                    ):
2502
                        # instantiate class val with all relevant shared parameters
2503
                        # some shared parameters may not be arguments (e.g.
2504
                        # transfer_fct additive_param when function is Identity)
2505
                        # NOTE: this may cause an issue if there is an
2506
                        # incompatibility between a shared parameter and
2507
                        # the default variable, by forcing variable to
2508
                        # be _user_specified, where instead the variable
2509
                        # would be coerced to match
2510
                        val = call_with_pruned_args(
1✔
2511
                            val,
2512
                            default_variable=function_default_variable,
2513
                            **self.initial_shared_parameters[p.name]
2514
                        )
2515

2516
                        val.owner = self
1✔
2517
                        p._set(val, context)
1✔
2518

2519
                        for sub_param_name in itertools.chain(self.initial_shared_parameters[p.name], ['variable']):
1✔
2520
                            try:
1✔
2521
                                sub_param = getattr(val.parameters, sub_param_name)
1✔
2522
                            except AttributeError:
×
2523
                                # TransferWithCosts has SharedParameters
2524
                                # referencing transfer_fct's
2525
                                # additive_param or
2526
                                # multiplicative_param, but Identity
2527
                                # does not have them
2528
                                continue
×
2529

2530
                            try:
1✔
2531
                                orig_param_name = [x.name for x in self.parameters if isinstance(x, SharedParameter) and x.source is sub_param][0]
1✔
2532
                            except IndexError:
1✔
2533
                                orig_param_name = sub_param_name
1✔
2534
                            sub_param._user_specified = getattr(self.parameters, orig_param_name)._user_specified
1✔
2535

2536
                    elif isinstance(val, Function):
1!
2537
                        incompatible = False
1✔
2538
                        if function_default_variable.shape != val.defaults.variable.shape:
1✔
2539
                            incompatible = True
1✔
2540
                            if val._variable_shape_flexibility is DefaultsFlexibility.INCREASE_DIMENSION:
1✔
2541
                                increased_dim = np.asarray([val.defaults.variable])
1✔
2542

2543
                                if increased_dim.shape == function_default_variable.shape:
1✔
2544
                                    function_default_variable = increased_dim
1✔
2545
                                    incompatible = False
1✔
2546
                            elif val._variable_shape_flexibility is DefaultsFlexibility.FLEXIBLE:
1!
2547
                                incompatible = False
1✔
2548

2549
                        if incompatible:
1✔
2550
                            def _create_justified_line(k, v, error_line_len=110):
1✔
2551
                                return f'{k}: {v.rjust(error_line_len - len(k))}'
1✔
2552

2553
                            raise ParameterError(
2554
                                f'Variable shape incompatibility between {self} and its {p.name} Parameter'
2555
                                + _create_justified_line(
2556
                                    f'\n{self}.variable',
2557
                                    f'{function_default_variable}    (numpy.array shape: {np.asarray(function_default_variable).shape})'
2558
                                )
2559
                                + _create_justified_line(
2560
                                    f'\n{self}.{p.name}.variable',
2561
                                    f'{val.defaults.variable}    (numpy.array shape: {np.asarray(val.defaults.variable).shape})'
2562
                                )
2563
                            )
2564
                        val._update_default_variable(
1✔
2565
                            function_default_variable,
2566
                            context
2567
                        )
2568

2569
                        if isinstance(p.default_value, Function):
1!
2570
                            p.default_value._update_default_variable(
1✔
2571
                                function_default_variable,
2572
                                context
2573
                            )
2574

2575
        self._override_unspecified_shared_parameters(context)
1✔
2576

2577
    def _override_unspecified_shared_parameters(self, context):
1✔
2578
        for param in self.parameters._in_dependency_order:
1✔
2579
            if (
1✔
2580
                isinstance(param, SharedParameter)
2581
                and not isinstance(param.source, ParameterAlias)
2582
            ):
2583
                try:
1✔
2584
                    obj = getattr(self.parameters, param.attribute_name)
1✔
2585
                    shared_objs = [obj.default_value, obj._get(context)]
1✔
2586
                except AttributeError:
1✔
2587
                    obj = getattr(self, param.attribute_name)
1✔
2588
                    shared_objs = [obj]
1✔
2589

2590
                for c in shared_objs:
1✔
2591
                    if isinstance(c, Component):
1✔
2592
                        try:
1✔
2593
                            shared_obj_param = getattr(c.parameters, param.shared_parameter_name)
1✔
2594
                        except AttributeError:
1✔
2595
                            continue
1✔
2596

2597
                        if not shared_obj_param._user_specified:
1✔
2598
                            if (
1✔
2599
                                param.primary
2600
                                and param.default_value is not None
2601
                            ):
2602
                                shared_obj_param.default_value = copy.deepcopy(param.default_value)
1✔
2603
                                shared_obj_param._set(copy.deepcopy(param.default_value), context)
1✔
2604
                                shared_obj_param._user_specified = param._user_specified
1✔
2605
                        elif (
1✔
2606
                            param._user_specified
2607
                            and not safe_equals(param.default_value, shared_obj_param.default_value)
2608
                            # only show warning one time, for the non-default value if possible
2609
                            and c is shared_objs[-1]
2610
                        ):
2611
                            try:
1✔
2612
                                isp_arg = self.initial_shared_parameters[param.attribute_name][param.shared_parameter_name]
1✔
2613
                                # TODO: handle passed component but copied?
2614
                                throw_warning = (
1✔
2615
                                    # arg passed directly into shared_obj, no parsing
2616
                                    not safe_equals(shared_obj_param._get(context), isp_arg)
2617
                                    # arg passed but was parsed
2618
                                    and not safe_equals(shared_obj_param.spec, isp_arg)
2619
                                )
2620
                            except KeyError:
×
2621
                                throw_warning = True
×
2622

2623
                            if throw_warning:
1✔
2624
                                warnings.warn(
1✔
2625
                                    f'Specification of the "{param.name}" parameter ({param.default_value})'
2626
                                    f' for {self} conflicts with specification of its shared parameter'
2627
                                    f' "{shared_obj_param.name}" ({shared_obj_param.default_value}) for its'
2628
                                    f' {param.attribute_name} ({param.source._owner._owner}). The value'
2629
                                    f' specified on {param.source._owner._owner} will be used.'
2630
                                )
2631

2632

2633
    @handle_external_context()
1✔
2634
    def reset_params(self, mode=ResetMode.INSTANCE_TO_CLASS, context=None):
1✔
2635
        """Reset current and/or instance defaults
2636

2637
        If called with:
2638
            - CURRENT_TO_INSTANCE_DEFAULTS all current param settings are set to instance defaults
2639
            - INSTANCE_TO_CLASS all instance defaults are set to class defaults
2640
            - ALL_TO_CLASS_DEFAULTS all current and instance param settings are set to class defaults
2641

2642
        :param mode: (ResetMode) - determines which params are reset
2643
        :return none:
2644
        """
2645

2646
        if not isinstance(mode, ResetMode):
×
2647
            warnings.warn("No ResetMode specified for reset_params; CURRENT_TO_INSTANCE_DEFAULTS will be used")
×
2648

2649
        for param in self.parameters:
×
2650
            if mode == ResetMode.CURRENT_TO_INSTANCE_DEFAULTS:
×
2651
                param._set(
×
2652
                    copy_parameter_value(param.default_value),
2653
                    context=context,
2654
                    skip_history=True,
2655
                    skip_log=True,
2656
                )
2657
            elif mode == ResetMode.INSTANCE_TO_CLASS:
×
2658
                param.reset()
×
2659
            elif mode == ResetMode.ALL_TO_CLASS_DEFAULTS:
×
2660
                param.reset()
×
2661
                param._set(
×
2662
                    copy_parameter_value(param.default_value),
2663
                    context=context,
2664
                    skip_history=True,
2665
                    skip_log=True,
2666
                )
2667

2668
    def _initialize_from_context(self, context, base_context=Context(execution_id=None), override=True, visited=None):
1✔
2669
        if context.execution_id is base_context.execution_id:
1✔
2670
            return
1✔
2671

2672
        if visited is None:
1✔
2673
            visited = set()
1✔
2674

2675
        for comp in self._dependent_components:
1✔
2676
            if comp not in visited:
1✔
2677
                visited.add(comp)
1✔
2678
                comp._initialize_from_context(context, base_context, override, visited=visited)
1✔
2679

2680
        non_alias_params = [p for p in self.stateful_parameters if not isinstance(p, (ParameterAlias, SharedParameter))]
1✔
2681
        for param in non_alias_params:
1✔
2682
            if param.setter is None:
1✔
2683
                param._initialize_from_context(context, base_context, override)
1✔
2684

2685
        # attempt to initialize any params with setters (some params with setters may depend on the
2686
        # initialization of other params)
2687
        # this pushes the problem down one level so that if there are two such that they depend on each other,
2688
        # it will still fail. in this case, it is best to resolve the problem in the setter with a default
2689
        # initialization value
2690
        for param in non_alias_params:
1✔
2691
            if param.setter is not None:
1✔
2692
                param._initialize_from_context(context, base_context, override)
1✔
2693

2694
    def _delete_contexts(self, *contexts, check_simulation_storage=False, visited=None):
1✔
2695
        if visited is None:
1!
2696
            visited = set()
×
2697

2698
        for comp in self._dependent_components:
1✔
2699
            if comp not in visited:
1✔
2700
                visited.add(comp)
1✔
2701
                comp._delete_contexts(*contexts, check_simulation_storage=check_simulation_storage, visited=visited)
1✔
2702

2703
        for param in self.stateful_parameters:
1✔
2704
            if not check_simulation_storage or not param.retain_old_simulation_data:
1!
2705
                for context in contexts:
1✔
2706
                    param.delete(context)
1✔
2707

2708
    def _set_all_parameter_properties_recursively(self, visited=None, **kwargs):
1✔
2709
        if visited is None:
1✔
2710
            visited = set()
1✔
2711

2712
        # sets a property of all parameters for this component and all its dependent components
2713
        # used currently for disabling history, but setting logging could use this
2714
        for param_name in self.parameters.names():
1✔
2715
            parameter = getattr(self.parameters, param_name)
1✔
2716
            for (k, v) in kwargs.items():
1✔
2717
                try:
1✔
2718
                    setattr(parameter, k, v)
1✔
2719
                except ParameterError as e:
×
2720
                    logger.warning(str(e) + ' Parameter has not been modified.')
×
2721

2722
        for comp in self._dependent_components:
1✔
2723
            if comp not in visited:
1!
2724
                visited.add(comp)
1✔
2725
                comp._set_all_parameter_properties_recursively(
1✔
2726
                    visited=visited,
2727
                    **kwargs
2728
                )
2729

2730
    def _set_multiple_parameter_values(self, context, **kwargs):
1✔
2731
        """
2732
            Unnecessary, but can simplify multiple parameter assignments at once
2733
            For every kwarg k, v pair, will attempt to set self.parameters.<k> to v for context
2734
        """
2735
        for (k, v) in kwargs.items():
1✔
2736
            getattr(self.parameters, k)._set(v, context)
1✔
2737

2738
    # ------------------------------------------------------------------------------------------------------------------
2739
    # Parsing methods
2740
    # ------------------------------------------------------------------------------------------------------------------
2741
    # ---------------------------------------------------------
2742
    # Argument parsers
2743
    # ---------------------------------------------------------
2744

2745
    def _parse_arg_generic(self, arg_val):
1✔
2746
        """
2747
            Argument parser for any argument that does not have a specialized parser
2748
        """
2749
        return arg_val
×
2750

2751
    def _parse_arg_variable(self, variable):
1✔
2752
        """
2753
            Transforms **variable** into a form that Components expect. Used to allow
2754
            users to pass input in convenient forms, like a single float when a list
2755
            for input ports is expected
2756

2757
            Returns
2758
            -------
2759
            The transformed **input**
2760
        """
2761
        if variable is None:
1✔
2762
            return variable
1✔
2763

2764
        if not isinstance(variable, (list, np.ndarray)):
1✔
2765
            variable = np.atleast_1d(variable)
1✔
2766

2767
        return convert_all_elements_to_np_array(variable)
1✔
2768

2769
    # ---------------------------------------------------------
2770
    # Misc parsers
2771
    # ---------------------------------------------------------
2772

2773
    def _parse_function_variable(self, variable, context=None):
1✔
2774
        """
2775
            Parses the **variable** passed in to a Component into a function_variable that can be used with the
2776
            Function associated with this Component
2777
        """
2778
        return variable
1✔
2779

2780
    # ------------------------------------------------------------------------------------------------------------------
2781
    # Validation methods
2782
    # ------------------------------------------------------------------------------------------------------------------
2783

2784
    def _validate(self, context=None):
1✔
2785
        """
2786
            Eventually should contain all validation methods, occurs at end of Component.__init__
2787
        """
2788
        # 4/18/18 kmantel: below is a draft of what such a method should look like
2789
        # it's beyond the scope of the current changes however
2790

2791
        # # currently allows chance to validate anything in constructor defaults
2792
        # # when fleshed out, this should go over the new Parameters structure
2793
        # for param, _ in self.get_param_class_defaults().items():
2794
        #     try:
2795
        #         # automatically call methods of the form _validate_<param name> with the attribute
2796
        #         # as single argument. Sticking to this format can allow condensed and modular validation
2797
        #         getattr(self, '_validate_' + param)(getattr(self, param))
2798
        #     except AttributeError:
2799
        #         pass
2800
        self._validate_value()
1✔
2801

2802
    def _validate_variable(self, variable, context=None):
1✔
2803
        """Validate variable and return validated variable
2804

2805
        Convert self.class_defaults.variable specification and variable (if specified) to list of 1D np.ndarrays:
2806

2807
        VARIABLE SPECIFICATION:                                        ENCODING:
2808
        Simple value variable:                                         0 -> [array([0])]
2809
        Single state array (vector) variable:                         [0, 1] -> [array([0, 1])]
2810
        Multiple port variables, each with a single value variable:  [[0], [0]] -> [array[0], array[0]]
2811

2812
        Perform top-level type validation of variable against the self.class_defaults.variable;
2813
            if the type is OK, the value is returned (which should be used by the function)
2814
        This can be overridden by a subclass to perform more detailed checking (e.g., range, recursive, etc.)
2815
        It is called only if the parameter_validation attribute is `True` (which it is by default)
2816

2817
        IMPLEMENTATION NOTES:
2818
           * future versions should add hierarchical/recursive content (e.g., range) checking
2819
           * add request/target pattern?? (as per _validate_params) and return validated variable?
2820

2821
        :param variable: (anything other than a dictionary) - variable to be validated:
2822
        :param context: (str)
2823
        :return variable: validated variable
2824
        """
2825

2826
        if inspect.isclass(variable):
1✔
2827
            raise ComponentError(f"Assignment of class ({variable.__name__}) "
2828
                                 f"as a variable (for {self.name}) is not allowed.")
2829

2830
        # If variable is not specified, then:
2831
        #    - assign to (??now np-converted version of) self.class_defaults.variable
2832
        #    - mark as not having been specified
2833
        #    - return
2834
        if variable is None:
1!
2835
            try:
×
2836
                variable = self.defaults.variable
×
2837
            except AttributeError:
×
2838
                variable = self.class_defaults.variable
×
2839
            return copy_parameter_value(variable)
×
2840

2841
        # Otherwise, do some checking on variable before converting to np.ndarray
2842

2843
        # If variable is callable (function or object reference), call it and assign return to value to variable
2844
        # Note: check for list is necessary since function references must be passed wrapped in a list so that they are
2845
        #       not called before being passed
2846
        if isinstance(variable, list) and callable(variable[0]):
1!
2847
            variable = variable[0]()
×
2848
        # NOTE (7/24/17 CW): the above two lines of code can be commented out without causing any current tests to fail
2849
        # So we should either write tests for this piece of code, or remove it.
2850
        # Convert variable to np.ndarray
2851
        # Note: this insures that variable will be AT LEAST 1D;  however, can also be higher:
2852
        #       e.g., given a list specification of [[0],[0]], it will return a 2D np.array
2853
        variable = convert_to_np_array(variable, 1)
1✔
2854

2855
        return variable
1✔
2856

2857
    def _validate_params(self, request_set, target_set=None, context=None):
1✔
2858
        """Validate params and assign validated values to targets,
2859

2860
        This performs top-level type validation of params
2861

2862
        This can be overridden by a subclass to perform more detailed checking (e.g., range, recursive, etc.)
2863
        It is called only if the parameter_validation attribute is `True` (which it is by default)
2864

2865
        IMPLEMENTATION NOTES:
2866
           * future versions should add recursive and content (e.g., range) checking
2867
           * should method return validated param set?
2868

2869
        :param dict (request_set) - set of params to be validated:
2870
        :param dict (target_set) - repository of params that have been validated:
2871
        :return none:
2872
        """
2873

2874
        for param_name, param_value in request_set.items():
1✔
2875
            # setattr(self, "_"+param_name, param_value)
2876

2877
            # Check that param is in self.defaults (if not, it is assumed to be invalid for this object)
2878
            if param_name not in self.defaults.names(show_all=True):
1!
2879
                continue
×
2880

2881
            # The default value of the param is None: suppress type checking
2882
            # IMPLEMENTATION NOTE: this can be used for params with multiple possible types,
2883
            #                      until type lists are implemented (see below)
2884
            if getattr(self.defaults, param_name) is None or getattr(self.defaults, param_name) is NotImplemented:
1✔
2885
                if self.prefs.verbosePref:
1!
2886
                    warnings.warn(f"{param_name} is specified as None for {self.name} which suppresses type checking.")
×
2887
                if target_set is not None:
1!
2888
                    target_set[param_name] = param_value
1✔
2889
                continue
1✔
2890

2891
            # If the value in self.defaults is a type, check if param value is an instance of it
2892
            if inspect.isclass(getattr(self.defaults, param_name)):
1✔
2893
                if isinstance(param_value, getattr(self.defaults, param_name)):
1!
2894
                    target_set[param_name] = param_value
×
2895
                    continue
×
2896
                # If the value is a Function class, allow any instance of Function class
2897
                from psyneulink.core.components.functions.function import Function_Base
1✔
2898
                if issubclass(getattr(self.defaults, param_name), Function_Base):
1✔
2899
                    # if isinstance(param_value, (function_type, Function_Base)):  <- would allow function of any kind
2900
                    if isinstance(param_value, Function_Base):
1!
2901
                        target_set[param_name] = param_value
×
2902
                        continue
×
2903

2904
            # If the value in self.defaults is an object, check if param value is the corresponding class
2905
            # This occurs if the item specified by the param has not yet been implemented (e.g., a function)
2906
            if inspect.isclass(param_value):
1✔
2907
                if isinstance(getattr(self.defaults, param_name), param_value):
1!
2908
                    continue
×
2909

2910
            # If the value is a projection, projection class, or a keyword for one, for anything other than
2911
            #    the FUNCTION param (which is not allowed to be specified as a projection)
2912
            #    then simply assign value (implication of not specifying it explicitly);
2913
            #    this also allows it to pass the test below and function execution to occur for initialization;
2914
            from psyneulink.core.components.shellclasses import Projection
1✔
2915
            if (((isinstance(param_value, str) and
1✔
2916
                          param_value in {CONTROL_PROJECTION, LEARNING_PROJECTION, LEARNING}) or
2917
                isinstance(param_value, Projection) or  # These should be just ControlProjection or LearningProjection
2918
                inspect.isclass(param_value) and issubclass(param_value,(Projection)))
2919
                and not param_name == FUNCTION):
2920
                param_value = getattr(self.defaults, param_name)
1✔
2921

2922
            # If self is a Function and param is a class ref for function, instantiate it as the function
2923
            from psyneulink.core.components.functions.function import Function_Base
1✔
2924
            if (isinstance(self, Function_Base) and
1✔
2925
                    inspect.isclass(param_value) and
2926
                    inspect.isclass(getattr(self.defaults, param_name))
2927
                    and issubclass(param_value, getattr(self.defaults, param_name))):
2928
                # Assign instance to target and move on
2929
                #  (compatiblity check no longer needed and can't handle function)
2930
                target_set[param_name] = param_value()
1✔
2931
                continue
1✔
2932

2933
            # Check if param value is of same type as one with the same name in defaults
2934
            #    don't worry about length
2935
            if iscompatible(param_value, getattr(self.defaults, param_name), **{kwCompatibilityLength:0}):
1✔
2936
                if isinstance(param_value, dict):
1✔
2937

2938
                    # If assign_default_FUNCTION_PARAMS is False, it means that function's class is
2939
                    #     compatible but different from the one in defaults;
2940
                    #     therefore, FUNCTION_PARAMS will not match defaults;
2941
                    #     instead, check that functionParams are compatible with the function's default params
2942
                    if param_name == FUNCTION_PARAMS:
1!
2943
                        if not self.assign_default_FUNCTION_PARAMS:
×
2944
                            # Get function:
2945
                            try:
×
2946
                                function = request_set[FUNCTION]
×
2947
                            except KeyError:
×
2948
                                # If no function is specified, self.assign_default_FUNCTION_PARAMS should be True
2949
                                # (see _instantiate_defaults above)
2950
                                raise ComponentError("PROGRAM ERROR: No function params for {} so should be able to "
2951
                                                    "validate {}".format(self.name, FUNCTION_PARAMS))
2952
                            else:
2953
                                for entry_name, entry_value in param_value.items():
×
2954
                                    try:
×
2955
                                        getattr(function.defaults, entry_name)
×
2956
                                    except KeyError:
×
2957
                                        raise ComponentError("{0} is not a valid entry in {1} for {2} ".
2958
                                                            format(entry_name, param_name, self.name))
2959
                                    # add [entry_name] entry to [param_name] dict
2960
                                    else:
2961
                                        try:
×
2962
                                            target_set[param_name][entry_name] = entry_value
×
2963
                                        # [param_name] dict not yet created, so create it
2964
                                        except KeyError:
×
2965
                                            target_set[param_name] = {}
×
2966
                                            target_set[param_name][entry_name] = entry_value
×
2967
                                        # target_set None
2968
                                        except TypeError:
×
2969
                                            pass
×
2970
                        else:
2971
                            # if param_name != FUNCTION_PARAMS:
2972
                            #     assert True
2973
                            for entry_name, entry_value in param_value.items():
×
2974
                                # Make sure [entry_name] is in self.defaults
2975
                                try:
×
2976
                                    getattr(self.defaults, param_name)[entry_name]
×
2977
                                except KeyError:
×
2978
                                    raise ComponentError("{0} is not a valid entry in {1} for {2} ".
2979
                                                        format(entry_name, param_name, self.name))
2980
                                # TBI: (see above)
2981
                                # if not iscompatible(entry_value,
2982
                                #                     getattr(self.defaults, param_name)[entry_name],
2983
                                #                     **{kwCompatibilityLength:0}):
2984
                                #     raise ComponentError("{0} ({1}) in {2} of {3} must be a {4}".
2985
                                #         format(entry_name, entry_value, param_name, self.name,
2986
                                #                type(getattr(self.defaults, param_name)[entry_name]).__name__))
2987
                                else:
2988
                                    # add [entry_name] entry to [param_name] dict
2989
                                    try:
×
2990
                                        target_set[param_name][entry_name] = entry_value
×
2991
                                    # [param_name] dict not yet created, so create it
2992
                                    except KeyError:
×
2993
                                        target_set[param_name] = {}
×
2994
                                        target_set[param_name][entry_name] = entry_value
×
2995
                                    # target_set None
2996
                                    except TypeError:
×
2997
                                        pass
×
2998

2999
                elif target_set is not None:
1!
3000
                    # Copy any iterables so that deletions can be made to assignments belonging to the instance
3001
                    if not isinstance(param_value, Iterable) or isinstance(param_value, str):
1✔
3002
                        target_set[param_name] = param_value
1✔
3003
                    else:
3004
                        # hack for validation until it's streamlined
3005
                        # parse modulable parameter values
3006
                        if getattr(self.parameters, param_name).modulable:
1✔
3007
                            try:
1✔
3008
                                target_set[param_name] = param_value.copy()
1✔
3009
                            except AttributeError:
1✔
3010
                                modulable_param_parser = self.parameters._get_parse_method('modulable')
1✔
3011
                                if modulable_param_parser is not None:
1!
3012
                                    param_value = modulable_param_parser(param_name, param_value)
1✔
3013
                                    target_set[param_name] = param_value
1✔
3014
                                else:
3015
                                    target_set[param_name] = param_value.copy()
×
3016

3017
                        else:
3018
                            target_set[param_name] = copy.copy(param_value)
1✔
3019

3020
            # If param is a function_type (or it has a function attribute that is one), allow any other function_type
3021
            elif callable(param_value):
1!
3022
                target_set[param_name] = param_value
×
3023
            elif hasattr(param_value, FUNCTION) and callable(param_value.function):
1!
3024
                target_set[param_name] = param_value
×
3025

3026
            # It has already passed as the name of a valid param, so let it pass;
3027
            #    value should be validated in subclass _validate_params override
3028
            elif isinstance(param_name, str):
1!
3029
                # FIX: 10/3/17 - THIS IS A HACK;  IT SHOULD BE HANDLED EITHER
3030
                # FIX:           MORE GENERICALLY OR LOCALLY (E.G., IN OVERRIDE OF _validate_params)
3031
                if param_name == 'matrix':
1!
3032
                    if is_matrix(getattr(self.defaults, param_name)):
×
3033
                        # FIX:  ?? ASSIGN VALUE HERE, OR SIMPLY ALLOW AND ASSUME IT WILL BE PARSED ELSEWHERE
3034
                        # param_value = getattr(self.defaults, param_name)
3035
                        # target_set[param_name] = param_value
3036
                        target_set[param_name] = param_value
×
3037
                    else:
3038
                        raise ComponentError("Value of {} param for {} ({}) must be a valid matrix specification".
3039
                                             format(param_name, self.name, param_value))
3040
                target_set[param_name] = param_value
1✔
3041

3042
            # Parameter is not a valid type
3043
            else:
3044
                if type(getattr(self.defaults, param_name)) is type:
×
3045
                    type_name = 'the name of a subclass of ' + getattr(self.defaults, param_name).__base__.__name__
×
3046
                raise ComponentError("Value of {} param for {} ({}) is not compatible with {}".
3047
                                    format(param_name, self.name, param_value, type_name))
3048

3049
    def _get_param_value_for_modulatory_spec(self, param_name, param_value):
1✔
3050
        from psyneulink.core.globals.keywords import MODULATORY_SPEC_KEYWORDS
×
3051
        if isinstance(param_value, str):
×
3052
            param_spec = param_value
×
3053
        elif isinstance(param_value, Component):
×
3054
            param_spec = param_value.__class__.__name__
×
3055
        elif isinstance(param_value, type):
×
3056
            param_spec = param_value.__name__
×
3057
        else:
3058
            raise ComponentError("PROGRAM ERROR: got {} instead of string, Component, or Class".format(param_value))
3059

3060
        if param_spec not in MODULATORY_SPEC_KEYWORDS:
×
3061
            return (param_value)
×
3062

3063
        try:
×
3064
            param_default_value = getattr(self.defaults, param_name)
×
3065
            # Only assign default value if it is not None
3066
            if param_default_value is not None:
×
3067
                return param_default_value
×
3068
            else:
3069
                return param_value
×
3070
        except:
×
3071
            raise ComponentError("PROGRAM ERROR: Could not get default value for {} of {} (to replace spec as {})".
3072
                                 format(param_name, self.name, param_value))
3073

3074
    def _get_param_value_from_tuple(self, param_spec):
1✔
3075
        """Returns param value (first item) of a (value, projection) tuple;
3076
        """
3077
        from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism import ModulatoryMechanism_Base
×
3078
        from psyneulink.core.components.projections.modulatory.modulatoryprojection import ModulatoryProjection_Base
×
3079
        from psyneulink.core.components.ports.modulatorysignals.modulatorysignal import ModulatorySignal
×
3080

3081
        ALLOWABLE_TUPLE_SPEC_KEYWORDS = MODULATORY_SPEC_KEYWORDS
×
3082
        ALLOWABLE_TUPLE_SPEC_CLASSES = (ModulatoryProjection_Base, ModulatorySignal, ModulatoryMechanism_Base)
×
3083

3084
        # If the 2nd item is a CONTROL or LEARNING SPEC, return the first item as the value
3085
        if (isinstance(param_spec, tuple) and len(param_spec) == 2 and
×
3086
                not isinstance(param_spec[1], (dict, list, np.ndarray)) and
3087
                (param_spec[1] in ALLOWABLE_TUPLE_SPEC_KEYWORDS or
3088
                 isinstance(param_spec[1], ALLOWABLE_TUPLE_SPEC_CLASSES) or
3089
                 (inspect.isclass(param_spec[1]) and issubclass(param_spec[1], ALLOWABLE_TUPLE_SPEC_CLASSES)))
3090
            ):
3091
            value = param_spec[0]
×
3092

3093
        # Otherwise, just return the tuple
3094
        else:
3095
            value = param_spec
×
3096

3097
        return value
×
3098

3099
    def _validate_function(self, function, context=None):
1✔
3100
        """Check that either params[FUNCTION] and/or self.execute are implemented
3101

3102
        # FROM _validate_params:
3103
        # It also checks FUNCTION:
3104
        #     if it is specified and is a type reference (rather than an instance),
3105
        #     it instantiates the reference (using FUNCTION_PARAMS if present)
3106
        #     and puts a reference to the instance in target_set[FUNCTION]
3107
        #
3108
        This checks for an execute method in function
3109
        If a specification is not present or valid:
3110
            - it checks self.execute and, if present, kwExecute is assigned to it
3111
            - if self.execute is not present or valid, an exception is raised
3112
        When completed, there is guaranteed to be a valid method in self.function and/or self.execute;
3113
            otherwise, an exception is raised
3114

3115
        Notes:
3116
            * no new assignments (to FUNCTION or self.execute) are made here, except:
3117
            * if FUNCTION is missing, it is assigned to self.execute (if it is present)
3118
            * no instantiations are done here;
3119
            * any assignment(s) to and/or instantiation(s) of self.execute and/or params[FUNCTION]
3120
                is/are carried out in _instantiate_function
3121

3122
        :return:
3123
        """
3124

3125
        from psyneulink.core.components.shellclasses import Function
×
3126

3127
        # FUNCTION is not specified, so try to assign self.function to it
3128
        if function is None:
×
3129
            try:
×
3130
                function = self.function
×
3131
            except AttributeError:
×
3132
                # self.function is also missing, so raise exception
3133
                raise ComponentError("{0} must either implement a function method or specify one in {0}.Parameters".
3134
                                    format(self.__class__.__name__))
3135

3136
        # self.function is None
3137
        # IMPLEMENTATION NOTE:  This is a coding error;  self.function should NEVER be assigned None
3138
        if function is None:
×
3139
            raise ComponentError("PROGRAM ERROR: either {0} must be specified or {1}.function must be implemented for {2}".
3140
                  format(FUNCTION,self.__class__.__name__, self.name))
3141
        # self.function is OK, so return
3142
        elif (
×
3143
            isinstance(function, types.FunctionType)
3144
            or isinstance(function, types.MethodType)
3145
            or is_instance_or_subclass(function, Function)
3146
        ):
3147
            self.parameters.function._set(function, context)
×
3148
            return
×
3149
        # self.function is NOT OK, so raise exception
3150
        else:
3151
            raise ComponentError("{0} not specified and {1}.function is not a Function object or class "
3152
                                "or valid method in {2}".
3153
                                format(FUNCTION, self.__class__.__name__, self.name))
3154

3155
    def _validate_value(self):
1✔
3156
        pass
1✔
3157

3158
    def _instantiate_attributes_before_function(self, function=None, context=None):
1✔
3159
        pass
1✔
3160

3161
    def _instantiate_function(self, function, function_params=None, context=None):
1✔
3162
        """Instantiate function defined in <subclass>.function or <subclass>.function
3163

3164
        Instantiate params[FUNCTION] if present, and assign it to self.function
3165

3166
        If params[FUNCTION] is present and valid,
3167
            it is assigned as the function's execute method, overriding any direct implementation of self.function
3168

3169
        If FUNCTION IS in params:
3170
            - if it is a Function object, it is simply assigned to self.function;
3171
            - if it is a Function class reference:
3172
                it is instantiated using self.defaults.variable and, if present, params[FUNCTION_PARAMS]
3173
        If FUNCTION IS NOT in params:
3174
            - if self.function IS implemented, it is assigned to params[FUNCTION]
3175
            - if self.function IS NOT implemented: program error (should have been caught in _validate_function)
3176
        Upon successful completion:
3177
            - self._function === self.function
3178
            - self.execute should always return the output of self.function in the first item of its output array;
3179
                 this is done by Function.execute;  any subclass override should do the same, so that...
3180
            - value is value[0] returned by self.execute
3181

3182
        """
3183
        from psyneulink.core.components.functions.userdefinedfunction import UserDefinedFunction
1✔
3184
        from psyneulink.core.components.shellclasses import Function
1✔
3185

3186
        function_variable = copy.deepcopy(
1✔
3187
            self._parse_function_variable(
3188
                self.defaults.variable,
3189
                context
3190
            )
3191
        )
3192

3193
        # Specification is the function of a (non-instantiated?) Function class
3194
        # KDM 11/12/18: parse an instance of a Function's .function method to itself
3195
        # (not sure how worth it this is, but it existed in Scripts/Examples/Reinforcement-Learning REV)
3196
        # purposely not attempting to parse a class Function.function
3197
        # JDC 3/6/19:  ?what about parameter ports for its parameters (see python function problem below)?
3198
        if isinstance(function, types.MethodType):
1!
3199
            try:
×
3200
                if isinstance(function.__self__, Function):
×
3201
                    function = function.__self__
×
3202
            except AttributeError:
×
3203
                pass
×
3204

3205
        # Specification is a standard python function, so wrap as a UserDefnedFunction
3206
        # Note:  parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
3207
        if isinstance(function, (types.FunctionType, str)):
1✔
3208
            function = UserDefinedFunction(
1✔
3209
                default_variable=function_variable,
3210
                custom_function=function,
3211
                owner=self,
3212
                context=context,
3213
                **function_params,
3214
            )
3215

3216
        # Specification is an already implemented Function
3217
        elif isinstance(function, Function):
1✔
3218
            if function_variable.shape != function.defaults.variable.shape:
1✔
3219
                owner_str = ''
1✔
3220
                if hasattr(self, 'owner') and self.owner is not None:
1✔
3221
                    owner_str = f' of {repr(self.owner.name)}'
1✔
3222
                if function._variable_shape_flexibility is DefaultsFlexibility.RIGID:
1✔
3223
                    raise ComponentError(f'Variable format ({function.defaults.variable}) of {function.name} '
3224
                                         f'is not compatible with the variable format ({function_variable}) '
3225
                                         f'of {repr(self.name)}{owner_str} to which it is being assigned.')
3226
                                         # f'Make sure variable for {function.name} is 2d.')
3227
                elif function._variable_shape_flexibility is DefaultsFlexibility.INCREASE_DIMENSION:
1✔
3228
                    function_increased_dim = np.asarray([function.defaults.variable])
1✔
3229
                    if function_variable.shape != function_increased_dim.shape:
1✔
3230
                        raise ComponentError(f'Variable format ({function.defaults.variable}) of {function.name} '
3231
                                             f'is not compatible with the variable format ({function_variable})'
3232
                                             f' of {repr(self.name)}{owner_str} to which it is being assigned.')
3233
                                             # f'Make sure variable for {function.name} is 2d.')
3234

3235
            # class default functions should always be copied, otherwise anything this component
3236
            # does with its function will propagate to anything else that wants to use
3237
            # the default
3238
            if function.owner is self:
1✔
3239
                try:
1✔
3240
                    if function._is_pnl_inherent:
1!
3241
                        # This will most often occur if a Function instance is
3242
                        # provided as a default argument in a constructor. These
3243
                        # should instead be added as default values for the
3244
                        # corresponding Parameter.
3245
                        # Adding the function as a default constructor argument
3246
                        # will lead to incorrect setting of
3247
                        # Parameter._user_specified
3248
                        warnings.warn(
×
3249
                            f'{function} is generated once during import of'
3250
                            ' psyneulink, and is now being reused. Please report'
3251
                            ' this, including the script you were using, to the'
3252
                            ' psyneulink developers at'
3253
                            ' psyneulinkhelp@princeton.edu or'
3254
                            ' https://github.com/PrincetonUniversity/PsyNeuLink/issues'
3255
                        )
3256
                        function = copy.deepcopy(function)
×
3257
                except AttributeError:
1✔
3258
                    pass
1✔
3259
            elif function.owner is not None:
1✔
3260
                function = copy.deepcopy(function)
1✔
3261

3262
            # set owner first because needed for is_initializing calls
3263
            function.owner = self
1✔
3264
            function._update_default_variable(function_variable, context)
1✔
3265

3266
        # Specification is Function class
3267
        # Note:  parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
3268
        elif inspect.isclass(function) and issubclass(function, Function):
1✔
3269
            kwargs_to_instantiate = {}
1✔
3270
            if function_params is not None:
1✔
3271
                kwargs_to_instantiate.update(**function_params)
1✔
3272
                # default_variable should not be in any function_params but sometimes it is
3273
                kwargs_to_remove = ['default_variable']
1✔
3274

3275
                for arg in kwargs_to_remove:
1✔
3276
                    try:
1✔
3277
                        del kwargs_to_instantiate[arg]
1✔
3278
                    except KeyError:
1✔
3279
                        pass
1✔
3280

3281
                try:
1✔
3282
                    kwargs_to_instantiate.update(self.initial_shared_parameters[FUNCTION])
1✔
3283
                except KeyError:
×
3284
                    pass
×
3285

3286
                # matrix is determined from ParameterPort based on string value in function_params
3287
                # update it here if needed
3288
                if MATRIX in kwargs_to_instantiate:
1✔
3289
                    try:
1✔
3290
                        kwargs_to_instantiate[MATRIX] = copy_parameter_value(self.parameter_ports[MATRIX].defaults.value)
1✔
3291
                    except (AttributeError, KeyError, TypeError):
×
3292
                        pass
×
3293

3294
            try:
1✔
3295
                function = function(default_variable=function_variable, owner=self, **kwargs_to_instantiate)
1✔
3296
            except TypeError as e:
1✔
3297
                if 'unexpected keyword argument' in str(e):
1✔
3298
                    raise ComponentError(f'(function): {function} {e}', component=self) from e
3299
                else:
3300
                    raise
1✔
3301

3302
        else:
3303
            raise ComponentError(f'Unsupported function type: {type(function)}, function={function}.')
3304

3305
        self.parameters.function._set(function, context)
1✔
3306

3307
        # KAM added 6/14/18 for functions that do not pass their has_initializers status up to their owner via property
3308
        # FIX: need comprehensive solution for has_initializers; need to determine whether ports affect mechanism's
3309
        # has_initializers status
3310
        if self.function.parameters.has_initializers._get(context):
1✔
3311
            self.parameters.has_initializers._set(True, context)
1✔
3312

3313
        self._parse_param_port_sources()
1✔
3314

3315
    def _instantiate_attributes_after_function(self, context=None):
1✔
3316
        if hasattr(self, "_parameter_ports"):
1✔
3317
            shared_params = [p for p in self.parameters if isinstance(p, (ParameterAlias, SharedParameter))]
1✔
3318
            sources = [p.source for p in shared_params]
1✔
3319

3320
            for param_port in self._parameter_ports:
1✔
3321
                property_names = {param_port.name}
1✔
3322
                try:
1✔
3323
                    alias_index = sources.index(param_port.source)
1✔
3324
                    property_names.add(shared_params[alias_index].name)
1✔
3325
                except ValueError:
1✔
3326
                    pass
1✔
3327

3328
                for property_name in property_names:
1✔
3329
                    setattr(self.__class__, "mod_" + property_name, make_property_mod(property_name, param_port.name))
1✔
3330
                    setattr(self.__class__, "get_mod_" + property_name, make_stateful_getter_mod(property_name, param_port.name))
1✔
3331

3332
    def _instantiate_value(self, context=None):
1✔
3333
        #  - call self.execute to get value, since the value of a Component is defined as what is returned by its
3334
        #    execute method, not its function
3335
        default_variable = copy.deepcopy(self.defaults.variable)
1✔
3336
        try:
1✔
3337
            value = self.execute(variable=default_variable, context=context)
1✔
3338
        except TypeError as e:
1✔
3339
            # don't hide other TypeErrors
3340
            if "execute() got an unexpected keyword argument 'variable'" not in str(e):
1✔
3341
                raise
1✔
3342

3343
            try:
1✔
3344
                value = self.execute(input=default_variable, context=context)
1✔
UNCOV
3345
            except TypeError as e:
×
3346
                if "execute() got an unexpected keyword argument 'input'" != str(e):
×
3347
                    raise
×
3348

3349
                value = self.execute(context=context)
×
3350
        if value is None:
1✔
3351
            raise ComponentError(f"PROGRAM ERROR: Execute method for {self.name} must return a value.")
3352

3353
        self.parameters.value._set(value, context=context, skip_history=True)
1✔
3354
        try:
1✔
3355
            # Could be mutable, so assign copy
3356
            self.defaults.value = value.copy()
1✔
3357
        except AttributeError:
1✔
3358
            # Immutable, so just assign value
3359
            self.defaults.value = value
1✔
3360

3361
    def _update_default_variable(self, new_default_variable, context):
1✔
3362
        from psyneulink.core.components.shellclasses import Function
1✔
3363

3364
        new_default_variable = convert_all_elements_to_np_array(new_default_variable)
1✔
3365
        self.defaults.variable = copy.deepcopy(new_default_variable)
1✔
3366

3367
        # exclude value from validation because it isn't updated until
3368
        # _instantiate_value is called
3369
        call_with_pruned_args(
1✔
3370
            self._validate_params,
3371
            variable=new_default_variable,
3372
            request_set={
3373
                k: v.default_value
3374
                for k, v in self.parameters.values(True).items()
3375
                if k not in {'value'} and not isinstance(v, ParameterAlias)
3376
            },
3377
            target_set={},
3378
            context=context
3379
        )
3380
        self._instantiate_value(context)
1✔
3381

3382
        for p in self.parameters._in_dependency_order:
1✔
3383
            try:
1✔
3384
                val = p._get(context)
1✔
3385
            except ParameterInvalidSourceError:
1✔
3386
                continue
1✔
3387

3388
            if (
1✔
3389
                not p.reference
3390
                and isinstance(val, Function)
3391
                and not isinstance(p, SharedParameter)
3392
            ):
3393
                function_default_variable = self._get_parsed_variable(p, context=context)
1✔
3394

3395
                try:
1✔
3396
                    val._update_default_variable(function_default_variable, context)
1✔
3397
                    if isinstance(p.default_value, Component):
1✔
3398
                        p.default_value._update_default_variable(function_default_variable, context)
1✔
3399
                except (AttributeError, TypeError):
×
3400
                    pass
×
3401

3402
                # TODO: is it necessary to call _validate_value here?
3403

3404
    def initialize(self, context=None):
1✔
3405
        raise ComponentError("{} class does not support initialize() method".format(self.__class__.__name__))
3406

3407
    def _check_for_composition(self, context=None):
1✔
3408
        """Allow Component to check whether it or its attributes are suitable for inclusion in a Composition
3409
        Called by Composition.add_node.
3410
        """
3411
        pass
1✔
3412

3413
    @handle_external_context(fallback_most_recent=True)
1✔
3414
    def reset(self, *args, context=None, **kwargs):
1✔
3415
        """
3416
            If the component's execute method involves execution of an `IntegratorFunction` Function, this method
3417
            effectively begins the function's accumulation over again at the specified value, and may update related
3418
            values on the component, depending on the component type.  Otherwise, it simply reassigns the Component's
3419
            value based on its default_variable.
3420
        """
3421
        from psyneulink.core.components.functions.stateful.integratorfunctions import IntegratorFunction
×
3422
        if isinstance(self.function, IntegratorFunction):
×
3423
            new_value = self.function.reset(*args, **kwargs, context=context)
×
3424
            self.parameters.value.set(np.atleast_2d(new_value), context, override=True)
×
3425
        else:
3426
            raise ComponentError(f"Resetting {self.name} is not allowed because this Component is not stateful. "
3427
                                 "(It does not have an accumulator to reset).")
3428

3429
    @handle_external_context()
1✔
3430
    def execute(self, variable=None, context=None, runtime_params=None):
1✔
3431
        """Executes Component's `function <Component_Function>`.  See Component-specific execute method for details.
3432
        """
3433

3434
        if context is None:
1!
3435
            try:
×
3436
                context = self.owner.most_recent_context
×
3437
            except AttributeError:
×
3438
                context = self.most_recent_context
×
3439

3440
        if context.source is ContextFlags.COMMAND_LINE:
1✔
3441
            self._initialize_from_context(context, override=False)
1✔
3442
            if is_numeric(variable):
1✔
3443
                variable = convert_all_elements_to_np_array(variable)
1✔
3444

3445
        value = self._execute(variable=variable, context=context, runtime_params=runtime_params)
1✔
3446
        self.parameters.value._set(value, context=context)
1✔
3447

3448
        return value
1✔
3449

3450
    def _execute(self, variable=None, context=None, runtime_params=None, **kwargs):
1✔
3451
        from psyneulink.core.components.functions.function import Function
1✔
3452

3453
        self.parameters.variable._set(variable, context=context)
1✔
3454

3455
        if self.initialization_status & ~(ContextFlags.VALIDATING | ContextFlags.INITIALIZING):
1✔
3456
            self._increment_execution_count()
1✔
3457

3458
            # Functions don't have Logs or maintain time
3459
            if not isinstance(self, Function):
1✔
3460
                self._update_current_execution_time(context=context)
1✔
3461
                self._increment_num_executions(
1✔
3462
                    context,
3463
                    [TimeScale.TIME_STEP, TimeScale.PASS, TimeScale.TRIAL, TimeScale.RUN, TimeScale.LIFE]
3464
                )
3465

3466
        value = None
1✔
3467

3468
        # GET VALUE if specified in runtime_params
3469
        if runtime_params and VALUE in runtime_params:
1✔
3470
            # Get value and then pop from runtime_param, as no need to restore to previous value
3471
            value = np.atleast_1d(runtime_params.pop(VALUE))
1✔
3472
            # Eliminate any other params (including ones for function),
3473
            #  since they will not be assigned and therefore should not be restored to previous value below
3474
            #  (doing so would restore them to the previous previous value)
3475
            runtime_params = {}
1✔
3476

3477
        # CALL FUNCTION if value is not specified
3478
        if value is None:
1✔
3479
            # IMPLEMENTATION NOTE:  **kwargs is included to accommodate required arguments
3480
            #                     that are specific to particular class of Functions
3481
            #                     (e.g., error_matrix for LearningMechanism and controller for EVCControlMechanism)
3482
            function_variable = self._parse_function_variable(variable, context=context)
1✔
3483
            # IMPLEMENTATION NOTE: Need to pass full runtime_params (and not just function's params) since
3484
            #                      Mechanisms with secondary functions (e.g., IntegratorMechanism) seem them
3485
            value = self.function(variable=function_variable, context=context, params=runtime_params, **kwargs)
1✔
3486
            try:
1✔
3487
                self.function.parameters.value._set(value, context)
1✔
3488
            except AttributeError:
1✔
3489
                pass
1✔
3490

3491
        self.most_recent_context = context
1✔
3492
        self._reset_runtime_parameters(context)
1✔
3493

3494
        return value
1✔
3495

3496
    def is_finished(self, context=None):
1✔
3497
        """
3498
            Set by a Component to signal completion of its `execution <Component_Execution>` in a `TRIAL
3499
            <TimeScale.TRIAL>`; used by `Component-based Conditions <Conditions_Component_Based>` to predicate the
3500
            execution of one or more other Components on a Component.
3501
        """
3502
        return self.parameters.is_finished_flag._get(context)
1✔
3503

3504
    def _parse_param_port_sources(self):
1✔
3505
        if hasattr(self, '_parameter_ports'):
1✔
3506
            for param_port in self._parameter_ports:
1✔
3507
                try:
1✔
3508
                    orig_source = param_port.source
1✔
3509
                    param_port.source = param_port.source(self)
1✔
3510
                    del self.parameter_ports.parameter_mapping[orig_source]
1✔
3511
                    self.parameter_ports.parameter_mapping[param_port.source] = param_port
1✔
3512
                except TypeError:
1✔
3513
                    pass
1✔
3514
                param_port.source.port = param_port
1✔
3515

3516
    def _get_current_parameter_value(self, parameter, context=None):
1✔
3517
        from psyneulink.core.components.ports.parameterport import ParameterPortError
1✔
3518

3519
        if parameter == "variable" or parameter == self.parameters.variable:
1✔
3520
            raise ComponentError(
3521
                f"The method '_get_current_parameter_value' is intended for retrieving the current "
3522
                f"value of a modulable parameter; 'variable' is not a modulable parameter. If looking "
3523
                f"for {self.name}'s default variable, try '{self.name}.defaults.variable'."
3524
            )
3525

3526
        try:
1✔
3527
            parameter = getattr(self.parameters, parameter)
1✔
3528
        # just fail now if string and no corresponding parameter (AttributeError)
3529
        except TypeError:
1✔
3530
            pass
1✔
3531

3532
        parameter_port_list = None
1✔
3533
        try:
1✔
3534
            # parameter is SharedParameter and ultimately points to
3535
            # something with a corresponding ParameterPort
3536
            parameter_port_list = parameter.final_source._owner._owner.parameter_ports
1✔
3537
        except AttributeError:
1✔
3538
            # prefer parameter ports from self over owner
3539
            try:
1✔
3540
                parameter_port_list = self._parameter_ports
1✔
3541
            except AttributeError:
1✔
3542
                try:
1✔
3543
                    parameter_port_list = self.owner._parameter_ports
1✔
3544
                except AttributeError:
1✔
3545
                    pass
1✔
3546

3547
        if parameter_port_list is not None:
1✔
3548
            try:
1✔
3549
                return parameter_port_list[parameter].parameters.value._get(context)
1✔
3550
            # *parameter* string or Parameter didn't correspond to a parameter port
3551
            except TypeError:
1✔
3552
                pass
×
3553
            except ParameterPortError as e:
1✔
3554
                if 'Multiple ParameterPorts' in str(e):
1!
3555
                    raise
×
3556

3557
        return parameter._get(context, modulated=True)
1✔
3558

3559
    def _reset_runtime_parameters(self, context):
1✔
3560
        if context.execution_id in self._runtime_params_reset:
1✔
3561
            for key in self._runtime_params_reset[context.execution_id]:
1✔
3562
                self._set_parameter_value(
1✔
3563
                    key,
3564
                    self._runtime_params_reset[context.execution_id][key],
3565
                    context
3566
                )
3567
            self._runtime_params_reset[context.execution_id] = {}
1✔
3568

3569
    def _try_execute_param(self, param, var, context=None):
1✔
3570
        def execute_if_callable(value, context=None):
1✔
3571
            try:
1✔
3572
                return value(context=context)
1✔
3573
            except TypeError:
1✔
3574
                try:
1✔
3575
                    return value()
1✔
3576
                except TypeError:
1✔
3577
                    return value
1✔
3578

3579
        def fill_recursively(arr, value, indices=()):
1✔
3580
            try:
1✔
3581
                is_scalar = arr.ndim == 0
1✔
3582
            except AttributeError:
×
3583
                is_scalar = True
×
3584

3585
            if is_scalar:
1✔
3586
                return execute_if_callable(value, context)
1✔
3587

3588
            try:
1✔
3589
                len_value = len(value)
1✔
3590
                len_arr = safe_len(arr)
1✔
3591

3592
                if len_value > len_arr:
1!
3593
                    if len_arr == len_value - 1:
×
3594
                        ignored_items_str = f'Item {len_value - 1}'
×
3595
                    else:
3596
                        ignored_items_str = f'The items {len_arr} to {len_value - 1}'
×
3597

3598
                    warnings.warn(
×
3599
                        f'The length of {value} is greater than that of {arr}.'
3600
                        f'{ignored_items_str} will be ignored for index {indices}'
3601
                    )
3602
            except TypeError:
1✔
3603
                # if noise value is not an iterable, ignore shape warnings
3604
                pass
1✔
3605

3606
            for i, _ in enumerate(arr):
1✔
3607
                new_indices = indices + (i,)  # for error reporting
1✔
3608
                try:
1✔
3609
                    arr[i] = fill_recursively(arr[i], value[i], new_indices)
1✔
3610
                except (IndexError, TypeError):
1✔
3611
                    arr[i] = fill_recursively(arr[i], value, new_indices)
1✔
3612

3613
            return arr
1✔
3614

3615
        var = convert_all_elements_to_np_array(copy.deepcopy(var), cast_from=int, cast_to=float)
1✔
3616

3617
        # ignore zero-length variables (e.g. empty Buffer)
3618
        if var.shape == (0, ):
1✔
3619
            return var
1✔
3620

3621
        # handle simple wrapping of a Component (e.g. from ParameterPort in
3622
        # case of set after Component instantiation)
3623
        if (
1✔
3624
            (isinstance(param, list) and len(param) == 1)
3625
            or (isinstance(param, np.ndarray) and param.shape == (1,))
3626
        ):
3627
            if isinstance(param[0], Component) or len(var) > 1:
1✔
3628
                param = param[0]
1✔
3629

3630
        # Currently most noise functions do not return noise in the same
3631
        # shape as their variable:
3632
        if isinstance(param, Component):
1✔
3633
            try:
1✔
3634
                if param.defaults.value.shape == var.shape:
1✔
3635
                    return param(context=context)
1✔
3636
            except AttributeError:
×
3637
                pass
×
3638

3639
        # special case where var is shaped same as param, but with extra dims
3640
        # assign param elements to deepest dim of var (ex: param [1, 2, 3], var [[0, 0, 0]])
3641
        try:
1✔
3642
            if param.shape != var.shape:
1✔
3643
                if param.shape == np.squeeze(var).shape:
1✔
3644
                    param = param.reshape(var.shape)
1✔
3645
        except AttributeError:
1✔
3646
            pass
1✔
3647

3648
        try:
1✔
3649
            # try to broadcast param to variable if param is numeric and regular
3650
            param_arr = np.asarray(param)
1✔
3651
            if param_arr.dtype != object:
1✔
3652
                return np.broadcast_to(param_arr, var.shape)
1✔
3653
        except ValueError:
×
3654
            # param not directly compatible with variable, continue elementwise
3655
            pass
×
3656

3657
        param = try_extract_0d_array_item(param)
1✔
3658
        fill_recursively(var, param)
1✔
3659
        return var
1✔
3660

3661
    def _increment_execution_count(self, count=1):
1✔
3662
        self.parameters.execution_count.set(self.execution_count + count, override=True)
1✔
3663
        return self.execution_count
1✔
3664

3665
    def _increment_num_executions(self, context, time_scales:(list, TimeScale), count=1):
1✔
3666
        # get relevant Time object:
3667
        time_scales = list(time_scales)
1✔
3668
        assert [isinstance(i, TimeScale) for i in time_scales], \
1✔
3669
            'non-TimeScale value provided in time_scales argument of _increment_num_executions'
3670
        curr_num_execs = self.parameters.num_executions._get(context)
1✔
3671
        for time_scale in time_scales:
1✔
3672
            new_val = curr_num_execs._get_by_time_scale(time_scale) + count
1✔
3673
            curr_num_execs._set_by_time_scale(time_scale, new_val)
1✔
3674
        self.parameters.num_executions.set(curr_num_execs, override=True)
1✔
3675
        return curr_num_execs
1✔
3676

3677
    @property
1✔
3678
    def current_execution_time(self):
1✔
3679
        try:
×
3680
            return self._current_execution_time
×
3681
        except AttributeError:
×
3682
            self._update_current_execution_time(self.most_recent_context.string)
×
3683

3684
    def get_current_execution_time(self, context=None):
1✔
3685
        if context is None:
1!
3686
            return self.current_execution_time
×
3687
        else:
3688
            try:
1✔
3689
                return context.composition.scheduler.get_clock(context).time
1✔
3690
            except AttributeError:
×
3691
                return None
×
3692
    # MODIFIED 9/22/19 END
3693

3694
    def _get_current_execution_time(self, context):
1✔
3695
        return _get_time(self, context=context)
1✔
3696

3697
    def _update_current_execution_time(self, context):
1✔
3698
        self._current_execution_time = self._get_current_execution_time(context=context)
1✔
3699

3700
    def _change_function(self, to_function):
1✔
3701
        pass
×
3702

3703
    @property
1✔
3704
    def _sender_ports(self):
1✔
3705
        """
3706
        Returns:
3707
            ContentAddressableList: list containing Ports on this object
3708
            that can send Projections
3709
        """
3710
        from psyneulink.core.components.shellclasses import Port
1✔
3711

3712
        ports = []
1✔
3713
        try:
1✔
3714
            ports.extend(self.output_ports)
1✔
3715
        except AttributeError:
×
3716
            pass
×
3717

3718
        return ContentAddressableList(Port, list=ports)
1✔
3719

3720
    @property
1✔
3721
    def _receiver_ports(self):
1✔
3722
        """
3723
        Returns:
3724
            ContentAddressableList: list containing Ports on this object
3725
            that can receive Projections
3726
        """
3727
        from psyneulink.core.components.shellclasses import Port
1✔
3728

3729
        ports = []
1✔
3730
        try:
1✔
3731
            ports.extend(self.input_ports)
1✔
3732
        except AttributeError:
×
3733
            pass
×
3734

3735
        try:
1✔
3736
            ports.extend(self.parameter_ports)
1✔
3737
        except AttributeError:
1✔
3738
            pass
1✔
3739

3740
        return ContentAddressableList(Port, list=ports)
1✔
3741

3742
    def _get_matching_projections(self, component, projections, filter_component_is_sender):
1✔
3743
        from psyneulink.core.components.shellclasses import Projection
1✔
3744

3745
        if filter_component_is_sender:
1✔
3746
            def proj_matches_component(proj):
1✔
3747
                return proj.sender.owner == component or proj.sender == component
1✔
3748
        else:
3749
            def proj_matches_component(proj):
1✔
3750
                return proj.receiver.owner == component or proj.receiver == component
1✔
3751

3752
        if component:
1!
3753
            projections = filter(proj_matches_component, projections)
1✔
3754

3755
        return ContentAddressableList(Projection, list=list(projections))
1✔
3756

3757
    def get_afferents(self, from_component=None):
1✔
3758
        """
3759
        Args:
3760
            from_component (Component, optional): if specified, filters
3761
            returned list to contain only afferents originating from
3762
            *from_component* or one of its Ports. Defaults to None.
3763

3764
        Returns:
3765
            ContentAddressableList: list of afferent Projections to this
3766
            Component
3767
        """
3768
        projections = itertools.chain(*[p.all_afferents for p in self._receiver_ports])
1✔
3769
        return self._get_matching_projections(
1✔
3770
            from_component, projections, filter_component_is_sender=True
3771
        )
3772

3773
    def get_efferents(self, to_component=None):
1✔
3774
        """
3775
        Args:
3776
            to_component (Component, optional): if specified, filters
3777
            returned list to contain only efferents ending at
3778
            *to_component* or one of its Ports. Defaults to None.
3779

3780
        Returns:
3781
            ContentAddressableList: list of efferent Projections from
3782
            this Component
3783
        """
3784
        projections = itertools.chain(*[p.efferents for p in self._sender_ports])
1✔
3785
        return self._get_matching_projections(
1✔
3786
            to_component, projections, filter_component_is_sender=False
3787
        )
3788

3789
    @property
1✔
3790
    def name(self):
1✔
3791
        try:
1✔
3792
            return self._name
1✔
3793
        except AttributeError:
1✔
3794
            return 'unnamed {0}'.format(self.__class__)
1✔
3795

3796
    @name.setter
1✔
3797
    def name(self, value):
1✔
3798
        if not isinstance(value, str):
1✔
3799
            raise ComponentError(f"Name assigned to {self.__class__.__name__} ({value}) must be a string constant.")
3800

3801
        self._name = value
1✔
3802

3803
    @property
1✔
3804
    def input_shapes(self):
1✔
3805
        s = []
1✔
3806

3807
        try:
1✔
3808
            v = np.atleast_2d(self.defaults.variable)
1✔
3809
        except AttributeError:
×
3810
            return None
×
3811

3812
        for i in range(len(v)):
1✔
3813
            s.append(len(v[i]))
1✔
3814
        return np.array(s)
1✔
3815

3816
    @property
1✔
3817
    def prefs(self):
1✔
3818
        # Whenever pref is accessed, use current owner as context (for level checking)
3819
        self._prefs.owner = self
1✔
3820
        return self._prefs
1✔
3821

3822
    @prefs.setter
1✔
3823
    def prefs(self, pref_set):
1✔
3824
        if (isinstance(pref_set, PreferenceSet)):
1✔
3825
            # IMPLEMENTATION NOTE:
3826
            # - Complements dynamic assignment of owner in getter (above)
3827
            # - Needed where prefs are assigned before they've been gotten (e.g., in PreferenceSet.__init__()
3828
            # - owner needs to be assigned for call to get_pref_setting_for_level below
3829
            # MODIFIED 6/1/16
3830
            try:
1✔
3831
                pref_set.owner = self
1✔
3832
            except:
×
3833
            # MODIFIED 6/1/16 END
3834
                pass
×
3835
            self._prefs = pref_set
1✔
3836
            if self.prefs.verbosePref:
1✔
3837
                warnings.warn('PreferenceSet {0} assigned to {1}'.format(pref_set.name, self.name))
1✔
3838
            # Make sure that every pref attrib in PreferenceSet is OK
3839
            for pref_name, pref_entry in self.prefs.__dict__.items():
1✔
3840
                if '_pref' in pref_name:
1✔
3841
                    value, err_msg = self.prefs.get_pref_setting_for_level(pref_name, pref_entry.level)
1✔
3842
                    if err_msg and self.prefs.verbosePref:
1!
3843
                        warnings.warn(err_msg)
×
3844
                    # FIX: VALUE RETURNED SHOULD BE OK, SO ASSIGN IT INSTEAD OF ONE IN pref_set??
3845
                    # FIX: LEVEL SHOULD BE LOWER THAN REQUESTED;  REPLACE RAISE WITH WARNING TO THIS EFFECT
3846
        else:
3847
            raise ComponentError("Attempt to assign non-PreferenceSet {0} to {0}.prefs".
3848
                                format(pref_set, self.name))
3849

3850
    @property
1✔
3851
    def verbosePref(self):
1✔
3852
        return self.prefs.verbosePref
1✔
3853

3854
    @verbosePref.setter
1✔
3855
    def verbosePref(self, setting):
1✔
3856
        self.prefs.verbosePref = setting
1✔
3857

3858
    @property
1✔
3859
    def paramValidationPref(self):
1✔
3860
        return self.prefs.paramValidationPref
1✔
3861

3862
    @paramValidationPref.setter
1✔
3863
    def paramValidationPref(self, setting):
1✔
3864
        self.prefs.paramValidationPref = setting
×
3865

3866
    @property
1✔
3867
    def reportOutputPref(self):
1✔
3868
        from psyneulink.core.compositions.report import ReportOutput
1✔
3869
        if self.prefs.reportOutputPref is False:
1!
3870
            return ReportOutput.OFF
×
3871
        elif self.prefs.reportOutputPref is True:
1!
3872
            return ReportOutput.TERSE
×
3873
        return self.prefs.reportOutputPref
1✔
3874

3875
    @reportOutputPref.setter
1✔
3876
    def reportOutputPref(self, setting):
1✔
3877
        from psyneulink.core.compositions.report import ReportOutput
1✔
3878
        if setting is False:
1✔
3879
            setting = ReportOutput.OFF
1✔
3880
        elif setting is True:
1✔
3881
            setting = ReportOutput.TERSE
1✔
3882
        self.prefs.reportOutputPref = setting
1✔
3883

3884
    @property
1✔
3885
    def logPref(self):
1✔
3886
        return self.prefs.logPref
×
3887

3888
    @logPref.setter
1✔
3889
    def logPref(self, setting):
1✔
3890
        self.prefs.logPref = setting
×
3891

3892
    @property
1✔
3893
    def runtimeParamModulationPref(self):
1✔
3894
        return self.prefs.runtimeParamModulationPref
×
3895

3896
    @runtimeParamModulationPref.setter
1✔
3897
    def runtimeParamModulationPref(self, setting):
1✔
3898
        self.prefs.runtimeParamModulationPref = setting
×
3899

3900
    @property
1✔
3901
    def initialization_status(self):
1✔
3902
        try:
1✔
3903
            return self._initialization_status
1✔
3904
        except AttributeError:
1✔
3905
            self._initialization_status = ContextFlags.INITIALIZING
1✔
3906
        return self._initialization_status
1✔
3907

3908
    @initialization_status.setter
1✔
3909
    def initialization_status(self, flag):
1✔
3910
        """Check that a flag is one and only one status flag
3911
        """
3912
        if flag in INITIALIZATION_STATUS_FLAGS:
1!
3913
            self._initialization_status = flag
1✔
3914
        elif not flag:
×
3915
            self._initialization_status = ContextFlags.UNINITIALIZED
×
3916
        elif not (flag & ContextFlags.INITIALIZATION_MASK):
×
3917
            raise ContextError("Attempt to assign a flag ({}) to initialization_status "
3918
                               "that is not an initialization status flag".
3919
                               format(str(flag)))
3920
        else:
3921
            raise ContextError("Attempt to assign more than one flag ({}) to initialization_status".
3922
                               format(str(flag)))
3923

3924
    @property
1✔
3925
    def is_initializing(self):
1✔
3926
        try:
1✔
3927
            owner_initializing = self.owner.initialization_status == ContextFlags.INITIALIZING
1✔
3928
        except AttributeError:
1✔
3929
            owner_initializing = False
1✔
3930

3931
        return self.initialization_status == ContextFlags.INITIALIZING or owner_initializing
1✔
3932

3933
    @property
1✔
3934
    def log(self):
1✔
3935
        try:
1✔
3936
            return self._log
1✔
3937
        except AttributeError:
×
3938
            if self.initialization_status == ContextFlags.DEFERRED_INIT:
×
3939
                raise ComponentError("Initialization of {} is deferred; try assigning {} after it is complete "
3940
                                     "or appropriately configuring a Composition to which it belongs".
3941
                                     format(self.name, 'log'))
3942
            else:
3943
                raise AttributeError
3944

3945
    @log.setter
1✔
3946
    def log(self, log):
1✔
3947
        self._log = log
1✔
3948

3949
    @property
1✔
3950
    def loggable_items(self):
1✔
3951
        """Diciontary of items that can be logged in the Component's `log <Component.log>` and their current `ContextFlags`.
3952
        This is a convenience method that calls the `loggable_items <Log.loggable_items>` property of the Component's
3953
        `log <Component.log>`.
3954
        """
3955
        return self.log.loggable_items
1✔
3956

3957
    def set_log_conditions(self, items, log_condition=LogCondition.EXECUTION):
1✔
3958
        """
3959
        set_log_conditions(          \
3960
            items                    \
3961
            log_condition=EXECUTION  \
3962
        )
3963

3964
        Specifies items to be logged; these must be be `loggable_items <Component.loggable_items>` of the Component's
3965
        `log <Component.log>`. This is a convenience method that calls the `set_log_conditions <Log.set_log_conditions>`
3966
        method of the Component's `log <Component.log>`.
3967
        """
3968
        self.log.set_log_conditions(items=items, log_condition=log_condition)
1✔
3969

3970
    def set_delivery_conditions(self, items, delivery_condition=LogCondition.EXECUTION):
1✔
3971
        """
3972
        _set_delivery_conditions(          \
3973
            items                    \
3974
            delivery_condition=EXECUTION  \
3975
        )
3976

3977
        Specifies items to be delivered to external application via gRPC; these must be be `loggable_items <Component.loggable_items>`
3978
        of the Component's `log <Component.log>`. This is a convenience method that calls the `_set_delivery_conditions <Log._set_delivery_conditions>`
3979
        method of the Component's `log <Component.log>`.
3980
        """
3981
        self.log._set_delivery_conditions(items=items, delivery_condition=delivery_condition)
1✔
3982

3983
    def log_values(self, entries):
1✔
3984
        """
3985
        log_values(              \
3986
            entries              \
3987
        )
3988

3989
        Specifies items to be logged; ; these must be be `loggable_items <Component.loggable_items>` of the Component's
3990
        `log <Component.log>`. This is a convenience method that calls the `log_values <Log.log_values>` method
3991
        of the Component's `log <Component.log>`.
3992
        """
3993
        self.log.log_values(entries)
×
3994

3995
    def _propagate_most_recent_context(self, context=None, visited=None):
1✔
3996
        if visited is None:
1✔
3997
            visited = set([self])
1✔
3998

3999
        if context is None:
1!
4000
            context = self.most_recent_context
×
4001

4002
        self.most_recent_context = context
1✔
4003

4004
        # TODO: avoid duplicating objects in _dependent_components
4005
        # throughout psyneulink or at least condense these methods
4006
        for obj in self._dependent_components:
1✔
4007
            if obj not in visited:
1✔
4008
                visited.add(obj)
1✔
4009
                obj._propagate_most_recent_context(context, visited)
1✔
4010

4011
    def all_dependent_parameters(
1✔
4012
        self,
4013
        filter_name: typing.Union[str, typing.Iterable[str]] = None,
4014
        filter_regex: typing.Union[str, typing.Iterable[str]] = None,
4015
    ):
4016
        """Dictionary of Parameters of this Component and its \
4017
        `_dependent_components` filtered by **filter_name** and \
4018
        **filter_regex**. If no filter is specified, all Parameters \
4019
        are included.
4020

4021
        Args:
4022
            filter_name (Union[str, Iterable[str]], optional): The \
4023
                exact name or names of Parameters to include. Defaults \
4024
                to None.
4025
            filter_regex (Union[str, Iterable[str]], optional): \
4026
                Regular expression patterns. If any pattern matches a \
4027
                Parameter name (using re.match), it will be included \
4028
                in the result. Defaults to None.
4029

4030
        Returns:
4031
            dict[Parameter:Component]: Dictionary of filtered Parameters
4032
        """
4033
        def _all_dependent_parameters(obj, filter_name, filter_regex, visited):
1✔
4034
            parameters = {}
1✔
4035

4036
            if isinstance(filter_name, str):
1✔
4037
                filter_name = [filter_name]
1✔
4038

4039
            if isinstance(filter_regex, str):
1✔
4040
                filter_regex = [filter_regex]
1✔
4041

4042
            if filter_name is not None:
1✔
4043
                filter_name = set(filter_name)
1✔
4044

4045
            try:
1✔
4046
                filter_regex = [re.compile(r) for r in filter_regex]
1✔
4047
            except TypeError:
1✔
4048
                pass
1✔
4049

4050
            for p in obj.parameters:
1✔
4051
                include = filter_name is None and filter_regex is None
1✔
4052

4053
                if filter_name is not None:
1✔
4054
                    if p.name in filter_name:
1✔
4055
                        include = True
1✔
4056

4057
                if not include and filter_regex is not None:
1✔
4058
                    for r in filter_regex:
1✔
4059
                        if r.match(p.name):
1✔
4060
                            include = True
1✔
4061
                            break
1✔
4062

4063
                # owner check is primarily for value parameter on
4064
                # Composition which is deleted in
4065
                # ba56af82585e2d61f5b5bd13d9a19b7ee3b60124 presumably
4066
                # for clarity (results is used instead)
4067
                if include and p._owner._owner is obj:
1✔
4068
                    parameters[p] = obj
1✔
4069

4070
            for c in obj._dependent_components:
1✔
4071
                if c not in visited:
1✔
4072
                    visited.add(c)
1✔
4073
                    parameters.update(
1✔
4074
                        _all_dependent_parameters(
4075
                            c,
4076
                            filter_name,
4077
                            filter_regex,
4078
                            visited
4079
                        )
4080
                    )
4081

4082
            return parameters
1✔
4083

4084
        return _all_dependent_parameters(self, filter_name, filter_regex, set())
1✔
4085

4086
    def _get_mdf_parameters(self):
1✔
4087
        import modeci_mdf.mdf as mdf
1✔
4088

4089
        from psyneulink.core.compositions.composition import Composition
1✔
4090
        from psyneulink.core.components.ports.port import Port
1✔
4091
        from psyneulink.core.components.ports.outputport import OutputPort
1✔
4092
        from psyneulink.core.components.functions.nonstateful.transformfunctions import MatrixTransform
1✔
4093

4094
        def parse_parameter_value(value, no_expand_components=False, functions_as_dill=False):
1✔
4095
            if isinstance(value, (list, tuple)):
1✔
4096
                try:
1✔
4097
                    # treat as a collections.namedtuple
4098
                    type(value)._fields
1✔
4099
                except AttributeError:
1✔
4100
                    pass
1✔
4101
                else:
4102
                    # cannot expect MDF/neuromllite to handle our namedtuples
4103
                    value = tuple(value)
1✔
4104

4105
                new_item = []
1✔
4106
                for item in value:
1✔
4107
                    new_item.append(
1✔
4108
                        parse_parameter_value(
4109
                            item,
4110
                            no_expand_components,
4111
                            functions_as_dill
4112
                        )
4113
                    )
4114
                try:
1✔
4115
                    value = type(value)(new_item)
1✔
4116
                except TypeError:
×
4117
                    value = type(value)(*new_item)
×
4118
            elif isinstance(value, dict):
1✔
4119
                value = {
1✔
4120
                    parse_parameter_value(k, no_expand_components, functions_as_dill): parse_parameter_value(v, no_expand_components, functions_as_dill)
4121
                    for k, v in value.items()
4122
                }
4123
            elif isinstance(value, Composition):
1✔
4124
                value = value.name
1✔
4125
            elif isinstance(value, Port):
1✔
4126
                if isinstance(value, OutputPort):
1✔
4127
                    state_port_name = MODEL_SPEC_ID_OUTPUT_PORTS
1✔
4128
                else:
4129
                    state_port_name = MODEL_SPEC_ID_INPUT_PORTS
1✔
4130

4131
                # assume we will use the identifier on reconstitution
4132
                value = '{0}.{1}.{2}'.format(
1✔
4133
                    value.owner.name,
4134
                    state_port_name,
4135
                    value.name
4136
                )
4137
            elif isinstance(value, Component):
1✔
4138
                # could potentially create duplicates when it should
4139
                # create a reference to an already existent Component like
4140
                # with Compositions, but in a vacuum the full specification
4141
                # is necessary.
4142
                # in fact this would happen unless the parser specifically
4143
                # handles it like ours does
4144
                if no_expand_components:
1✔
4145
                    value = parse_valid_identifier(value.name)
1✔
4146
                else:
4147
                    try:
1✔
4148
                        value = value.as_mdf_model(simple_edge_format=False)
1✔
4149
                    except TypeError as e:
1✔
4150
                        if "got an unexpected keyword argument 'simple_edge_format'" not in str(e):
1!
4151
                            raise
×
4152
                        value = value.as_mdf_model()
1✔
4153
            elif isinstance(value, ComponentsMeta):
1✔
4154
                value = value.__name__
1✔
4155
            elif isinstance(value, (type, types.BuiltinFunctionType)):
1✔
4156
                if value.__module__ == 'builtins':
1!
4157
                    # just give standard type, like float or int
4158
                    value = f'{value.__name__}'
1✔
4159
                elif value is np.ndarray:
×
4160
                    value = f'{value.__module__}.array'
×
4161
                else:
4162
                    # some builtin modules are internally "_module"
4163
                    # but are imported with "module"
4164
                    value = f"{value.__module__.lstrip('_')}.{value.__name__}"
×
4165
            elif isinstance(value, types.MethodType):
1✔
4166
                if isinstance(value.__self__, Component):
1!
4167
                    # assume reference to a method on a Component is
4168
                    # automatically assigned (may not be totally
4169
                    # accurate but is in current known cases)
4170
                    value = None
1✔
4171
                else:
4172
                    value = value.__qualname__
×
4173

4174
            # numpy functions are no longer "FunctionType" since numpy
4175
            # moved dispatch implementation from Python to C in
4176
            # https://github.com/numpy/numpy/commit/60a858a372b14b73547baacf4a472eccfade1073
4177
            # Use np.sum as a representative of these functions
4178
            elif isinstance(value, (types.FunctionType, type(np.sum))):
1✔
4179
                if functions_as_dill:
1!
4180
                    value = base64.encodebytes(dill.dumps(value)).decode('utf-8')
1✔
4181
                elif '.' in value.__qualname__:
×
4182
                    value = value.__qualname__
×
4183
                else:
4184
                    try:
×
4185
                        if value is getattr(eval(value.__module__.replace('numpy', 'np')), value.__qualname__):
×
4186
                            return f'{value.__module__}.{value.__qualname__}'
×
4187
                    except (AttributeError, NameError, TypeError):
×
4188
                        pass
×
4189

4190
                    value = str(value)
×
4191
            elif isinstance(value, SampleIterator):
1✔
4192
                value = f'{value.__class__.__name__}({repr(value.specification)})'
1✔
4193
            elif value is NotImplemented:
1!
4194
                value = None
×
4195
            # IntEnum gets treated as int
4196
            elif isinstance(value, (Enum, types.SimpleNamespace)):
1✔
4197
                value = str(value)
1✔
4198
            elif not isinstance(value, (float, int, str, bool, mdf.Base, type(None), np.ndarray)):
1✔
4199
                value = str(value)
1✔
4200

4201
            return value
1✔
4202

4203
        # attributes that aren't Parameters but are psyneulink-specific
4204
        # and are stored in the PNL parameters section
4205
        implicit_parameter_attributes = ['node_ordering', 'required_node_roles']
1✔
4206

4207
        parameters_dict = {}
1✔
4208
        pnl_specific_parameters = {}
1✔
4209
        deferred_init_values = {}
1✔
4210

4211
        if self.initialization_status is ContextFlags.DEFERRED_INIT:
1✔
4212
            deferred_init_values = copy.copy(self._init_args)
1✔
4213
            try:
1✔
4214
                deferred_init_values.update(deferred_init_values['params'])
1✔
4215
            except (KeyError, TypeError):
1✔
4216
                pass
1✔
4217

4218
            # .parameters still refers to class parameters during deferred init
4219
            assert self.parameters._owner is not self
1✔
4220

4221
        for p in self.parameters:
1✔
4222
            if (
1✔
4223
                p.name not in self._model_spec_parameter_blacklist
4224
                and not isinstance(p, (ParameterAlias, SharedParameter))
4225
            ):
4226
                if self.initialization_status is ContextFlags.DEFERRED_INIT:
1✔
4227
                    try:
1✔
4228
                        val = deferred_init_values[p.name]
1✔
4229
                    except KeyError:
1✔
4230
                        # class default
4231
                        val = p.default_value
1✔
4232
                else:
4233
                    # special handling because MatrixTransform default values
4234
                    # can be PNL-specific keywords. In future, generalize
4235
                    # this workaround
4236
                    if (
1✔
4237
                        isinstance(self, MatrixTransform)
4238
                        and p.name == 'matrix'
4239
                    ):
4240
                        val = self.parameters.matrix.values[None]
1✔
4241
                    elif p.spec is not None:
1✔
4242
                        val = p.spec
1✔
4243
                    else:
4244
                        val = None
1✔
4245
                        if not p.stateful and not p.structural:
1✔
4246
                            val = p.get(None)
1✔
4247
                        if val is None:
1✔
4248
                            val = p.default_value
1✔
4249

4250
                val = parse_parameter_value(val, functions_as_dill=True)
1✔
4251

4252
                # split parameters designated as PsyNeuLink-specific and
4253
                # parameters that are universal
4254
                if p.pnl_internal:
1✔
4255
                    target_param_dict = pnl_specific_parameters
1✔
4256
                else:
4257
                    target_param_dict = parameters_dict
1✔
4258

4259
                if p.mdf_name is not None:
1✔
4260
                    target_param_dict[p.mdf_name] = val
1✔
4261
                else:
4262
                    target_param_dict[p.name] = val
1✔
4263

4264
        for attr in implicit_parameter_attributes:
1✔
4265
            try:
1✔
4266
                pnl_specific_parameters[attr] = parse_parameter_value(getattr(self, attr), no_expand_components=True, functions_as_dill=True)
1✔
4267
            except AttributeError:
1✔
4268
                pass
1✔
4269

4270
        if len(pnl_specific_parameters) > 0:
1!
4271
            parameters_dict[MODEL_SPEC_ID_PSYNEULINK] = pnl_specific_parameters
1✔
4272

4273
        return {self._model_spec_id_parameters: {k: v for k, v in parameters_dict.items()}}
1✔
4274

4275
    @property
1✔
4276
    def _mdf_model_parameters(self):
1✔
4277
        params = self._get_mdf_parameters()
1✔
4278
        try:
1✔
4279
            del params[self._model_spec_id_parameters][MODEL_SPEC_ID_PSYNEULINK]
1✔
4280
        except KeyError:
×
4281
            pass
×
4282

4283
        params[self._model_spec_id_parameters] = {
1✔
4284
            k: v for k, v in params[self._model_spec_id_parameters].items()
4285
            if (
4286
                k == MODEL_SPEC_ID_MDF_VARIABLE
4287
                or (
4288
                    isinstance(v, (numbers.Number, np.ndarray))
4289
                    and not isinstance(v, bool)
4290
                )
4291
            )
4292
        }
4293

4294
        return params
1✔
4295

4296
    @property
1✔
4297
    def _mdf_model_nonstateful_parameters(self):
1✔
4298
        parameters = self._mdf_model_parameters
1✔
4299

4300
        return {
1✔
4301
            self._model_spec_id_parameters: {
4302
                k: v for k, v in parameters[self._model_spec_id_parameters].items()
4303
                if (k not in self.parameters or getattr(self.parameters, k).initializer is None)
4304
            }
4305
        }
4306

4307
    @property
1✔
4308
    def _mdf_metadata(self):
1✔
4309
        all_parameters = self._get_mdf_parameters()
1✔
4310
        try:
1✔
4311
            params = all_parameters[self._model_spec_id_parameters][MODEL_SPEC_ID_PSYNEULINK]
1✔
4312
        except KeyError:
×
4313
            params = {}
×
4314

4315
        params = {
1✔
4316
            **params,
4317
            **{
4318
                k: v for k, v in all_parameters[self._model_spec_id_parameters].items()
4319
                if (
4320
                    k not in {MODEL_SPEC_ID_MDF_VARIABLE, MODEL_SPEC_ID_PSYNEULINK}
4321
                    and (
4322
                        not isinstance(v, (numbers.Number, np.ndarray))
4323
                        or isinstance(v, bool)
4324
                    )
4325
                )
4326
            }
4327
        }
4328

4329
        return {
1✔
4330
            MODEL_SPEC_ID_METADATA: {
4331
                'type': type(self).__name__,
4332
                **params
4333
            }
4334
        }
4335

4336
    def _set_mdf_arg(self, model, arg, value):
1✔
4337
        # must set both args attr and args in function because otherwise
4338
        # mdf dependency tracking doesn't work
4339
        try:
1✔
4340
            if model.function is not None:
1✔
4341
                model.function[list(model.function.keys())[0]][arg] = value
1✔
4342
        except AttributeError:
1✔
4343
            pass
1✔
4344

4345
        model.args[arg] = value
1✔
4346

4347
    def _add_to_composition(self, composition):
1✔
4348
        self.compositions.add(composition)
1✔
4349

4350
        for obj in self._parameter_components:
1✔
4351
            obj._add_to_composition(composition)
1✔
4352

4353
    def _remove_from_composition(self, composition):
1✔
4354
        self.compositions.discard(composition)
1✔
4355

4356
        for obj in self._parameter_components:
1✔
4357
            obj._remove_from_composition(composition)
1✔
4358

4359
    @property
1✔
4360
    def logged_items(self):
1✔
4361
        """Dictionary of all items that have entries in the log, and their currently assigned `ContextFlags`\\s
4362
        This is a convenience method that calls the `logged_items <Log.logged_items>` property of the Component's
4363
        `log <Component.log>`.
4364
        """
4365
        return self.log.logged_items
1✔
4366

4367
    @property
1✔
4368
    def _loggable_parameters(self):
1✔
4369
        return [param.name for param in self.parameters if param.loggable and param.user]
1✔
4370

4371
    @property
1✔
4372
    def _variable_shape_flexibility(self):
1✔
4373
        try:
1✔
4374
            return self.__variable_shape_flexibility
1✔
4375
        except AttributeError:
1✔
4376
            self.__variable_shape_flexibility = DefaultsFlexibility.FLEXIBLE
1✔
4377
            return self.__variable_shape_flexibility
1✔
4378

4379
    @_variable_shape_flexibility.setter
1✔
4380
    def _variable_shape_flexibility(self, value):
1✔
4381
        self.__variable_shape_flexibility = value
1✔
4382

4383
    @property
1✔
4384
    def class_parameters(self):
1✔
4385
        return self.__class__.parameters
1✔
4386

4387
    @property
1✔
4388
    def stateful_parameters(self):
1✔
4389
        """
4390
            A list of all of this object's `parameters <Parameters>` whose values
4391
            may change during runtime
4392
        """
4393
        return [param for param in self.parameters if param.stateful]
1✔
4394

4395
    @property
1✔
4396
    def stateful_attributes(self):
1✔
4397
        return [p.name for p in self.parameters if p.initializer is not None]
1✔
4398

4399
    @property
1✔
4400
    def initializers(self):
1✔
4401
        return [getattr(self.parameters, p).initializer for p in self.stateful_attributes]
1✔
4402

4403
    @property
1✔
4404
    def function_parameters(self):
1✔
4405
        """
4406
            The `parameters <Parameters>` object of this object's `function`
4407
        """
4408
        try:
1✔
4409
            return self.function.parameters
1✔
4410
        except AttributeError:
×
4411
            return None
×
4412

4413
    @property
1✔
4414
    def class_defaults(self):
1✔
4415
        """
4416
            Refers to the defaults of this object's class
4417
        """
4418
        return self.__class__.defaults
1✔
4419

4420
    @property
1✔
4421
    def is_pnl_inherent(self):
1✔
4422
        try:
×
4423
            return self._is_pnl_inherent
×
4424
        except AttributeError:
×
4425
            self._is_pnl_inherent = False
×
4426
            return self._is_pnl_inherent
×
4427

4428
    @property
1✔
4429
    def _parameter_components(self):
1✔
4430
        """
4431
            Returns a set of Components that are values of this object's
4432
            Parameters
4433
        """
4434
        try:
1✔
4435
            return self.__parameter_components
1✔
4436
        except AttributeError:
1✔
4437
            self.__parameter_components = set()
1✔
4438
            return self.__parameter_components
1✔
4439

4440
    @handle_external_context()
1✔
4441
    def _update_parameter_components(self, context=None):
1✔
4442
        # store all Components in Parameters to be used in
4443
        # _dependent_components for _initialize_from_context
4444
        for p in self.parameters:
1✔
4445
            try:
1✔
4446
                param_value = p._get(context)
1✔
4447
            except ParameterInvalidSourceError:
1✔
4448
                continue
1✔
4449

4450
            try:
1✔
4451
                param_value = param_value.__self__
1✔
4452
            except AttributeError:
1✔
4453
                pass
1✔
4454

4455
            if isinstance(param_value, Component) and param_value is not self:
1✔
4456
                self._parameter_components.add(param_value)
1✔
4457

4458
    @property
1✔
4459
    def _dependent_components(self) -> Iterable['Component']:
1✔
4460
        """
4461
            Returns:
4462
                Components that must have values in a given Context for
4463
                this Component to execute in that Context
4464
        """
4465
        return list(self._parameter_components)
1✔
4466

4467
    @property
1✔
4468
    def most_recent_context(self):
1✔
4469
        """
4470
        Returns:
4471
            `Context`: the `Context` object this Component used in its
4472
            last call to `execute <Component.execute>`,
4473
            `run <Composition.run>`, or `learn <Composition.learn>`. If
4474
            none of these methods have been called, a `Context` object
4475
            with the default execution_id (None)
4476
        """
4477
        try:
1✔
4478
            return self._most_recent_context
1✔
4479
        except AttributeError:
1✔
4480
            self._most_recent_context = Context(source=ContextFlags.COMMAND_LINE, execution_id=None)
1✔
4481
            return self._most_recent_context
1✔
4482

4483
    @most_recent_context.setter
1✔
4484
    def most_recent_context(self, value):
1✔
4485
        self._most_recent_context = value
1✔
4486

4487
    @property
1✔
4488
    def _model_spec_parameter_blacklist(self):
1✔
4489
        """
4490
            A set of Parameter names that should not be added to the generated
4491
            constructor string
4492
        """
4493
        return {'function', 'value', 'execution_count', 'is_finished_flag', 'num_executions', 'num_executions_before_finished'}
1✔
4494

4495

4496
COMPONENT_BASE_CLASS = Component
1✔
4497

4498

4499
def make_property_mod(param_name, parameter_port_name=None):
1✔
4500
    if parameter_port_name is None:
1!
4501
        parameter_port_name = param_name
×
4502

4503
    def getter(self):
1✔
4504
        warnings.warn(
1✔
4505
            f'Getting modulated parameter values with <object>.mod_<param_name>'
4506
            ' may be removed in a future release. It is replaced with,'
4507
            f' for example, <object>.{param_name}.modulated',
4508
            FutureWarning
4509
        )
4510
        try:
1✔
4511
            return self._parameter_ports[parameter_port_name].value
1✔
4512
        except TypeError:
×
4513
            raise ComponentError("{} does not have a '{}' ParameterPort."
4514
                                 .format(self.name, param_name))
4515

4516
    def setter(self, value):
1✔
4517
        raise ComponentError("Cannot set to {}'s mod_{} directly because it is computed by the ParameterPort."
4518
                             .format(self.name, param_name))
4519

4520
    prop = property(getter).setter(setter)
1✔
4521

4522
    return prop
1✔
4523

4524

4525
def make_stateful_getter_mod(param_name, parameter_port_name=None):
1✔
4526
    if parameter_port_name is None:
1!
4527
        parameter_port_name = param_name
×
4528

4529
    def getter(self, context=None):
1✔
4530
        try:
1✔
4531
            return self._parameter_ports[parameter_port_name].parameters.value.get(context)
1✔
4532
        except TypeError:
1✔
4533
            raise ComponentError("{} does not have a '{}' ParameterPort."
4534
                                 .format(self.name, param_name))
4535

4536
    return getter
1✔
4537

4538

4539
class ParameterValue:
1✔
4540
    def __init__(self, owner, parameter):
1✔
4541
        self._owner = owner
1✔
4542
        self._parameter = parameter
1✔
4543

4544
    def __repr__(self):
4545
        return f'{self._owner}:\n\t{self._parameter.name}.base: {self.base}\n\t{self._parameter.name}.modulated: {self.modulated}'
4546

4547
    @property
1✔
4548
    def modulated(self):
1✔
4549
        # TODO: consider using self._parameter.port.has_modulation
4550
        # because the port existing doesn't necessarily mean modulation
4551
        # is actually happening
4552
        if self._parameter.port is not None:
1✔
4553
            res = self._parameter.port.owner._get_current_parameter_value(
1✔
4554
                self._parameter,
4555
                self._owner.most_recent_context
4556
            )
4557
            if is_array_like(res):
1!
4558
                res = copy_parameter_value(res)
1✔
4559
            return res
1✔
4560
        else:
4561
            warnings.warn(
1✔
4562
                f'{self._parameter.name} is not currently modulated in most'
4563
                f' recent context {self._owner.most_recent_context}'
4564
            )
4565
            return None
1✔
4566

4567
    @modulated.setter
1✔
4568
    def modulated(self, value):
1✔
4569
        raise ComponentError(
4570
            f"Cannot set {self._owner.name}'s modulated {self._parameter.name}"
4571
            ' value directly because it is computed by the ParameterPort.'
4572
        )
4573

4574
    @property
1✔
4575
    def base(self):
1✔
4576
        return self._parameter.get(self._owner.most_recent_context)
1✔
4577

4578
    @base.setter
1✔
4579
    def base(self, value):
1✔
4580
        self._parameter.set(value, self._owner.most_recent_context)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc