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

PrincetonUniversity / PsyNeuLink / 11992518143

12 Nov 2024 03:50AM UTC coverage: 83.719% (-1.2%) from 84.935%
11992518143

push

github

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

Devel

9406 of 12466 branches covered (75.45%)

Branch coverage included in aggregate %.

3240 of 3767 new or added lines in 77 files covered. (86.01%)

120 existing lines in 26 files now uncovered.

32555 of 37655 relevant lines covered (86.46%)

0.86 hits per line

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

85.94
/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, CONTROL_PROJECTION, DEFERRED_INITIALIZATION, EXECUTE_UNTIL_FINISHED, \
532
    FUNCTION, FUNCTION_PARAMS, INIT_FULL_EXECUTE_METHOD, INPUT_PORTS, \
533
    LEARNING, LEARNING_PROJECTION, MATRIX, MAX_EXECUTIONS_BEFORE_FINISHED, \
534
    MODEL_SPEC_ID_PSYNEULINK, MODEL_SPEC_ID_METADATA, \
535
    MODEL_SPEC_ID_INPUT_PORTS, MODEL_SPEC_ID_OUTPUT_PORTS, \
536
    MODEL_SPEC_ID_MDF_VARIABLE, \
537
    MODULATORY_SPEC_KEYWORDS, NAME, OUTPUT_PORTS, OWNER, PARAMS, PREFS_ARG, \
538
    RESET_STATEFUL_FUNCTION_WHEN, INPUT_SHAPES, VALUE, VARIABLE, SHARED_COMPONENT_TYPES
539
from psyneulink.core.globals.log import LogCondition
1✔
540
from psyneulink.core.globals.parameters import \
1✔
541
    Defaults, SharedParameter, Parameter, ParameterAlias, ParameterError, ParametersBase, check_user_specified, copy_parameter_value, is_array_like
542
from psyneulink.core.globals.preferences.basepreferenceset import BasePreferenceSet, VERBOSE_PREF
1✔
543
from psyneulink.core.globals.preferences.preferenceset import \
1✔
544
    PreferenceLevel, PreferenceSet, _assign_prefs
545
from psyneulink.core.globals.registry import register_category, _get_auto_name_prefix
1✔
546
from psyneulink.core.globals.sampleiterator import SampleIterator
1✔
547
from psyneulink.core.globals.utilities import \
1✔
548
    ContentAddressableList, convert_all_elements_to_np_array, convert_to_np_array, get_deepcopy_with_shared, \
549
    is_instance_or_subclass, is_matrix, iscompatible, kwCompatibilityLength, \
550
    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
551
from psyneulink.core.scheduling.condition import Never
1✔
552
from psyneulink.core.scheduling.time import Time, TimeScale
1✔
553

554
__all__ = [
1✔
555
    'Component', 'COMPONENT_BASE_CLASS', 'component_keywords', 'ComponentError', 'ComponentLog',
556
    'DefaultsFlexibility', 'DeferredInitRegistry', 'parameter_keywords', 'ResetMode',
557
]
558

559
logger = logging.getLogger(__name__)
1✔
560

561
component_keywords = {NAME, VARIABLE, VALUE, FUNCTION, FUNCTION_PARAMS, PARAMS, PREFS_ARG, CONTEXT}
1✔
562

563
DeferredInitRegistry = {}
1✔
564

565

566
class ResetMode(Enum):
1✔
567
    """
568

569
    .. _Component_ResetMode:
570

571
    ResetModes used for **reset_params**:
572

573
    .. _CURRENT_TO_INSTANCE_DEFAULTS:
574

575
    *CURRENT_TO_INSTANCE_DEFAULTS*
576
      • resets all current values to instance default values.
577

578
    .. _INSTANCE_TO_CLASS:
579

580
    *INSTANCE_TO_CLASS*
581
      • resets all instance default values to class default values.
582

583
    .. _ALL_TO_CLASS_DEFAULTS:
584

585
    *ALL_TO_CLASS_DEFAULTS*
586
      • resets all current values and instance default values to \
587
      class default values
588

589
    """
590
    CURRENT_TO_INSTANCE_DEFAULTS = 0
1✔
591
    INSTANCE_TO_CLASS = 1
1✔
592
    ALL_TO_CLASS_DEFAULTS = 2
1✔
593

594

595
class DefaultsFlexibility(Enum):
1✔
596
    """
597
        Denotes how rigid an assignment to a default is. That is, how much it can be modified, if at all,
598
        to suit the purpose of a method/owner/etc.
599

600
        e.g. when assigning a Function to a Mechanism:
601

602
            ``pnl.TransferMechanism(default_variable=[0, 0], function=pnl.Linear())``
603

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

609
    Attributes
610
    ----------
611

612
    FLEXIBLE
613
        can be modified in any way.
614

615
    RIGID
616
        cannot be modifed in any way.
617

618
    INCREASE_DIMENSION
619
        can be wrapped in a single extra dimension.
620

621
    """
622
    FLEXIBLE = 0
1✔
623
    RIGID = 1
1✔
624
    INCREASE_DIMENSION = 2
1✔
625

626

627
parameter_keywords = set()
1✔
628

629
# suppress_validation_preference_set = BasePreferenceSet(prefs = {
630
#     PARAM_VALIDATION_PREF: PreferenceEntry(False,PreferenceLevel.INSTANCE),
631
#     VERBOSE_PREF: PreferenceEntry(False,PreferenceLevel.INSTANCE),
632
#     REPORT_OUTPUT_PREF: PreferenceEntry(True,PreferenceLevel.INSTANCE)})
633

634

635
class ComponentLog(IntEnum):
1✔
636
    NONE            = 0
1✔
637
    ALL = 0
1✔
638
    DEFAULTS = NONE
1✔
639

640

641
class ComponentError(Exception):
1✔
642
    def __init__(self, message, component=None):
1✔
643
        try:
1✔
644
            component_str = component.name
1✔
645
            try:
1✔
646
                if component.owner is not None:
1✔
647
                    component_str = f'{component_str} (owned by {component.owner.name})'
1✔
648
            except AttributeError:
1✔
649
                pass
1✔
650
        except AttributeError:
1✔
651
            component_str = None
1✔
652

653
        if component_str is not None:
1✔
654
            message = f'{component_str}: {message}'
1✔
655

656
        super().__init__(message)
1✔
657

658

659
def _get_parametervalue_attr(param):
1✔
660
    return f'_{param.name}'
1✔
661

662

663
def make_parameter_property(param):
1✔
664
    def getter(self):
1✔
665
        p = getattr(self.parameters, param.name)
1✔
666

667
        if p.port is not None:
1✔
668
            assert p.modulable
1✔
669
            return getattr(self, _get_parametervalue_attr(p))
1✔
670
        else:
671
            # _get does handle stateful case, but checking here avoids
672
            # extra overhead for most_recent_context and external get.
673
            # external get is being used so that dot-notation returns a
674
            # copy of stored numpy arrays. dot-notation is also often
675
            # used internally for non-stateful parameters, like
676
            # function, input_ports, output_ports, etc.
677
            if not p.stateful:
1✔
678
                return p._get()
1✔
679
            else:
680
                return p.get(self.most_recent_context)
1✔
681

682
    def setter(self, value):
1✔
683
        p = getattr(self.parameters, param.name)
1✔
684
        if p.port is not None:
1!
685
            assert p.modulable
×
686
            warnings.warn(
×
687
                'Setting parameter values directly using dot notation'
688
                ' may be removed in a future release. It is replaced with,'
689
                f' for example, <object>.{param.name}.base = {value}',
690
                FutureWarning,
691
            )
692
        try:
1✔
693
            getattr(self.parameters, p.name).set(value, self.most_recent_context)
1✔
694
        except ParameterError as e:
×
695
            if 'Pass override=True to force set.' in str(e):
×
696
                raise ParameterError(
697
                    f"Parameter '{p.name}' is read-only. Set at your own risk."
698
                    f' Use .parameters.{p.name}.set with override=True to force set.'
699
                ) from None
700

701
    return property(getter).setter(setter)
1✔
702

703

704
def _has_initializers_setter(value, owning_component=None, context=None, *, compilation_sync=False):
1✔
705
    """
706
    Assign has_initializers status to Component and any of its owners up the hierarchy.
707
    """
708
    if value and not compilation_sync:
1✔
709
        # only update owner's attribute if setting to True, because there may be
710
        # other children that have initializers
711
        try:
1✔
712
            owning_component.owner.parameters.has_initializers._set(value, context)
1✔
713
        except AttributeError:
1✔
714
            # no owner
715
            pass
1✔
716

717
    return value
1✔
718

719
# *****************************************   COMPONENT CLASS  ********************************************************
720

721
class ComponentsMeta(ABCMeta):
1✔
722
    def __init__(self, *args, **kwargs):
1✔
723
        super().__init__(*args, **kwargs)
1✔
724

725
        self.defaults = Defaults(owner=self)
1✔
726
        try:
1✔
727
            parent = self.__mro__[1].parameters
1✔
728
        except AttributeError:
1✔
729
            parent = None
1✔
730
        self.parameters = self.Parameters(owner=self, parent=parent)
1✔
731

732
        for param in self.parameters:
1✔
733
            if not hasattr(self, param.name):
1✔
734
                setattr(self, param.name, make_parameter_property(param))
1✔
735

736
            try:
1✔
737
                if param.default_value.owner is None:
1✔
738
                    param.default_value.owner = param
1✔
739
            except AttributeError:
1✔
740
                pass
1✔
741

742
    # consider removing this for explicitness
743
    # but can be useful for simplicity
744
    @property
1✔
745
    def class_defaults(self):
1✔
746
        return self.defaults
1✔
747

748

749
class Component(MDFSerializable, metaclass=ComponentsMeta):
1✔
750
    """
751
    Component(                 \
752
        default_variable=None, \
753
        input_shapes=None,             \
754
        params=None,           \
755
        name=None,             \
756
        prefs=None,            \
757
        context=None           \
758
    )
759

760
    Base class for Component.
761

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

764
    .. note::
765
       Component is an abstract class and should *never* be instantiated by a direct call to its constructor.
766
       It should be instantiated using the constructor for a subclass.
767

768
    COMMENT:
769
    FOR API DOCUMENTATION:
770
        The Component itself can be called without any arguments (in which case it uses its instance defaults) or
771
            one or more variables (as defined by the subclass) followed by an optional params dictionary
772
        The variable(s) can be a function reference, in which case the function is called to resolve the value;
773
            however:  it must be "wrapped" as an item in a list, so that it is not called before being passed
774
                      it must of course return a variable of the type expected for the variable
775
        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)
776
            of zeros.
777
        The default variableList is a list of default values, one for each of the variables defined in the child class
778
        The params argument is a dictionary; the key for each entry is the parameter name, associated with its value.
779
            + Component subclasses can define the param FUNCTION:<method or Function class>
780
        The instance defaults can be assigned at initialization or using the _instantiate_defaults class method;
781
            - if instance defaults are not assigned on initialization, the corresponding class defaults are assigned
782
        Each Component child class must initialize itself by calling super(childComponentName).__init__()
783
            with a default value for its variable, and optionally an instance default paramList.
784

785
        A subclass MUST either:
786
            - implement a <class>.function method OR
787
            - specify a default Function
788
            - this is checked in Component._instantiate_function()
789
            - if params[FUNCTION] is NOT specified, it is assigned to self.function (so that it can be referenced)
790
            - if params[FUNCTION] IS specified, it assigns it's value to self.function (superceding existing value):
791
                self.function is aliased to it (in Component._instantiate_function):
792
                    if FUNCTION is found on initialization:
793
                        if it is a reference to an instantiated function, self.function is pointed to it
794
                        if it is a class reference to a function:
795
                            it is instantiated using self.defaults.variable and FUNCTION_PARAMS (if they are there too)
796
                            this works, since _validate_params is always called after _validate_variable
797
                            so self.defaults.variable can be used to initialize function
798
                            to the method referenced by self.defaults.function
799
                    if self.function is found, an exception is raised
800

801
        NOTES:
802
            * In the current implementation, validation is:
803
              - top-level only (items in lists, tuples and dictionaries are not checked, nor are nested items)
804
              - for type only (it is oblivious to content)
805
              - forgiving (e.g., no distinction is made among numberical types)
806
            * However, more restrictive validation (e.g., recurisve, range checking, etc.) can be achieved
807
                by overriding the class _validate_variable and _validate_params methods
808

809
    COMMENT
810

811
    Arguments
812
    ---------
813

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

818
    input_shapes : int, or Iterable of tuple or int : default None
819
        specifies default_variable as array(s) of zeros if **default_variable** is not passed as an argument;
820
        if **default_variable** is specified, it is checked for
821
        compatibility against **input_shapes** (see
822
        `input_shapes <Component_Input_Shapes>` for additonal details).
823

824
    COMMENT:
825
    param_defaults :   :  default None,
826
    COMMENT
827

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

833
    name : str : for default see `name <Component_Name>`
834
        a string used for the name of the Component;  default is assigned by relevant `Registry` for Component
835
        (see `Registry_Naming` for conventions used for default and duplicate names).
836

837
    prefs : PreferenceSet or specification dict : default Component.classPreferences
838
        specifies the `PreferenceSet` for the Component (see `prefs <Component_Base.prefs>` for details).
839

840
    context : Context : default None
841
        specifies `context <Context>` in which Component is being initialized or executed.
842

843

844
    Attributes
845
    ----------
846

847
    variable : 2d np.array
848
        see `variable <Component_Variable>`
849

850
    input_shapes : Union[int, Iterable[Union[int, tuple]]]
851
        see `input_shapes <Component_Input_Shapes>`
852

853
    function : Function, function or method
854
        see `function <Component_Function>`
855

856
    value : 2d np.array
857
        see `value <Component_Value>`
858

859
    log : Log
860
        see `log <Component_Log>`
861

862
    execution_count : int
863
        see `execution_count <Component_Execution_Count>`
864

865
    num_executions : Time
866
        see `num_executions <_Component_Num_Executions>`
867

868
    current_execution_time : tuple(`Time.RUN`, `Time.TRIAL`, `Time.PASS`, `Time.TIME_STEP`)
869
        see `current_execution_time <Component_Current_Execution_Time>`
870

871
    execute_until_finished : bool
872
        see `execute_until_finished <Component_Execute_Until_Finished>`
873

874
    num_executions_before_finished : int
875
        see `num_executions_before_finished <Component_Num_Executions_Before_Finished>`
876

877
    max_executions_before_finished : bool
878
        see `max_executions_before_finished <Component_Max_Executions_Before_Finished>`
879

880
    stateful_parameters : list
881
        see `stateful_parameters <Component_Stateful_Parameters>`
882

883
    reset_stateful_function_when : `Condition`
884
        see `reset_stateful_function_when <Component_reset_stateful_function_when>`
885

886
    name : str
887
        see `name <Component_Name>`
888

889
    prefs : PreferenceSet
890
        see `prefs <Component_Prefs>`
891

892
    parameters :  Parameters
893
        see `parameters <Component_Parameters>` and `Parameters` for additional information.
894

895
    defaults : Defaults
896
        an object that provides access to the default values of a `Component's` `parameters`;
897
        see `parameter defaults <Parameter_Defaults>` for additional information.
898

899
    initialization_status : field of flags attribute
900
        indicates the state of initialization of the Component;
901
        one and only one of the following flags is always set:
902

903
            * `DEFERRED_INIT <ContextFlags.DEFERRED_INIT>`
904
            * `INITIALIZING <ContextFlags.INITIALIZING>`
905
            * `VALIDATING <ContextFlags.VALIDATING>`
906
            * `INITIALIZED <ContextFlags.INITIALIZED>`
907
            * `RESET <ContextFlags.RESET>`
908
            * `UNINITIALIZED <ContextFlags.UNINITALIZED>`
909

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

917
    name : str : default see `name <InputPort.name>`
918
        specifies the name of the InputPort; see InputPort `name <InputPort.name>` for details.
919

920
    prefs : PreferenceSet or specification dict : default Port.classPreferences
921
        specifies the `PreferenceSet` for the InputPort; see `prefs <InputPort.prefs>` for details.
922
    COMMENT
923

924
    """
925

926
    #CLASS ATTRIBUTES
927
    className = "COMPONENT"
1✔
928
    suffix = " " + className
1✔
929
# IMPLEMENTATION NOTE:  *** CHECK THAT THIS DOES NOT CAUSE ANY CHANGES AT SUBORDNIATE LEVELS TO PROPOGATE EVERYWHERE
930
    componentCategory = None
1✔
931
    componentType = None
1✔
932

933
    standard_constructor_args = {EXECUTE_UNTIL_FINISHED, FUNCTION_PARAMS, MAX_EXECUTIONS_BEFORE_FINISHED, RESET_STATEFUL_FUNCTION_WHEN, INPUT_SHAPES}
1✔
934

935
    # helper attributes for MDF model spec
936
    _model_spec_id_parameters = 'parameters'
1✔
937
    _model_spec_id_stateful_parameters = 'stateful_parameters'
1✔
938

939
    _model_spec_generic_type_name = NotImplemented
1✔
940
    """
941
        string describing this class's generic type in universal model
942
        specification,
943
        if it exists and is different than the class name
944
    """
945

946
    _model_spec_class_name_is_generic = False
1✔
947
    """
948
        True if the class name is the class's generic type in universal model specification,
949
        False otherwise
950
    """
951

952
    _specified_variable_shape_flexibility = DefaultsFlexibility.RIGID
1✔
953
    """
954
        The `DefaultsFlexibility` ._variable_shape_flexibility takes on
955
        when variable shape was manually specified
956
    """
957

958
    class Parameters(ParametersBase):
1✔
959
        """
960
            The `Parameters` that are associated with all `Components`
961

962
            Attributes
963
            ----------
964

965
                variable
966
                    see `variable <Component_Variable>`
967

968
                    :default value: numpy.array([0])
969
                    :type: ``numpy.ndarray``
970
                    :read only: True
971

972
                value
973
                    see `value <Component_Value>`
974

975
                    :default value: numpy.array([0])
976
                    :type: ``numpy.ndarray``
977
                    :read only: True
978

979
                execute_until_finished
980
                    see `execute_until_finished <Component_Execute_Until_Finished>`
981

982
                    :default value: True
983
                    :type: ``bool``
984

985
                execution_count
986
                    see `execution_count <Component_Execution_Count>`
987

988
                    :default value: 0
989
                    :type: ``int``
990
                    :read only: True
991

992
                num_executions
993
                    see `num_executions <_Component_Num_Executions>`
994

995
                    :default value:
996
                    :type: ``Time``
997
                    :read only: True
998

999
                has_initializers
1000
                    see `has_initializers <Component.has_initializers>`
1001

1002
                    :default value: False
1003
                    :type: ``bool``
1004

1005
                is_finished_flag
1006
                    internal parameter used by some Component types to track previous status of is_finished() method,
1007
                    or to set the status reported by the is_finished (see `is_finished <Component_Is_Finished>`
1008

1009
                    :default value: True
1010
                    :type: ``bool``
1011

1012
                max_executions_before_finished
1013
                    see `max_executions_before_finished <Component_Max_Executions_Before_Finished>`
1014

1015
                    :default value: 1000
1016
                    :type: ``int``
1017

1018
                num_executions_before_finished
1019
                    see `num_executions_before_finished <Component_Num_Executions_Before_Finished>`
1020

1021
                    :default value: 0
1022
                    :type: ``int``
1023
                    :read only: True
1024
        """
1025
        variable = Parameter(np.array([0]), read_only=True, pnl_internal=True, constructor_argument='default_variable')
1✔
1026
        value = Parameter(np.array([0]), read_only=True, pnl_internal=True)
1✔
1027
        has_initializers = Parameter(False, setter=_has_initializers_setter, pnl_internal=True)
1✔
1028
        # execution_count is not stateful because it is a global counter;
1029
        #    for context-specific counts should use schedulers which store this info
1030
        execution_count = Parameter(0,
1✔
1031
                                    read_only=True,
1032
                                    loggable=False,
1033
                                    stateful=False,
1034
                                    fallback_default=True,
1035
                                    pnl_internal=True)
1036
        is_finished_flag = Parameter(True, loggable=False, stateful=True)
1✔
1037
        execute_until_finished = Parameter(True, pnl_internal=True)
1✔
1038
        num_executions = Parameter(Time(), read_only=True, modulable=False, loggable=False, pnl_internal=True)
1✔
1039
        num_executions_before_finished = Parameter(0, read_only=True, modulable=False, pnl_internal=True)
1✔
1040
        max_executions_before_finished = Parameter(1000, modulable=False, pnl_internal=True)
1✔
1041

1042
        def _parse_variable(self, variable):
1✔
1043
            if variable is None:
1✔
1044
                return variable
1✔
1045

1046
            try:
1✔
1047
                return convert_to_np_array(variable)
1✔
1048
            except ValueError:
×
1049
                return convert_all_elements_to_np_array(variable)
×
1050

1051
        def _validate_variable(self, variable):
1✔
1052
            return None
1✔
1053

1054
        def _parse_modulable(self, param_name, param_value):
1✔
1055
            from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism import ModulatoryMechanism_Base
1✔
1056
            from psyneulink.core.components.ports.modulatorysignals import ModulatorySignal
1✔
1057
            from psyneulink.core.components.projections.modulatory.modulatoryprojection import ModulatoryProjection_Base
1✔
1058

1059
            # assume 2-tuple with class/instance as second item is a proper
1060
            # modulatory spec, can possibly add in a flag on acceptable
1061
            # classes in the future
1062
            if (
1✔
1063
                isinstance(param_value, tuple)
1064
                and len(param_value) == 2
1065
                and (
1066
                    is_instance_or_subclass(param_value[1], Component)
1067
                    or (
1068
                        isinstance(param_value[1], str)
1069
                        and param_value[1] in MODULATORY_SPEC_KEYWORDS
1070
                    )
1071
                )
1072
            ):
1073
                value = param_value[0]
1✔
1074
            elif (
1✔
1075
                is_instance_or_subclass(
1076
                    param_value,
1077
                    (ModulatoryMechanism_Base, ModulatorySignal, ModulatoryProjection_Base)
1078
                )
1079
                or (
1080
                    isinstance(param_value, str)
1081
                    and param_value in MODULATORY_SPEC_KEYWORDS
1082
                )
1083
            ):
1084
                value = getattr(self, param_name).default_value
1✔
1085
            else:
1086
                value = param_value
1✔
1087

1088
            if isinstance(value, list):
1✔
1089
                value = np.asarray(value)
1✔
1090

1091
            return value
1✔
1092

1093
    initMethod = INIT_FULL_EXECUTE_METHOD
1✔
1094

1095
    classPreferenceLevel = PreferenceLevel.COMPOSITION
1✔
1096
    # Any preferences specified below will override those specified in COMPOSITION_DEFAULT_PREFERENCES
1097
    # Note: only need to specify setting;  level will be assigned to COMPOSITION automatically
1098
    # classPreferences = {
1099
    #     PREFERENCE_SET_NAME: 'ComponentCustomClassPreferences',
1100
    #     PREFERENCE_KEYWORD<pref>: <setting>...}
1101

1102
    exclude_from_parameter_ports = [INPUT_PORTS, OUTPUT_PORTS]
1✔
1103

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

1110
    _deepcopy_shared_keys = frozenset(['owner'])
1✔
1111

1112
    @check_user_specified
1✔
1113
    def __init__(self,
1✔
1114
                 default_variable,
1115
                 param_defaults,
1116
                 input_shapes=None,
1117
                 function=None,
1118
                 name=None,
1119
                 reset_stateful_function_when=None,
1120
                 prefs=None,
1121
                 function_params=None,
1122
                 **kwargs):
1123
        """Assign default preferences; enforce required params; validate and instantiate params and execute method
1124

1125
        Initialization arguments:
1126
        - default_variable (anything): establishes type for the variable, used for validation
1127
        - input_shapes (int or list/array of ints): if specified, establishes variable if variable was not already specified
1128
        - params_default (dict): assigned as default
1129
        Note: if parameter_validation is off, validation is suppressed (for efficiency) (Component class default = on)
1130

1131
        """
1132
        context = Context(
1✔
1133
            source=ContextFlags.CONSTRUCTOR,
1134
            execution_phase=ContextFlags.IDLE,
1135
            execution_id=None,
1136
        )
1137

1138
        if reset_stateful_function_when is not None:
1✔
1139
            self.reset_stateful_function_when = reset_stateful_function_when
1✔
1140
        else:
1141
            self.reset_stateful_function_when = Never()
1✔
1142

1143
        parameter_values, function_params = self._parse_arguments(
1✔
1144
            default_variable, param_defaults, input_shapes, function, function_params, kwargs
1145
        )
1146

1147
        self._initialize_parameters(
1✔
1148
            context=context,
1149
            **parameter_values
1150
        )
1151

1152
        var = call_with_pruned_args(
1✔
1153
            self._handle_default_variable,
1154
            **parameter_values
1155
        )
1156
        if var is None:
1✔
1157
            default_variable = self.defaults.variable
1✔
1158
        else:
1159
            default_variable = var
1✔
1160
            self.parameters.variable._user_specified = True
1✔
1161

1162
        self.defaults.variable = copy.deepcopy(default_variable)
1✔
1163

1164
        self.parameters.variable._set(
1✔
1165
            copy_parameter_value(default_variable),
1166
            context=context,
1167
            skip_log=True,
1168
            skip_history=True,
1169
            override=True
1170
        )
1171

1172
        # ASSIGN PREFS
1173
        _assign_prefs(self, prefs, BasePreferenceSet)
1✔
1174

1175
        # VALIDATE VARIABLE AND PARAMS, AND ASSIGN DEFAULTS
1176

1177
        # TODO: the below overrides setting default values to None context,
1178
        # at least in stateless parameters. Possibly more. Below should be
1179
        # removed eventually
1180

1181
        # Validate the set passed in
1182
        self._instantiate_defaults(variable=default_variable,
1✔
1183
                                   request_set={k: v for (k, v) in self.defaults.values().items() if k in parameter_values},  # requested set
1184
               assign_missing=True,                   # assign missing params from classPreferences to instanceDefaults
1185
               target_set=self.defaults.values(), # destination set to which params are being assigned
1186
               default_set=self.class_defaults.values(),   # source set from which missing params are assigned
1187
               context=context,
1188
               )
1189

1190
        self.initial_shared_parameters = collections.defaultdict(dict)
1✔
1191

1192
        for param_name, param in self.parameters.values(show_all=True).items():
1✔
1193
            if (
1✔
1194
                isinstance(param, SharedParameter)
1195
                and not isinstance(param.source, ParameterAlias)
1196
            ):
1197
                try:
1✔
1198
                    if parameter_values[param_name] is not None:
1✔
1199
                        isp_val = parameter_values[param_name]
1✔
1200
                    else:
1201
                        isp_val = copy.deepcopy(param.default_value)
1✔
1202
                except KeyError:
1✔
1203
                    isp_val = copy.deepcopy(param.default_value)
1✔
1204

1205
                if isp_val is not None:
1✔
1206
                    self.initial_shared_parameters[param.attribute_name][param.shared_parameter_name] = isp_val
1✔
1207

1208
        # we must know the final variable shape before setting up parameter
1209
        # Functions or they will mismatch
1210
        self._instantiate_parameter_classes(context)
1✔
1211

1212
        # self.componentName = self.componentType
1213
        try:
1✔
1214
            self.componentName = self.componentName or self.componentType
1✔
1215
        except AttributeError:
1✔
1216
            self.componentName = self.componentType
1✔
1217

1218
        # ENFORCE REGISRY
1219
        if self.__class__.__bases__[0].__bases__[0].__bases__[0].__name__ == 'ShellClass':
1✔
1220
            try:
1✔
1221
                self.__class__.__bases__[0].registry
1✔
1222
            except AttributeError:
×
1223
                raise ComponentError("{0} is a category class and so must implement a registry".
1224
                                    format(self.__class__.__bases__[0].__name__))
1225

1226
        # ASSIGN LOG
1227
        from psyneulink.core.globals.log import Log
1✔
1228
        self.log = Log(owner=self)
1✔
1229
        # Used by run to store return value of execute
1230
        self.results = []
1✔
1231

1232
        if function is None:
1✔
1233
            if (
1✔
1234
                param_defaults is not None
1235
                and FUNCTION in param_defaults
1236
                and param_defaults[FUNCTION] is not None
1237
            ):
1238
                function = param_defaults[FUNCTION]
1✔
1239
            else:
1240
                try:
1✔
1241
                    function = self.class_defaults.function
1✔
1242
                except AttributeError:
1✔
1243
                    # assume function is a method on self
1244
                    pass
1✔
1245

1246
        self._runtime_params_reset = {}
1✔
1247

1248
        # KDM 11/12/19: this exists to deal with currently unknown attribute
1249
        # setting - if not set these will be included in logs as COMMAND_LINE
1250
        # settings. Remove this eventually
1251
        self.most_recent_context = context
1✔
1252

1253
        # INSTANTIATE ATTRIBUTES BEFORE FUNCTION
1254
        # Stub for methods that need to be executed before instantiating function
1255
        #    (e.g., _instantiate_sender and _instantiate_receiver in Projection)
1256
        # Allow _instantiate_attributes_before_function of subclass
1257
        #    to modify/replace function arg provided in constructor (e.g. TransferWithCosts)
1258
        function = self._instantiate_attributes_before_function(function=function, context=context) or function
1✔
1259

1260
        # INSTANTIATE FUNCTION
1261
        #    - assign initial function parameter values from ParameterPorts,
1262
        #    - assign function's output to self.defaults.value (based on call of self.execute)
1263
        self._instantiate_function(function=function, function_params=function_params, context=context)
1✔
1264

1265
        self._instantiate_value(context=context)
1✔
1266

1267
        # INSTANTIATE ATTRIBUTES AFTER FUNCTION
1268
        # Stub for methods that need to be executed after instantiating function
1269
        #    (e.g., instantiate_output_port in Mechanism)
1270
        self._instantiate_attributes_after_function(context=context)
1✔
1271

1272
        self._validate(context=context)
1✔
1273

1274
        self.initialization_status = ContextFlags.INITIALIZED
1✔
1275

1276
        self._update_parameter_components(context)
1✔
1277

1278
        self.compositions = weakref.WeakSet()
1✔
1279

1280
        # Delete the _user_specified_args attribute, we don't need it anymore
1281
        del self._user_specified_args
1✔
1282

1283
    def __repr__(self):
1284
        return '({0} {1})'.format(type(self).__name__, self.name)
1285
        #return '{1}'.format(type(self).__name__, self.name)
1286

1287
    def __lt__(self, other):
1✔
1288
        return self.name < other.name
1✔
1289

1290
    def __deepcopy__(self, memo):
1✔
1291
        if SHARED_COMPONENT_TYPES in memo:
1✔
1292
            if (
1✔
1293
                memo[SHARED_COMPONENT_TYPES]
1294
                and isinstance(self, memo[SHARED_COMPONENT_TYPES])
1295
            ):
1296
                return self
1✔
1297
        else:
1298
            memo[SHARED_COMPONENT_TYPES] = (Component,)
1✔
1299

1300
        fun = get_deepcopy_with_shared(self._deepcopy_shared_keys)
1✔
1301
        newone = fun(self, memo)
1✔
1302
        memo[id(self)] = newone
1✔
1303

1304
        if newone.parameters is not newone.class_parameters:
1✔
1305
            # may be in DEFERRED INIT, so parameters/defaults belongs to class
1306
            newone.parameters._owner = newone
1✔
1307
            newone.defaults._owner = newone
1✔
1308

1309
            for p in newone.parameters:
1✔
1310
                p._owner = newone.parameters
1✔
1311

1312
        # by copying, this instance is no longer "inherent" to a single
1313
        # 'import psyneulink' call
1314
        newone._is_pnl_inherent = False
1✔
1315

1316
        return newone
1✔
1317

1318
    # ------------------------------------------------------------------------------------------------------------------
1319
    # Compilation support
1320
    # ------------------------------------------------------------------------------------------------------------------
1321
    def _is_compilable_param(self, p):
1✔
1322

1323
        # User only parameters are not compiled.
1324
        if p.read_only and p.getter is not None:
1✔
1325
            return False
1✔
1326

1327
        # Shared and aliased parameters are for user conveniecne and not compiled.
1328
        if isinstance(p, (ParameterAlias, SharedParameter)):
1✔
1329
            return False
1✔
1330

1331
        # TODO this should use default value
1332
        val = p.get()
1✔
1333

1334
        # Strings, builtins, functions, and methods are not compilable
1335
        return not isinstance(val, (str,
1✔
1336
                                    type(max),
1337
                                    type(np.sum),
1338
                                    type(make_parameter_property),
1339
                                    type(self._get_compilation_params)))
1340

1341

1342
    def _get_compilation_state(self):
1✔
1343
        # FIXME: MAGIC LIST, Use stateful tag for this
1344
        whitelist = {"previous_time", "previous_value", "previous_v",
1✔
1345
                     "previous_w", "random_state",
1346
                     "input_ports", "output_ports",
1347
                     "adjustment_cost", "intensity_cost", "duration_cost",
1348
                     "intensity"}
1349

1350
        # Prune subcomponents (which are enabled by type rather than a list)
1351
        # that should be omitted
1352
        blacklist = { "objective_mechanism", "agent_rep", "projections", "shadow_inputs"}
1✔
1353

1354
        # Mechanisms;
1355
        # * use "value" state
1356
        # * can execute 'until finished'
1357
        # * need to track number of executions
1358
        if hasattr(self, 'ports'):
1✔
1359
            whitelist.update({"value", "num_executions_before_finished",
1✔
1360
                              "num_executions", "is_finished_flag"})
1361

1362
            # If both the mechanism and its function use random_state.
1363
            # it's DDM with integrator function.
1364
            # The mechanism's random_state is not used.
1365
            if hasattr(self.parameters, 'random_state') and hasattr(self.function.parameters, 'random_state'):
1✔
1366
                whitelist.remove('random_state')
1✔
1367

1368

1369
        # Compositions need to track number of executions
1370
        if hasattr(self, 'nodes'):
1✔
1371
            whitelist.add("num_executions")
1✔
1372

1373
        # Drop combination function params from RTM if not needed
1374
        if getattr(self.parameters, 'has_recurrent_input_port', False):
1✔
1375
            blacklist.add('combination_function')
1✔
1376

1377
        # Drop integrator function if integrator_mode is not enabled
1378
        if not getattr(self, 'integrator_mode', False):
1✔
1379
            blacklist.add('integrator_function')
1✔
1380

1381
        # Drop unused cost functions
1382
        cost_functions = getattr(self, 'enabled_cost_functions', None)
1✔
1383
        if cost_functions is not None:
1✔
1384
            if cost_functions.INTENSITY not in cost_functions:
1✔
1385
                blacklist.add('intensity_cost_fct')
1✔
1386
            if cost_functions.ADJUSTMENT not in cost_functions:
1✔
1387
                blacklist.add('adjustment_cost_fct')
1✔
1388
            if cost_functions.DURATION not in cost_functions:
1✔
1389
                blacklist.add('duration_cost_fct')
1✔
1390

1391
        # Drop previous_value from MemoryFunctions
1392
        if hasattr(self.parameters, 'duplicate_keys'):
1✔
1393
            blacklist.add("previous_value")
1✔
1394

1395
        # Matrices of learnable projections are stateful
1396
        if getattr(self, 'owner', None) and getattr(self.owner, 'learnable', False):
1✔
1397
            whitelist.add('matrix')
1✔
1398

1399
        def _is_compilation_state(p):
1✔
1400
            # FIXME: This should use defaults instead of 'p.get'
1401
            return p.name not in blacklist and \
1✔
1402
                   (p.name in whitelist or isinstance(p.get(), Component)) and \
1403
                   self._is_compilable_param(p)
1404

1405
        return filter(_is_compilation_state, self.parameters)
1✔
1406

1407
    def _get_state_ids(self):
1✔
1408
        return [sp.name for sp in self._get_compilation_state()]
1✔
1409

1410
    @property
1✔
1411
    def llvm_state_ids(self):
1✔
1412
        ids = getattr(self, "_state_ids", None)
1✔
1413
        if ids is None:
1✔
1414
            ids = self._get_state_ids()
1✔
1415
            setattr(self, "_state_ids", ids)
1✔
1416
        return ids
1✔
1417

1418
    def _get_state_initializer(self, context):
1✔
1419
        def _convert(p):
1✔
1420
            x = p.get(context)
1✔
1421
            if p.name == 'matrix': # Flatten matrix
1✔
1422
                val = tuple(np.asfarray(x).flatten())
1✔
1423
            elif isinstance(x, np.random.RandomState):
1✔
1424
                state = x.get_state(legacy=False)
1✔
1425

1426
                # Keep the indices in sync with bultins.py:get_mersenne_twister_state_struct
1427
                val = pnlvm._tupleize((state['state']['key'],
1✔
1428
                                       state['gauss'],
1429
                                       state['state']['pos'],
1430
                                       state['has_gauss'],
1431
                                       x.used_seed[0]))
1432
            elif isinstance(x, np.random.Generator):
1✔
1433
                state = x.bit_generator.state
1✔
1434

1435
                # Keep the indices in sync with bultins.py:get_philox_state_struct
1436
                val = pnlvm._tupleize((state['state']['counter'],
1✔
1437
                                       state['state']['key'],
1438
                                       state['buffer'],
1439
                                       state['uinteger'],
1440
                                       state['buffer_pos'],
1441
                                       state['has_uint32'],
1442
                                       x.used_seed[0]))
1443
            elif isinstance(x, Time):
1✔
1444
                val = tuple(x._get_by_time_scale(t) for t in TimeScale)
1✔
1445
            elif isinstance(x, Component):
1✔
1446
                return x._get_state_initializer(context)
1✔
1447
            elif isinstance(x, ContentAddressableList):
1✔
1448
                return tuple(p._get_state_initializer(context) for p in x)
1✔
1449
            else:
1450
                val = pnlvm._tupleize(x)
1✔
1451

1452
            return tuple(val for _ in range(p.history_min_length + 1))
1✔
1453

1454
        return tuple(map(_convert, self._get_compilation_state()))
1✔
1455

1456
    def _get_compilation_params(self):
1✔
1457
        # FIXME: MAGIC LIST, detect used parameters automatically
1458
        blacklist = {# Stateful parameters
1✔
1459
                     "previous_time", "previous_value", "previous_v",
1460
                     "previous_w", "random_state", "is_finished_flag",
1461
                     "num_executions_before_finished", "num_executions",
1462
                     "variable", "value", "saved_values", "saved_samples",
1463
                     "integrator_function_value", "termination_measure_value",
1464
                     "execution_count", "intensity", "combined_costs",
1465
                     "adjustment_cost", "intensity_cost", "duration_cost",
1466
                     # Invalid types
1467
                     "input_port_variables", "results", "simulation_results",
1468
                     "monitor_for_control", "state_feature_values", "simulation_ids",
1469
                     "input_labels_dict", "output_labels_dict", "num_estimates",
1470
                     "modulated_mechanisms", "grid", "control_signal_params",
1471
                     "activation_derivative_fct", "input_specification",
1472
                     "state_feature_specs",
1473
                     # Reference to other components
1474
                     "objective_mechanism", "agent_rep", "projections",
1475
                     "outcome_input_ports", "state_input_ports",
1476
                     # autodiff specific types
1477
                     "pytorch_representation", "optimizer", "synch_projection_matrices_with_torch",
1478
                     # duplicate
1479
                     "allocation_samples", "control_allocation_search_space",
1480
                     # not used in computation
1481
                     "auto", "hetero", "cost", "costs",
1482
                     "control_signal", "competition",
1483
                     "has_recurrent_input_port", "enable_learning",
1484
                     "enable_output_type_conversion", "changes_shape",
1485
                     "output_type", "bounds", "internal_only",
1486
                     "require_projection_in_composition", "default_input",
1487
                     "shadow_inputs", "compute_reconfiguration_cost",
1488
                     "reconfiguration_cost", "net_outcome", "outcome",
1489
                     "enabled_cost_functions", "control_signal_costs",
1490
                     "default_allocation", "same_seed_for_all_allocations",
1491
                     "search_statefulness", "initial_seed", "combine",
1492
                     "random_variables", "smoothing_factor", "per_item",
1493
                     "key_size", "val_size", "max_entries", "random_draw",
1494
                     "randomization_dimension", "save_values", "save_samples",
1495
                     "max_iterations", "duplicate_keys",
1496
                     "search_termination_function", "state_feature_function",
1497
                     "search_function", "weight", "exponent", "gating_signal_params",
1498
                     "retain_old_simulation_data",
1499
                     # not used in compiled learning
1500
                     "learning_results", "learning_signal", "learning_signals",
1501
                     "error_matrix", "error_signal", "activation_input",
1502
                     "activation_output", "error_sources", "covariates_sources",
1503
                     "target", "sample", "learning_function",
1504
                     "minibatch_size", "optimizations_per_minibatch", "device",
1505
                     "retain_torch_trained_outputs", "retain_torch_targets", "retain_torch_losses"
1506
                     "torch_trained_outputs", "torch_targets", "torch_losses",
1507
                     # should be added to relevant _gen_llvm_function... when aug:
1508
                     # OneHot:
1509
                     'abs_val', 'indicator',
1510
                     # SoftMax:
1511
                     'mask_threshold', 'adapt_scale', 'adapt_base', 'adapt_entropy_weighting',
1512
                     # LCAMechanism
1513
                     "mask"
1514
                     }
1515
        # Mechanism's need few extra entries:
1516
        # * matrix -- is never used directly, and is flatened below
1517
        # * integration_rate -- shape mismatch with param port input
1518
        # * initializer -- only present on DDM and never used
1519
        # * search_space -- duplicated between OCM and its function
1520
        if hasattr(self, 'ports'):
1✔
1521
            blacklist.update(["matrix", "integration_rate", "initializer", "search_space"])
1✔
1522
        else:
1523
            # Execute until finished is only used by mechanisms
1524
            blacklist.update(["execute_until_finished", "max_executions_before_finished"])
1✔
1525

1526
            # "has_initializers" is only used by RTM
1527
            blacklist.add('has_initializers')
1✔
1528

1529
        # Drop combination function params from RTM if not needed
1530
        if getattr(self.parameters, 'has_recurrent_input_port', False):
1✔
1531
            blacklist.add('combination_function')
1✔
1532

1533
        # Drop integrator function if integrator_mode is not enabled
1534
        if not getattr(self, 'integrator_mode', False):
1✔
1535
            blacklist.add('integrator_function')
1✔
1536

1537
        # Drop unused cost functions
1538
        cost_functions = getattr(self, 'enabled_cost_functions', None)
1✔
1539
        if cost_functions is not None:
1✔
1540
            if cost_functions.INTENSITY not in cost_functions:
1✔
1541
                blacklist.add('intensity_cost_fct')
1✔
1542
            if cost_functions.ADJUSTMENT not in cost_functions:
1✔
1543
                blacklist.add('adjustment_cost_fct')
1✔
1544
            if cost_functions.DURATION not in cost_functions:
1✔
1545
                blacklist.add('duration_cost_fct')
1✔
1546

1547
        # Matrices of learnable projections are stateful
1548
        if getattr(self, 'owner', None) and getattr(self.owner, 'learnable', False):
1✔
1549
            blacklist.add('matrix')
1✔
1550

1551
        def _is_compilation_param(p):
1✔
1552
            return p.name not in blacklist and self._is_compilable_param(p)
1✔
1553

1554
        return filter(_is_compilation_param, self.parameters)
1✔
1555

1556
    def _get_param_ids(self):
1✔
1557
        return [p.name for p in self._get_compilation_params()]
1✔
1558

1559
    @property
1✔
1560
    def llvm_param_ids(self):
1✔
1561
        ids = getattr(self, "_param_ids", None)
1✔
1562
        if ids is None:
1✔
1563
            ids = self._get_param_ids()
1✔
1564
            setattr(self, "_param_ids", ids)
1✔
1565
        return ids
1✔
1566

1567
    def _is_param_modulated(self, p):
1✔
1568
        try:
1✔
1569
            if p in self.owner.parameter_ports:
1✔
1570
                return True
1✔
1571
        except AttributeError:
1✔
1572
            pass
1✔
1573
        try:
1✔
1574
            if p in self.parameter_ports:
1✔
1575
                return True
1✔
1576
        except AttributeError:
1✔
1577
            pass
1✔
1578
        try:
1✔
1579
            modulated_params = (
1✔
1580
                getattr(self.parameters, p.sender.modulation).source
1581
                for p in self.owner.mod_afferents)
1582
            if p in modulated_params:
1✔
1583
                return True
1✔
1584
        except AttributeError:
1✔
1585
            pass
1✔
1586

1587
        return False
1✔
1588

1589
    def _get_param_initializer(self, context):
1✔
1590
        def _convert(x):
1✔
1591
            if isinstance(x, Enum):
1✔
1592
                return x.value
1✔
1593
            elif isinstance(x, SampleIterator):
1✔
1594
                if isinstance(x.generator, list):
1✔
1595
                    return tuple(v for v in x.generator)
1✔
1596
                else:
1597
                    return (x.start, x.step, x.num)
1✔
1598
            elif isinstance(x, Component):
1✔
1599
                return x._get_param_initializer(context)
1✔
1600

1601
            try:
1✔
1602
                # This can't use tupleize and needs to recurse to handle
1603
                # 'search_space' list of SampleIterators
1604
                return tuple(_convert(i) for i in x)
1✔
1605
            except TypeError:
1✔
1606
                return x if x is not None else tuple()
1✔
1607

1608
        def _get_values(p):
1✔
1609
            param = p.get(context)
1✔
1610
            # Modulated parameters change shape to array
1611
            if np.ndim(param) == 0 and self._is_param_modulated(p):
1✔
1612
                return (param,)
1✔
1613
            elif p.name == 'num_trials_per_estimate': # Should always be int
1✔
1614
                return 0 if param is None else int(param)
1✔
1615
            elif p.name == 'matrix': # Flatten matrix
1✔
1616
                return tuple(np.asfarray(param).flatten())
1✔
1617
            return _convert(param)
1✔
1618

1619
        return tuple(map(_get_values, self._get_compilation_params()))
1✔
1620

1621
    def _gen_llvm_function_reset(self, ctx, builder, *_, tags):
1✔
1622
        assert "reset" in tags
1✔
1623
        return builder
1✔
1624

1625
    def _gen_llvm_function(self, *, ctx:pnlvm.LLVMBuilderContext,
1✔
1626
                                    extra_args=[], tags:frozenset):
1627
        args = [ctx.get_param_struct_type(self).as_pointer(),
1✔
1628
                ctx.get_state_struct_type(self).as_pointer(),
1629
                ctx.get_input_struct_type(self).as_pointer(),
1630
                ctx.get_output_struct_type(self).as_pointer()]
1631
        builder = ctx.create_llvm_function(args + extra_args, self, tags=tags)
1✔
1632

1633
        params, state, arg_in, arg_out = builder.function.args[:len(args)]
1✔
1634
        if len(extra_args) == 0:
1✔
1635
            for p in params, state, arg_in, arg_out:
1✔
1636
                p.attributes.add('noalias')
1✔
1637

1638
        if "reset" in tags:
1✔
1639
            builder = self._gen_llvm_function_reset(ctx, builder, params, state,
1✔
1640
                                                    arg_in, arg_out, tags=tags)
1641
        else:
1642
            builder = self._gen_llvm_function_body(ctx, builder, params, state,
1✔
1643
                                                   arg_in, arg_out, tags=tags)
1644
        builder.ret_void()
1✔
1645
        return builder.function
1✔
1646

1647
    # ------------------------------------------------------------------------------------------------------------------
1648
    # Handlers
1649
    # ------------------------------------------------------------------------------------------------------------------
1650

1651
    def _handle_default_variable(self, default_variable=None, input_shapes=None):
1✔
1652
        """
1653
            Finds whether default_variable can be determined using **default_variable** and **input_shapes**
1654
            arguments.
1655

1656
            Returns
1657
            -------
1658
                a default variable if possible
1659
                None otherwise
1660
        """
1661
        default_variable = self._parse_arg_variable(default_variable)
1✔
1662
        default_variable = self._handle_input_shapes(input_shapes, default_variable)
1✔
1663

1664
        if default_variable is None or default_variable is NotImplemented:
1✔
1665
            return None
1✔
1666
        else:
1667
            self._variable_shape_flexibility = self._specified_variable_shape_flexibility
1✔
1668

1669
        return convert_to_np_array(default_variable, dimension=1)
1✔
1670

1671
    def _parse_input_shapes(
1✔
1672
        self, input_shapes: Union[int, Iterable[Union[int, tuple]]]
1673
    ) -> np.ndarray:
1674
        """
1675
        Returns the equivalent 'variable' array specified by **input_shapes**
1676

1677
        Args:
1678
            input_shapes (Union[int, Iterable[Union[int, tuple]]])
1679

1680
        Returns:
1681
            np.ndarray
1682
        """
1683
        def get_input_shapes_elem(s, idx=None):
1✔
1684
            try:
1✔
1685
                return np.zeros(s)
1✔
1686
            except (TypeError, ValueError) as e:
1✔
1687
                if idx is not None:
1!
NEW
1688
                    idx_str = f' at index {idx}'
×
1689
                else:
1690
                    idx_str = ''
1✔
1691

1692
                raise ComponentError(
1693
                    f'Invalid input_shapes argument of {self}{idx_str}. input_shapes must be a'
1694
                    ' valid numpy shape or a list of shapes for use with'
1695
                    f' numpy.zeros: {e}'
1696
                ) from e
1697

1698
        if not is_iterable(input_shapes, exclude_str=True):
1✔
1699
            variable_from_input_shapes = np.asarray([get_input_shapes_elem(input_shapes)])
1✔
1700
        else:
1701
            if len(input_shapes) == 0:
1✔
1702
                raise ComponentError(
1703
                    f'Invalid input_shapes argument of {self}. input_shapes must not be an empty list'
1704
                )
1705
            variable_from_input_shapes = []
1✔
1706
            for i, s in enumerate(input_shapes):
1✔
1707
                variable_from_input_shapes.append(get_input_shapes_elem(s, i))
1✔
1708
            variable_from_input_shapes = convert_all_elements_to_np_array(variable_from_input_shapes)
1✔
1709

1710
        return variable_from_input_shapes
1✔
1711

1712
    # ELIMINATE SYSTEM
1713
    # IMPLEMENTATION NOTE: (7/7/17 CW) Due to System and Process being initialized with input_shapes at the moment (which will
1714
    # be removed later), I’m keeping _handle_input_shapes in Component.py. I’ll move the bulk of the function to Mechanism
1715
    # through an override, when Composition is done. For now, only Port.py overwrites _handle_input_shapes().
1716
    def _handle_input_shapes(self, input_shapes, variable):
1✔
1717
        """If variable is None, _handle_input_shapes tries to infer variable based on the **input_shapes** argument to the
1718
            __init__() function. If input_shapes is None (usually in the case of
1719
            Projections/Functions), then this function passes without
1720
            doing anything. If both input_shapes and variable are not None, a
1721
            ComponentError is thrown if they are not compatible.
1722
        """
1723
        if input_shapes is not None:
1✔
1724
            self._variable_shape_flexibility = self._specified_variable_shape_flexibility
1✔
1725
            # region Fill in and infer variable and input_shapes if they aren't specified in args
1726
            # if variable is None and input_shapes is None:
1727
            #     variable = self.class_defaults.variable
1728
            # 6/30/17 now handled in the individual subclasses' __init__() methods because each subclass has different
1729
            # expected behavior when variable is None and input_shapes is None.
1730

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

1734
            def conflict_error(reason=None):
1✔
1735
                if reason is not None:
1!
1736
                    reason_str = f': {reason}'
1✔
1737
                else:
NEW
1738
                    reason_str = ''
×
1739

1740
                return ComponentError(
1✔
1741
                    f'input_shapes and default_variable arguments of {self} conflict{reason_str}'
1742
                )
1743

1744
            variable_from_input_shapes = self._parse_input_shapes(input_shapes)
1✔
1745

1746
            if variable is None:
1✔
1747
                return variable_from_input_shapes
1✔
1748

1749
            if is_iterable(input_shapes, exclude_str=True):
1✔
1750
                assert len(input_shapes) == len(variable_from_input_shapes)
1✔
1751

1752
                if variable.ndim == 0:
1!
NEW
1753
                    raise conflict_error(
×
1754
                        'input_shapes gives a list of items but default_variable is 0d'
1755
                    )
1756
                elif len(input_shapes) != len(variable):
1✔
1757
                    raise conflict_error(
1✔
1758
                        f'len(input_shapes) is {len(input_shapes)};'
1759
                        f' len(default_variable) is {len(variable)}'
1760
                    )
1761
                else:
1762
                    for i in range(len(input_shapes)):
1✔
1763
                        if variable_from_input_shapes[i].shape != variable[i].shape:
1✔
1764
                            raise conflict_error(
1✔
1765
                                f'input_shapes[{i}].shape: {variable_from_input_shapes[i].shape};'
1766
                                f' default_variable[{i}].shape: {variable[i].shape}'
1767
                            )
1768
            else:
1769
                if variable_from_input_shapes.shape != variable.shape:
1✔
1770
                    raise conflict_error(
1✔
1771
                        f'input_shapes.shape: {variable_from_input_shapes.shape};'
1772
                        f' default_variable.shape: {variable.shape}'
1773
                    )
1774

1775
        # if variable_from_input_shapes is created an error has not been thrown
1776
        # so far, variable is equal
1777
        return variable
1✔
1778

1779
    def _get_allowed_arguments(self) -> set:
1✔
1780
        """
1781
        Returns a set of argument names that can be passed into
1782
        __init__, directly or through params dictionaries
1783

1784
        Includes:
1785
            - all Parameter constructor_argument names
1786
            - all Parameter names except for those that have a
1787
              constructor_argument
1788
            - all ParameterAlias names
1789
            - all other explicitly specified named arguments in __init__
1790
        """
1791
        allowed_kwargs = self.standard_constructor_args.union(
1✔
1792
            get_all_explicit_arguments(self.__class__, '__init__')
1793
        )
1794
        for p in self.parameters:
1✔
1795
            # restrict to constructor argument, if both are desired, use alias
1796
            if p.constructor_argument is not None and p.constructor_argument != p.name:
1✔
1797
                allowed_kwargs.add(p.constructor_argument)
1✔
1798
                try:
1✔
1799
                    allowed_kwargs.remove(p.name)
1✔
1800
                except KeyError:
1✔
1801
                    pass
1✔
1802
            else:
1803
                allowed_kwargs.add(p.name)
1✔
1804

1805
            if p.aliases is not None:
1✔
1806
                if isinstance(p.aliases, str):
1!
1807
                    allowed_kwargs.add(p.aliases)
×
1808
                else:
1809
                    allowed_kwargs = allowed_kwargs.union(p.aliases)
1✔
1810

1811
        return allowed_kwargs
1✔
1812

1813
    def _get_illegal_arguments(self, **kwargs) -> set:
1✔
1814
        allowed_kwargs = self._get_allowed_arguments()
1✔
1815
        return {
1✔
1816
            k for k in kwargs
1817
            if k not in allowed_kwargs and k in self._user_specified_args
1818
        }
1819

1820
    # breaking self convention here because when storing the args,
1821
    # "self" is often among them. To avoid needing to preprocess to
1822
    # avoid argument duplication, use "self_" in this method signature
1823
    def _store_deferred_init_args(self_, **kwargs):
1✔
1824
        self = self_
1✔
1825

1826
        try:
1✔
1827
            del kwargs['self']
1✔
1828
        except KeyError:
×
1829
            pass
×
1830

1831
        # add unspecified kwargs
1832
        kwargs_names = [
1✔
1833
            k
1834
            for k, v in inspect.signature(self.__init__).parameters.items()
1835
            if v.kind is inspect.Parameter.VAR_KEYWORD
1836
        ]
1837

1838
        self._init_args = {
1✔
1839
            k: v
1840
            for k, v in kwargs.items()
1841
            if (
1842
                k in get_all_explicit_arguments(self.__class__, '__init__')
1843
                or k in kwargs_names
1844
            )
1845
        }
1846
        try:
1✔
1847
            self._init_args.update(self._init_args['kwargs'])
1✔
1848
            del self._init_args['kwargs']
1✔
1849
        except KeyError:
×
1850
            pass
×
1851

1852
    def _deferred_init(self, **kwargs):
1✔
1853
        """Use in subclasses that require deferred initialization
1854
        """
1855
        if self.initialization_status == ContextFlags.DEFERRED_INIT:
1!
1856

1857
            # Flag that object is now being initialized
1858
            #       (usually in _instantiate_function)
1859
            self.initialization_status = ContextFlags.INITIALIZING
1✔
1860

1861
            self._init_args.update(kwargs)
1✔
1862

1863
            # Complete initialization
1864
            super(self.__class__,self).__init__(**self._init_args)
1✔
1865

1866
            # If name was assigned, "[DEFERRED INITIALIZATION]" was appended to it, so remove it
1867
            if DEFERRED_INITIALIZATION in self.name:
1!
1868
                self.name = self.name.replace("[" + DEFERRED_INITIALIZATION + "]", "")
×
1869
            # Otherwise, allow class to replace std default name with class-specific one if it has a method for doing so
1870
            else:
1871
                self._assign_default_name()
1✔
1872

1873
            del self._init_args
1✔
1874

1875
    def _assign_deferred_init_name(self, name):
1✔
1876

1877
        name = "{} [{}]".format(name, DEFERRED_INITIALIZATION) if name \
1✔
1878
          else "{}{} {}".format(_get_auto_name_prefix(), DEFERRED_INITIALIZATION, self.__class__.__name__)
1879

1880
        # Register with ProjectionRegistry or create one
1881
        register_category(entry=self,
1✔
1882
                          base_class=Component,
1883
                          name=name,
1884
                          registry=DeferredInitRegistry,
1885
                          )
1886

1887
    def _assign_default_name(self, **kwargs):
1✔
1888
        return
1✔
1889

1890
    def _set_parameter_value(self, param, val, context=None):
1✔
1891
        param = getattr(self.parameters, param)
1✔
1892
        param._set(val, context)
1✔
1893
        if hasattr(self, "parameter_ports"):
1✔
1894
            if param in self.parameter_ports:
1✔
1895
                new_port_value = self.parameter_ports[param].execute(context=context)
1✔
1896
                self.parameter_ports[param].parameters.value._set(new_port_value, context)
1✔
1897
        elif hasattr(self, "owner"):
1!
1898
            if hasattr(self.owner, "parameter_ports"):
1✔
1899
                # skip Components, assume they are to be run to provide the
1900
                # value instead of given as a variable to a parameter port
1901
                if param in self.owner.parameter_ports:
1✔
1902
                    try:
1✔
1903
                        if any([isinstance(v, Component) for v in val]):
1!
1904
                            return
×
1905
                    except TypeError:
1✔
1906
                        if isinstance(val, Component):
1!
1907
                            return
×
1908

1909
                    new_port_value = self.owner.parameter_ports[param].execute(context=context)
1✔
1910
                    self.owner.parameter_ports[param].parameters.value._set(new_port_value, context)
1✔
1911

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

1915
        Called by functions to validate variable and params
1916
        Validation can be suppressed by turning parameter_validation attribute off
1917
        target_set is a params dictionary to which params should be assigned;
1918

1919
        Does the following:
1920
        - instantiate variable (if missing or callable)
1921
        - validate variable if PARAM_VALIDATION is set
1922
        - resets leftover runtime params back to original values (only if execute method was called directly)
1923
        - sets runtime params
1924
        - validate params if PARAM_VALIDATION is set
1925

1926
        :param variable: (anything but a dict) - variable to validate
1927
        :param params: (dict) - params to validate
1928
        :target_set: (dict) - set to which params should be assigned
1929
        :return:
1930
        """
1931
        # VARIABLE ------------------------------------------------------------
1932

1933
        # If function is called without any arguments, get default for variable
1934
        if variable is None:
1✔
1935
            variable = self.defaults.variable
1✔
1936

1937
            variable = copy_parameter_value(variable)
1✔
1938

1939
        # If the variable is a function, call it
1940
        if callable(variable):
1!
1941
            variable = variable()
×
1942

1943
        # Validate variable if parameter_validation is set and the function was called with a variable
1944
        if self.prefs.paramValidationPref and variable is not None:
1!
1945
            variable = self._validate_variable(variable, context=context)
1✔
1946

1947
        # PARAMS ------------------------------------------------------------
1948

1949
        # If params have been passed, treat as runtime params
1950
        self._validate_and_assign_runtime_params(params, context)
1✔
1951

1952
        self.parameters.variable._set(variable, context=context)
1✔
1953
        return variable
1✔
1954

1955
    def _validate_and_assign_runtime_params(self, runtime_params, context):
1✔
1956
        """Validate runtime_params, cache for reset, and assign values
1957

1958
        Check that all params belong either to Component or its function (raise error if any are found that don't)
1959
        Cache params to reset in _runtime_params_reset
1960
        """
1961

1962
        # # MODIFIED 5/8/20 OLD:
1963
        # # reset any runtime params that were leftover from a direct call to .execute (atypical)
1964
        # if context.execution_id in self._runtime_params_reset:
1965
        #     for key in self._runtime_params_reset[context.execution_id]:
1966
        #         self._set_parameter_value(key, self._runtime_params_reset[context.execution_id][key], context)
1967
        # self._runtime_params_reset[context.execution_id] = {}
1968
        # MODIFIED 5/8/20 END
1969

1970
        from psyneulink.core.components.functions.function import is_function_type, FunctionError
1✔
1971
        def generate_error(param_name):
1✔
1972
            owner_name = ""
1✔
1973
            if hasattr(self, OWNER) and self.owner:
1✔
1974
                owner_name = f" of {self.owner.name}"
1✔
1975
                if hasattr(self.owner, OWNER) and self.owner.owner:
1!
1976
                    owner_name = f"{owner_name} of {self.owner.owner.name}"
×
1977
            err_msg=f"Invalid specification in runtime_params arg for {self.name}{owner_name}: '{param_name}'."
1✔
1978
            if is_function_type(self):
1✔
1979
                raise FunctionError(err_msg)
1980
            else:
1981
                raise ComponentError(err_msg)
1982

1983
        if isinstance(runtime_params, dict):
1✔
1984
            runtime_params = copy_parameter_value(runtime_params)
1✔
1985
            for param_name in runtime_params:
1✔
1986
                if not isinstance(param_name, str):
1!
1987
                    generate_error(param_name)
×
1988
                elif param_name in self.parameters:
1✔
1989
                    if param_name in {FUNCTION, INPUT_PORTS, OUTPUT_PORTS}:
1!
1990
                        generate_error(param_name)
×
1991
                    if context.execution_id not in self._runtime_params_reset:
1✔
1992
                        self._runtime_params_reset[context.execution_id] = {}
1✔
1993
                    self._runtime_params_reset[context.execution_id][param_name] = copy_parameter_value(
1✔
1994
                        getattr(self.parameters, param_name)._get(context)
1995
                    )
1996
                    if is_numeric(runtime_params[param_name]):
1✔
1997
                        runtime_value = convert_all_elements_to_np_array(runtime_params[param_name])
1✔
1998
                    else:
1999
                        runtime_value = runtime_params[param_name]
1✔
2000

2001
                    self._set_parameter_value(param_name, runtime_value, context)
1✔
2002
                # Any remaining params should either belong to the Component's function
2003
                #    or, if the Component is a Function, to it or its owner
2004
                elif ( # If Component is not a function, and its function doesn't have the parameter or
1✔
2005
                        (not is_function_type(self) and param_name not in self.function.parameters)
2006
                       # the Component is a standalone function:
2007
                       or (is_function_type(self) and not self.owner)):
2008
                    generate_error(param_name)
1✔
2009

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

2013
    @handle_external_context()
1✔
2014
    def _instantiate_defaults(self,
1✔
2015
                        variable=None,
2016
                        request_set=None,
2017
                        assign_missing=True,
2018
                        target_set=None,
2019
                        default_set=None,
2020
                        context=None
2021
                        ):
2022
        """Validate variable and/or param defaults in requested set and assign values to params in target set
2023

2024
          Variable can be any type other than a dictionary (reserved for use as params)
2025
          request_set must contain a dict of params to be assigned to target_set
2026
          If assign_missing option is set, then any params defined for the class
2027
              but not included in the requested set are assigned values from the default_set;
2028
              if request_set is None, then all values in the target_set are assigned from the default_set
2029
          Class defaults can not be passed as target_set
2030
              IMPLEMENTATION NOTE:  for now, treating class defaults as hard coded;
2031
                                    could be changed in the future simply by commenting out code below
2032

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

2036
        :param variable: (anything but a dict (variable) - value to assign as defaults.variable
2037
        :param request_set: (dict) - params to be assigned
2038
        :param assign_missing: (bool) - controls whether missing params are set to default_set values (default: False)
2039
        :param target_set: (dict) - param set to which assignments should be made
2040
        :param default_set: (dict) - values used for params missing from request_set (only if assign_missing is True)
2041
        :return:
2042
        """
2043

2044
        # Make sure all args are legal
2045
        if variable is not None:
1!
2046
            if isinstance(variable,dict):
1✔
2047
                raise ComponentError("Dictionary passed as variable; probably trying to use param set as 1st argument")
2048
        if request_set:
1✔
2049
            if not isinstance(request_set, dict):
1✔
2050
                raise ComponentError("requested parameter set must be a dictionary")
2051
        if target_set:
1✔
2052
            if not isinstance(target_set, dict):
1✔
2053
                raise ComponentError("target parameter set must be a dictionary")
2054
        if default_set:
1✔
2055
            if not isinstance(default_set, dict):
1✔
2056
                raise ComponentError("default parameter set must be a dictionary")
2057

2058

2059
        # FIX: 6/3/19 [JDC] SHOULD DEAL WITH THIS AND SHAPE BELOW
2060
        # # GET VARIABLE FROM PARAM DICT IF SPECIFIED
2061
        # #    (give precedence to that over variable arg specification)
2062
        # if VARIABLE in request_set and request_set[VARIABLE] is not None:
2063
        #     variable = request_set[VARIABLE]
2064

2065
        # ASSIGN SHAPE TO VARIABLE if specified
2066

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

2072
            # Both variable and shape are specified
2073
            if variable is not None:
×
2074
                # If they conflict, raise exception, otherwise use variable (it specifies both shape and content)
2075
                if self.shape != np.array(variable).shape:
×
2076
                    raise ComponentError(
2077
                        "The shape arg of {} ({}) conflicts with the shape of its variable arg ({})".
2078
                        format(self.name, self.shape, np.array(variable).shape))
2079
            # Variable is not specified, so set to array of zeros with specified shape
2080
            else:
2081
                variable = np.zeros(self.shape)
×
2082

2083
        # VALIDATE VARIABLE
2084

2085
        if context.source is not ContextFlags.COMMAND_LINE:
1!
2086
            # if variable has been passed then validate and, if OK, assign as self.defaults.variable
2087
            variable = self._validate_variable(variable, context=context)
1✔
2088

2089
        # If no params were passed, then done
2090
        if request_set is None and target_set is None and default_set is None:
1✔
2091
            return
1✔
2092

2093
        # VALIDATE PARAMS
2094

2095
        # if request_set has been passed or created then validate and, if OK, assign params to target_set
2096
        if request_set:
1!
2097
            try:
1✔
2098
                self._validate_params(variable=variable,
1✔
2099
                                      request_set=request_set,
2100
                                      target_set=target_set,
2101
                                      context=context)
2102
            # variable not implemented by Mechanism subclass, so validate without it
2103
            except TypeError:
1✔
2104
                self._validate_params(request_set=request_set,
1✔
2105
                                      target_set=target_set,
2106
                                      context=context)
2107

2108
    def _validate_arguments(self, parameter_values):
1✔
2109
        """
2110
        Raises errors for illegal specifications of arguments to
2111
        __init__:
2112
            - original Parameter name when Parameter has a
2113
              constructor_argument
2114
            - arguments that don't correspond to Parameters or other
2115
              arguments to __init__
2116
            - non-equal values of a Parameter and a corresponding
2117
              ParameterAlias
2118
        """
2119
        def create_illegal_argument_error(illegal_arg_strs):
1✔
2120
            plural = 's' if len(illegal_arg_strs) > 1 else ''
1✔
2121
            base_err = f"Illegal argument{plural} in constructor (type: {type(self).__name__}):"
1✔
2122
            return ComponentError('\n\t'.join([base_err] + illegal_arg_strs), component=self)
1✔
2123

2124
        def alias_conflicts(alias, passed_name):
1✔
2125
            # some aliases share name with constructor_argument
2126
            if alias.name == passed_name:
1✔
2127
                return False
1✔
2128

2129
            try:
1✔
2130
                a_val = parameter_values[alias.name]
1✔
2131
                s_val = parameter_values[passed_name]
1✔
2132
            except KeyError:
1✔
2133
                return False
1✔
2134

2135
            if safe_equals(a_val, s_val):
1✔
2136
                return False
1✔
2137

2138
            # both specified, not equal -> conflict
2139
            alias_specified = (
1✔
2140
                alias.name in self._user_specified_args
2141
                and (a_val is not None or alias.specify_none)
2142
            )
2143
            source_specified = (
1✔
2144
                passed_name in self._user_specified_args
2145
                and (s_val is not None or alias.source.specify_none)
2146
            )
2147
            return alias_specified and source_specified
1✔
2148

2149
        illegal_passed_args = self._get_illegal_arguments(**parameter_values)
1✔
2150

2151
        conflicting_aliases = []
1✔
2152
        unused_constructor_args = {}
1✔
2153
        for p in self.parameters:
1✔
2154
            if p.name in illegal_passed_args:
1✔
2155
                assert p.constructor_argument is not None
1✔
2156
                unused_constructor_args[p.name] = p.constructor_argument
1✔
2157

2158
            if isinstance(p, ParameterAlias):
1✔
2159
                if p.source.constructor_argument is None:
1✔
2160
                    passed_name = p.source.name
1✔
2161
                else:
2162
                    passed_name = p.source.constructor_argument
1✔
2163

2164
                if alias_conflicts(p, passed_name):
1✔
2165
                    conflicting_aliases.append((p.source.name, passed_name, p.name))
1✔
2166

2167
        # raise constructor arg errors
2168
        if len(unused_constructor_args) > 0:
1✔
2169
            raise create_illegal_argument_error([
1✔
2170
                f"'{arg}': must use '{constr_arg}' instead"
2171
                for arg, constr_arg in unused_constructor_args.items()
2172
            ])
2173

2174
        # raise generic illegal argument error
2175
        unknown_args = illegal_passed_args.difference(unused_constructor_args)
1✔
2176
        if len(unknown_args) > 0:
1✔
2177
            raise create_illegal_argument_error([f"'{a}'" for a in unknown_args])
1✔
2178

2179
        # raise alias conflict error
2180
        # can standardize these with above error, but leaving for now for consistency
2181
        if len(conflicting_aliases) > 0:
1✔
2182
            source, passed, alias = conflicting_aliases[0]
1✔
2183
            constr_arg_str = f' ({source})' if source != passed else ''
1✔
2184
            raise ComponentError(
2185
                f"Multiple values ({alias}: {parameter_values[alias]}"
2186
                f"\t{passed}: {parameter_values[passed]}) "
2187
                f"assigned to identical Parameters. {alias} is an alias "
2188
                f"of {passed}{constr_arg_str}",
2189
                component=self,
2190
            )
2191

2192
    def _parse_arguments(
1✔
2193
        self, default_variable, param_defaults, input_shapes, function, function_params, kwargs
2194
    ):
2195
        if function_params is None:
1✔
2196
            function_params = {}
1✔
2197

2198
        # allow override of standard arguments with arguments specified
2199
        # in params (here, param_defaults) argument
2200
        # (if there are duplicates, later lines override previous)
2201
        # add named arguments here so that values from param_defaults
2202
        # can override them.
2203
        parameter_values = {
1✔
2204
            **{
2205
                'function': function,
2206
                'input_shapes': input_shapes,
2207
                'default_variable': default_variable,
2208
                'function_params': function_params
2209
            },
2210
            **kwargs,
2211
            **(param_defaults if param_defaults is not None else {}),
2212
        }
2213
        function_params = parameter_values['function_params']
1✔
2214

2215
        # if function is a standard python function or string, assume
2216
        # any unknown kwargs are for the corresponding UDF.
2217
        # Validation will happen there.
2218
        if isinstance(function, (types.FunctionType, str)):
1✔
2219
            function_params = {
1✔
2220
                **kwargs,
2221
                **function_params
2222
            }
2223
        else:
2224
            self._validate_arguments(parameter_values)
1✔
2225

2226
        # self.parameters here still references <class>.parameters, but
2227
        # only unchanging attributes are needed here
2228
        for p in self.parameters:
1✔
2229
            if p.constructor_argument is not None and p.constructor_argument != p.name:
1✔
2230
                try:
1✔
2231
                    parameter_values[p.name] = parameter_values[p.constructor_argument]
1✔
2232
                except KeyError:
1✔
2233
                    pass
1✔
2234
                else:
2235
                    # the value itself isn't used elsewhere
2236
                    self._user_specified_args[p.name] = f'FROM_{p.constructor_argument}'
1✔
2237

2238
            if isinstance(p, ParameterAlias):
1✔
2239
                if p.source.name not in self._user_specified_args:
1✔
2240
                    # if alias conflicts with source, error thrown in _validate_arguments
2241
                    try:
1✔
2242
                        parameter_values[p.source.name] = parameter_values[p.name]
1✔
2243
                    except KeyError:
1✔
2244
                        pass
1✔
2245
                    else:
2246
                        self._user_specified_args[p.source.name] = f'FROM_{p.name}'
1✔
2247

2248
        return parameter_values, function_params
1✔
2249

2250
    def _initialize_parameters(self, context=None, **param_defaults):
1✔
2251
        """
2252
        Args:
2253
            **param_defaults: maps Parameter names to their default
2254
            values. Sets instance-level Parameters dynamically for any
2255
            name that maps to a Parameter object.
2256
        """
2257
        from psyneulink.core.components.shellclasses import (
1✔
2258
            Composition_Base, Function, Mechanism, Port, Process_Base,
2259
            Projection, System_Base
2260
        )
2261

2262
        # excludes Function
2263
        shared_types = (
1✔
2264
            Mechanism,
2265
            Port,
2266
            Projection,
2267
            System_Base,
2268
            Process_Base,
2269
            Composition_Base,
2270
            ComponentsMeta,
2271
            types.MethodType,
2272
            types.ModuleType,
2273
            functools.partial,
2274
        )
2275
        alias_names = {p.name for p in self.class_parameters if isinstance(p, ParameterAlias)}
1✔
2276

2277
        self.parameters = self.Parameters(owner=self, parent=self.class_parameters)
1✔
2278

2279
        # assign defaults based on pass in params and class defaults
2280
        defaults = {
1✔
2281
            k: v for (k, v) in self.class_defaults.values(show_all=True).items()
2282
            if k not in alias_names
2283
        }
2284

2285
        if param_defaults is not None:
1!
2286
            for name, value in copy.copy(param_defaults).items():
1✔
2287
                if name in alias_names:
1✔
2288
                    continue
1✔
2289

2290
                if isinstance(value, Parameter):
1✔
2291
                    setattr(self.parameters, name, value)
1✔
2292
                    try:
1✔
2293
                        value = copy.copy(value.default_value)
1✔
2294
                    except TypeError:
1✔
2295
                        value = value.default_value
1✔
2296
                    param_defaults[name] = value
1✔
2297

2298
                if name in self.parameters._params:
1!
2299
                    parameter_obj = getattr(self.parameters, name)
1✔
2300
                else:
2301
                    # name in param_defaults does not correspond to a Parameter
UNCOV
2302
                    continue
×
2303

2304
                if (
1✔
2305
                    name not in self._user_specified_args
2306
                    and parameter_obj.constructor_argument not in self._user_specified_args
2307
                ):
2308
                    continue
1✔
2309
                elif value is not None or parameter_obj.specify_none:
1✔
2310
                    parameter_obj._user_specified = True
1✔
2311

2312
                if parameter_obj.structural:
1✔
2313
                    parameter_obj.spec = value
1✔
2314

2315
                if parameter_obj.modulable:
1✔
2316
                    # later, validate this
2317
                    modulable_param_parser = self.parameters._get_parse_method('modulable')
1✔
2318
                    if modulable_param_parser is not None:
1!
2319
                        parsed = modulable_param_parser(name, value)
1✔
2320

2321
                        if parsed is not value:
1✔
2322
                            # we have a modulable param spec
2323
                            parameter_obj.spec = value
1✔
2324
                            value = parsed
1✔
2325
                            param_defaults[name] = parsed
1✔
2326

2327
                if value is not None or parameter_obj.specify_none:
1✔
2328
                    defaults[name] = value
1✔
2329

2330
        self.defaults = Defaults(owner=self)
1✔
2331
        for k in sorted(defaults, key=self.parameters._dependency_order_key(names=True)):
1✔
2332
            if defaults[k] is not None:
1✔
2333
                defaults[k] = copy_parameter_value(
1✔
2334
                    defaults[k],
2335
                    shared_types=shared_types
2336
                )
2337
            parameter_obj = getattr(self.parameters, k)
1✔
2338
            parameter_obj._set_default_value(defaults[k], check_scalar=parameter_obj._user_specified)
1✔
2339

2340
        for p in filter(lambda x: not isinstance(x, (ParameterAlias, SharedParameter)), self.parameters._in_dependency_order):
1✔
2341
            # copy spec so it is not overwritten later
2342
            # TODO: check if this is necessary
2343
            p.spec = copy_parameter_value(p.spec, shared_types=shared_types)
1✔
2344

2345
            # set default to None context to ensure it exists
2346
            if (
1✔
2347
                p._get(context) is None and p.getter is None
2348
                or context.execution_id not in p.values
2349
            ):
2350
                if p._user_specified:
1✔
2351
                    val = param_defaults[p.name]
1✔
2352

2353
                    # ideally, this would include deepcopying any
2354
                    # Function objects with a non-None owner in val.
2355
                    # Avoiding universal deep copy for iterables
2356
                    # containing Functions here ensures that a list (ex.
2357
                    # noise) containing other objects and a Function
2358
                    # will use the actual Function passed in and not a
2359
                    # copy. Not copying - as was done prior to this
2360
                    # comment - should only be a problem if internal
2361
                    # code passes such an object that is also used
2362
                    # elsewhere
2363
                    if isinstance(val, Function):
1✔
2364
                        if val.owner is not None:
1✔
2365
                            val = copy.deepcopy(val)
1✔
2366
                    elif not contains_type(val, Function):
1✔
2367
                        val = copy_parameter_value(val, shared_types=shared_types)
1✔
2368
                else:
2369
                    val = copy_parameter_value(
1✔
2370
                        p.default_value,
2371
                        shared_types=shared_types
2372
                    )
2373

2374
                if isinstance(val, Function):
1✔
2375
                    val.owner = self
1✔
2376

2377
                val = p._parse(val)
1✔
2378
                p._validate(val)
1✔
2379
                p._set(val, context=context, skip_history=True, override=True)
1✔
2380

2381
            if isinstance(p.default_value, Function):
1✔
2382
                p.default_value.owner = p
1✔
2383

2384
        for p in self.parameters:
1✔
2385
            if p.stateful:
1✔
2386
                setattr(self, _get_parametervalue_attr(p), ParameterValue(self, p))
1✔
2387

2388
    def _get_parsed_variable(self, parameter, variable=NotImplemented, context=None):
1✔
2389
        if variable is NotImplemented:
1✔
2390
            variable = copy.deepcopy(self.defaults.variable)
1✔
2391

2392
        try:
1✔
2393
            parameter = getattr(self.parameters, parameter)
1✔
2394
        except TypeError:
1✔
2395
            pass
1✔
2396

2397
        try:
1✔
2398
            parse_variable_method = getattr(
1✔
2399
                self,
2400
                f'_parse_{parameter.name}_variable'
2401
            )
2402
            return copy.deepcopy(
1✔
2403
                call_with_pruned_args(parse_variable_method, variable, context=context)
2404
            )
2405
        except AttributeError:
1✔
2406
            # no parsing method, assume same shape as owner
2407
            return variable
1✔
2408

2409
    def _instantiate_parameter_classes(self, context=None):
1✔
2410
        """
2411
            An optional method that will take any Parameter values in
2412
            **context** that are classes/types, and instantiate them.
2413
        """
2414
        from psyneulink.core.components.shellclasses import Function
1✔
2415

2416
        # (this originally occurred in _validate_params)
2417
        for p in self.parameters._in_dependency_order:
1✔
2418
            if p.getter is None:
1✔
2419
                val = p._get(context)
1✔
2420
                if (
1✔
2421
                    p.name != FUNCTION
2422
                    and is_instance_or_subclass(val, Function)
2423
                    and not p.reference
2424
                    and not isinstance(p, SharedParameter)
2425
                ):
2426
                    function_default_variable = self._get_parsed_variable(p, context=context)
1✔
2427

2428
                    if (
1✔
2429
                        inspect.isclass(val)
2430
                        and issubclass(val, Function)
2431
                    ):
2432
                        # instantiate class val with all relevant shared parameters
2433
                        # some shared parameters may not be arguments (e.g.
2434
                        # transfer_fct additive_param when function is Identity)
2435
                        # NOTE: this may cause an issue if there is an
2436
                        # incompatibility between a shared parameter and
2437
                        # the default variable, by forcing variable to
2438
                        # be _user_specified, where instead the variable
2439
                        # would be coerced to match
2440
                        val = call_with_pruned_args(
1✔
2441
                            val,
2442
                            default_variable=function_default_variable,
2443
                            **self.initial_shared_parameters[p.name]
2444
                        )
2445

2446
                        val.owner = self
1✔
2447
                        p._set(val, context)
1✔
2448

2449
                        for sub_param_name in itertools.chain(self.initial_shared_parameters[p.name], ['variable']):
1✔
2450
                            try:
1✔
2451
                                sub_param = getattr(val.parameters, sub_param_name)
1✔
2452
                            except AttributeError:
×
2453
                                # TransferWithCosts has SharedParameters
2454
                                # referencing transfer_fct's
2455
                                # additive_param or
2456
                                # multiplicative_param, but Identity
2457
                                # does not have them
2458
                                continue
×
2459

2460
                            try:
1✔
2461
                                orig_param_name = [x.name for x in self.parameters if isinstance(x, SharedParameter) and x.source is sub_param][0]
1✔
2462
                            except IndexError:
1✔
2463
                                orig_param_name = sub_param_name
1✔
2464
                            sub_param._user_specified = getattr(self.parameters, orig_param_name)._user_specified
1✔
2465

2466
                    elif isinstance(val, Function):
1!
2467
                        incompatible = False
1✔
2468
                        if function_default_variable.shape != val.defaults.variable.shape:
1✔
2469
                            incompatible = True
1✔
2470
                            if val._variable_shape_flexibility is DefaultsFlexibility.INCREASE_DIMENSION:
1✔
2471
                                increased_dim = np.asarray([val.defaults.variable])
1✔
2472

2473
                                if increased_dim.shape == function_default_variable.shape:
1✔
2474
                                    function_default_variable = increased_dim
1✔
2475
                                    incompatible = False
1✔
2476
                            elif val._variable_shape_flexibility is DefaultsFlexibility.FLEXIBLE:
1!
2477
                                incompatible = False
1✔
2478

2479
                        if incompatible:
1✔
2480
                            def _create_justified_line(k, v, error_line_len=110):
1✔
2481
                                return f'{k}: {v.rjust(error_line_len - len(k))}'
1✔
2482

2483
                            raise ParameterError(
2484
                                f'Variable shape incompatibility between {self} and its {p.name} Parameter'
2485
                                + _create_justified_line(
2486
                                    f'\n{self}.variable',
2487
                                    f'{function_default_variable}    (numpy.array shape: {np.asarray(function_default_variable).shape})'
2488
                                )
2489
                                + _create_justified_line(
2490
                                    f'\n{self}.{p.name}.variable',
2491
                                    f'{val.defaults.variable}    (numpy.array shape: {np.asarray(val.defaults.variable).shape})'
2492
                                )
2493
                            )
2494
                        val._update_default_variable(
1✔
2495
                            function_default_variable,
2496
                            context
2497
                        )
2498

2499
                        if isinstance(p.default_value, Function):
1!
2500
                            p.default_value._update_default_variable(
1✔
2501
                                function_default_variable,
2502
                                context
2503
                            )
2504

2505
        self._override_unspecified_shared_parameters(context)
1✔
2506

2507
    def _override_unspecified_shared_parameters(self, context):
1✔
2508
        for param in self.parameters._in_dependency_order:
1✔
2509
            if (
1✔
2510
                isinstance(param, SharedParameter)
2511
                and not isinstance(param.source, ParameterAlias)
2512
            ):
2513
                try:
1✔
2514
                    obj = getattr(self.parameters, param.attribute_name)
1✔
2515
                    shared_objs = [obj.default_value, obj._get(context)]
1✔
2516
                except AttributeError:
1✔
2517
                    obj = getattr(self, param.attribute_name)
1✔
2518
                    shared_objs = [obj]
1✔
2519

2520
                for c in shared_objs:
1✔
2521
                    if isinstance(c, Component):
1✔
2522
                        try:
1✔
2523
                            shared_obj_param = getattr(c.parameters, param.shared_parameter_name)
1✔
2524
                        except AttributeError:
1✔
2525
                            continue
1✔
2526

2527
                        if not shared_obj_param._user_specified:
1✔
2528
                            if (
1✔
2529
                                param.primary
2530
                                and param.default_value is not None
2531
                            ):
2532
                                shared_obj_param.default_value = copy.deepcopy(param.default_value)
1✔
2533
                                shared_obj_param._set(copy.deepcopy(param.default_value), context)
1✔
2534
                                shared_obj_param._user_specified = param._user_specified
1✔
2535
                        elif (
1✔
2536
                            param._user_specified
2537
                            and not safe_equals(param.default_value, shared_obj_param.default_value)
2538
                            # only show warning one time, for the non-default value if possible
2539
                            and c is shared_objs[-1]
2540
                        ):
2541
                            try:
1✔
2542
                                isp_arg = self.initial_shared_parameters[param.attribute_name][param.shared_parameter_name]
1✔
2543
                                # TODO: handle passed component but copied?
2544
                                throw_warning = (
1✔
2545
                                    # arg passed directly into shared_obj, no parsing
2546
                                    not safe_equals(shared_obj_param._get(context), isp_arg)
2547
                                    # arg passed but was parsed
2548
                                    and not safe_equals(shared_obj_param.spec, isp_arg)
2549
                                )
2550
                            except KeyError:
×
2551
                                throw_warning = True
×
2552

2553
                            if throw_warning:
1✔
2554
                                warnings.warn(
1✔
2555
                                    f'Specification of the "{param.name}" parameter ({param.default_value})'
2556
                                    f' for {self} conflicts with specification of its shared parameter'
2557
                                    f' "{shared_obj_param.name}" ({shared_obj_param.default_value}) for its'
2558
                                    f' {param.attribute_name} ({param.source._owner._owner}). The value'
2559
                                    f' specified on {param.source._owner._owner} will be used.'
2560
                                )
2561

2562

2563
    @handle_external_context()
1✔
2564
    def reset_params(self, mode=ResetMode.INSTANCE_TO_CLASS, context=None):
1✔
2565
        """Reset current and/or instance defaults
2566

2567
        If called with:
2568
            - CURRENT_TO_INSTANCE_DEFAULTS all current param settings are set to instance defaults
2569
            - INSTANCE_TO_CLASS all instance defaults are set to class defaults
2570
            - ALL_TO_CLASS_DEFAULTS all current and instance param settings are set to class defaults
2571

2572
        :param mode: (ResetMode) - determines which params are reset
2573
        :return none:
2574
        """
2575

2576
        if not isinstance(mode, ResetMode):
×
2577
            warnings.warn("No ResetMode specified for reset_params; CURRENT_TO_INSTANCE_DEFAULTS will be used")
×
2578

2579
        for param in self.parameters:
×
2580
            if mode == ResetMode.CURRENT_TO_INSTANCE_DEFAULTS:
×
2581
                param._set(
×
2582
                    copy_parameter_value(param.default_value),
2583
                    context=context,
2584
                    skip_history=True,
2585
                    skip_log=True,
2586
                )
2587
            elif mode == ResetMode.INSTANCE_TO_CLASS:
×
2588
                param.reset()
×
2589
            elif mode == ResetMode.ALL_TO_CLASS_DEFAULTS:
×
2590
                param.reset()
×
2591
                param._set(
×
2592
                    copy_parameter_value(param.default_value),
2593
                    context=context,
2594
                    skip_history=True,
2595
                    skip_log=True,
2596
                )
2597

2598
    def _initialize_from_context(self, context, base_context=Context(execution_id=None), override=True, visited=None):
1✔
2599
        if context.execution_id is base_context.execution_id:
1✔
2600
            return
1✔
2601

2602
        if visited is None:
1✔
2603
            visited = set()
1✔
2604

2605
        for comp in self._dependent_components:
1✔
2606
            if comp not in visited:
1✔
2607
                visited.add(comp)
1✔
2608
                comp._initialize_from_context(context, base_context, override, visited=visited)
1✔
2609

2610
        non_alias_params = [p for p in self.stateful_parameters if not isinstance(p, (ParameterAlias, SharedParameter))]
1✔
2611
        for param in non_alias_params:
1✔
2612
            if param.setter is None:
1✔
2613
                param._initialize_from_context(context, base_context, override)
1✔
2614

2615
        # attempt to initialize any params with setters (some params with setters may depend on the
2616
        # initialization of other params)
2617
        # this pushes the problem down one level so that if there are two such that they depend on each other,
2618
        # it will still fail. in this case, it is best to resolve the problem in the setter with a default
2619
        # initialization value
2620
        for param in non_alias_params:
1✔
2621
            if param.setter is not None:
1✔
2622
                param._initialize_from_context(context, base_context, override)
1✔
2623

2624
    def _delete_contexts(self, *contexts, check_simulation_storage=False, visited=None):
1✔
2625
        if visited is None:
1!
2626
            visited = set()
×
2627

2628
        for comp in self._dependent_components:
1✔
2629
            if comp not in visited:
1✔
2630
                visited.add(comp)
1✔
2631
                comp._delete_contexts(*contexts, check_simulation_storage=check_simulation_storage, visited=visited)
1✔
2632

2633
        for param in self.stateful_parameters:
1✔
2634
            if not check_simulation_storage or not param.retain_old_simulation_data:
1!
2635
                for context in contexts:
1✔
2636
                    param.delete(context)
1✔
2637

2638
    def _set_all_parameter_properties_recursively(self, visited=None, **kwargs):
1✔
2639
        if visited is None:
1✔
2640
            visited = set()
1✔
2641

2642
        # sets a property of all parameters for this component and all its dependent components
2643
        # used currently for disabling history, but setting logging could use this
2644
        for param_name in self.parameters.names():
1✔
2645
            parameter = getattr(self.parameters, param_name)
1✔
2646
            for (k, v) in kwargs.items():
1✔
2647
                try:
1✔
2648
                    setattr(parameter, k, v)
1✔
2649
                except ParameterError as e:
×
2650
                    logger.warning(str(e) + ' Parameter has not been modified.')
×
2651

2652
        for comp in self._dependent_components:
1✔
2653
            if comp not in visited:
1!
2654
                visited.add(comp)
1✔
2655
                comp._set_all_parameter_properties_recursively(
1✔
2656
                    visited=visited,
2657
                    **kwargs
2658
                )
2659

2660
    def _set_multiple_parameter_values(self, context, **kwargs):
1✔
2661
        """
2662
            Unnecessary, but can simplify multiple parameter assignments at once
2663
            For every kwarg k, v pair, will attempt to set self.parameters.<k> to v for context
2664
        """
2665
        for (k, v) in kwargs.items():
1✔
2666
            getattr(self.parameters, k)._set(v, context)
1✔
2667

2668
    # ------------------------------------------------------------------------------------------------------------------
2669
    # Parsing methods
2670
    # ------------------------------------------------------------------------------------------------------------------
2671
    # ---------------------------------------------------------
2672
    # Argument parsers
2673
    # ---------------------------------------------------------
2674

2675
    def _parse_arg_generic(self, arg_val):
1✔
2676
        """
2677
            Argument parser for any argument that does not have a specialized parser
2678
        """
2679
        return arg_val
×
2680

2681
    def _parse_arg_variable(self, variable):
1✔
2682
        """
2683
            Transforms **variable** into a form that Components expect. Used to allow
2684
            users to pass input in convenient forms, like a single float when a list
2685
            for input ports is expected
2686

2687
            Returns
2688
            -------
2689
            The transformed **input**
2690
        """
2691
        if variable is None:
1✔
2692
            return variable
1✔
2693

2694
        if not isinstance(variable, (list, np.ndarray)):
1✔
2695
            variable = np.atleast_1d(variable)
1✔
2696

2697
        return convert_all_elements_to_np_array(variable)
1✔
2698

2699
    # ---------------------------------------------------------
2700
    # Misc parsers
2701
    # ---------------------------------------------------------
2702

2703
    def _parse_function_variable(self, variable, context=None):
1✔
2704
        """
2705
            Parses the **variable** passed in to a Component into a function_variable that can be used with the
2706
            Function associated with this Component
2707
        """
2708
        return variable
1✔
2709

2710
    # ------------------------------------------------------------------------------------------------------------------
2711
    # Validation methods
2712
    # ------------------------------------------------------------------------------------------------------------------
2713

2714
    def _validate(self, context=None):
1✔
2715
        """
2716
            Eventually should contain all validation methods, occurs at end of Component.__init__
2717
        """
2718
        # 4/18/18 kmantel: below is a draft of what such a method should look like
2719
        # it's beyond the scope of the current changes however
2720

2721
        # # currently allows chance to validate anything in constructor defaults
2722
        # # when fleshed out, this should go over the new Parameters structure
2723
        # for param, _ in self.get_param_class_defaults().items():
2724
        #     try:
2725
        #         # automatically call methods of the form _validate_<param name> with the attribute
2726
        #         # as single argument. Sticking to this format can allow condensed and modular validation
2727
        #         getattr(self, '_validate_' + param)(getattr(self, param))
2728
        #     except AttributeError:
2729
        #         pass
2730
        self._validate_value()
1✔
2731

2732
    def _validate_variable(self, variable, context=None):
1✔
2733
        """Validate variable and return validated variable
2734

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

2737
        VARIABLE SPECIFICATION:                                        ENCODING:
2738
        Simple value variable:                                         0 -> [array([0])]
2739
        Single state array (vector) variable:                         [0, 1] -> [array([0, 1])]
2740
        Multiple port variables, each with a single value variable:  [[0], [0]] -> [array[0], array[0]]
2741

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

2747
        IMPLEMENTATION NOTES:
2748
           * future versions should add hierarchical/recursive content (e.g., range) checking
2749
           * add request/target pattern?? (as per _validate_params) and return validated variable?
2750

2751
        :param variable: (anything other than a dictionary) - variable to be validated:
2752
        :param context: (str)
2753
        :return variable: validated variable
2754
        """
2755

2756
        if inspect.isclass(variable):
1✔
2757
            raise ComponentError(f"Assignment of class ({variable.__name__}) "
2758
                                 f"as a variable (for {self.name}) is not allowed.")
2759

2760
        # If variable is not specified, then:
2761
        #    - assign to (??now np-converted version of) self.class_defaults.variable
2762
        #    - mark as not having been specified
2763
        #    - return
2764
        if variable is None:
1!
2765
            try:
×
NEW
2766
                variable = self.defaults.variable
×
2767
            except AttributeError:
×
NEW
2768
                variable = self.class_defaults.variable
×
NEW
2769
            return copy_parameter_value(variable)
×
2770

2771
        # Otherwise, do some checking on variable before converting to np.ndarray
2772

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

2785
        return variable
1✔
2786

2787
    def _validate_params(self, request_set, target_set=None, context=None):
1✔
2788
        """Validate params and assign validated values to targets,
2789

2790
        This performs top-level type validation of params
2791

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

2795
        IMPLEMENTATION NOTES:
2796
           * future versions should add recursive and content (e.g., range) checking
2797
           * should method return validated param set?
2798

2799
        :param dict (request_set) - set of params to be validated:
2800
        :param dict (target_set) - repository of params that have been validated:
2801
        :return none:
2802
        """
2803

2804
        for param_name, param_value in request_set.items():
1✔
2805
            # setattr(self, "_"+param_name, param_value)
2806

2807
            # Check that param is in self.defaults (if not, it is assumed to be invalid for this object)
2808
            if param_name not in self.defaults.names(show_all=True):
1!
UNCOV
2809
                continue
×
2810

2811
            # The default value of the param is None: suppress type checking
2812
            # IMPLEMENTATION NOTE: this can be used for params with multiple possible types,
2813
            #                      until type lists are implemented (see below)
2814
            if getattr(self.defaults, param_name) is None or getattr(self.defaults, param_name) is NotImplemented:
1✔
2815
                if self.prefs.verbosePref:
1!
2816
                    warnings.warn(f"{param_name} is specified as None for {self.name} which suppresses type checking.")
×
2817
                if target_set is not None:
1!
2818
                    target_set[param_name] = param_value
1✔
2819
                continue
1✔
2820

2821
            # If the value in self.defaults is a type, check if param value is an instance of it
2822
            if inspect.isclass(getattr(self.defaults, param_name)):
1✔
2823
                if isinstance(param_value, getattr(self.defaults, param_name)):
1!
2824
                    target_set[param_name] = param_value
×
2825
                    continue
×
2826
                # If the value is a Function class, allow any instance of Function class
2827
                from psyneulink.core.components.functions.function import Function_Base
1✔
2828
                if issubclass(getattr(self.defaults, param_name), Function_Base):
1✔
2829
                    # if isinstance(param_value, (function_type, Function_Base)):  <- would allow function of any kind
2830
                    if isinstance(param_value, Function_Base):
1!
2831
                        target_set[param_name] = param_value
×
2832
                        continue
×
2833

2834
            # If the value in self.defaults is an object, check if param value is the corresponding class
2835
            # This occurs if the item specified by the param has not yet been implemented (e.g., a function)
2836
            if inspect.isclass(param_value):
1✔
2837
                if isinstance(getattr(self.defaults, param_name), param_value):
1!
2838
                    continue
×
2839

2840
            # If the value is a projection, projection class, or a keyword for one, for anything other than
2841
            #    the FUNCTION param (which is not allowed to be specified as a projection)
2842
            #    then simply assign value (implication of not specifying it explicitly);
2843
            #    this also allows it to pass the test below and function execution to occur for initialization;
2844
            from psyneulink.core.components.shellclasses import Projection
1✔
2845
            if (((isinstance(param_value, str) and
1✔
2846
                          param_value in {CONTROL_PROJECTION, LEARNING_PROJECTION, LEARNING}) or
2847
                isinstance(param_value, Projection) or  # These should be just ControlProjection or LearningProjection
2848
                inspect.isclass(param_value) and issubclass(param_value,(Projection)))
2849
                and not param_name == FUNCTION):
2850
                param_value = getattr(self.defaults, param_name)
1✔
2851

2852
            # If self is a Function and param is a class ref for function, instantiate it as the function
2853
            from psyneulink.core.components.functions.function import Function_Base
1✔
2854
            if (isinstance(self, Function_Base) and
1✔
2855
                    inspect.isclass(param_value) and
2856
                    inspect.isclass(getattr(self.defaults, param_name))
2857
                    and issubclass(param_value, getattr(self.defaults, param_name))):
2858
                # Assign instance to target and move on
2859
                #  (compatiblity check no longer needed and can't handle function)
2860
                target_set[param_name] = param_value()
1✔
2861
                continue
1✔
2862

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

2868
                    # If assign_default_FUNCTION_PARAMS is False, it means that function's class is
2869
                    #     compatible but different from the one in defaults;
2870
                    #     therefore, FUNCTION_PARAMS will not match defaults;
2871
                    #     instead, check that functionParams are compatible with the function's default params
2872
                    if param_name == FUNCTION_PARAMS:
1!
2873
                        if not self.assign_default_FUNCTION_PARAMS:
×
2874
                            # Get function:
2875
                            try:
×
2876
                                function = request_set[FUNCTION]
×
2877
                            except KeyError:
×
2878
                                # If no function is specified, self.assign_default_FUNCTION_PARAMS should be True
2879
                                # (see _instantiate_defaults above)
2880
                                raise ComponentError("PROGRAM ERROR: No function params for {} so should be able to "
2881
                                                    "validate {}".format(self.name, FUNCTION_PARAMS))
2882
                            else:
2883
                                for entry_name, entry_value in param_value.items():
×
2884
                                    try:
×
2885
                                        getattr(function.defaults, entry_name)
×
2886
                                    except KeyError:
×
2887
                                        raise ComponentError("{0} is not a valid entry in {1} for {2} ".
2888
                                                            format(entry_name, param_name, self.name))
2889
                                    # add [entry_name] entry to [param_name] dict
2890
                                    else:
2891
                                        try:
×
2892
                                            target_set[param_name][entry_name] = entry_value
×
2893
                                        # [param_name] dict not yet created, so create it
2894
                                        except KeyError:
×
2895
                                            target_set[param_name] = {}
×
2896
                                            target_set[param_name][entry_name] = entry_value
×
2897
                                        # target_set None
2898
                                        except TypeError:
×
2899
                                            pass
×
2900
                        else:
2901
                            # if param_name != FUNCTION_PARAMS:
2902
                            #     assert True
2903
                            for entry_name, entry_value in param_value.items():
×
2904
                                # Make sure [entry_name] is in self.defaults
2905
                                try:
×
2906
                                    getattr(self.defaults, param_name)[entry_name]
×
2907
                                except KeyError:
×
2908
                                    raise ComponentError("{0} is not a valid entry in {1} for {2} ".
2909
                                                        format(entry_name, param_name, self.name))
2910
                                # TBI: (see above)
2911
                                # if not iscompatible(entry_value,
2912
                                #                     getattr(self.defaults, param_name)[entry_name],
2913
                                #                     **{kwCompatibilityLength:0}):
2914
                                #     raise ComponentError("{0} ({1}) in {2} of {3} must be a {4}".
2915
                                #         format(entry_name, entry_value, param_name, self.name,
2916
                                #                type(getattr(self.defaults, param_name)[entry_name]).__name__))
2917
                                else:
2918
                                    # add [entry_name] entry to [param_name] dict
2919
                                    try:
×
2920
                                        target_set[param_name][entry_name] = entry_value
×
2921
                                    # [param_name] dict not yet created, so create it
2922
                                    except KeyError:
×
2923
                                        target_set[param_name] = {}
×
2924
                                        target_set[param_name][entry_name] = entry_value
×
2925
                                    # target_set None
2926
                                    except TypeError:
×
2927
                                        pass
×
2928

2929
                elif target_set is not None:
1!
2930
                    # Copy any iterables so that deletions can be made to assignments belonging to the instance
2931
                    if not isinstance(param_value, Iterable) or isinstance(param_value, str):
1✔
2932
                        target_set[param_name] = param_value
1✔
2933
                    else:
2934
                        # hack for validation until it's streamlined
2935
                        # parse modulable parameter values
2936
                        if getattr(self.parameters, param_name).modulable:
1✔
2937
                            try:
1✔
2938
                                target_set[param_name] = param_value.copy()
1✔
2939
                            except AttributeError:
1✔
2940
                                modulable_param_parser = self.parameters._get_parse_method('modulable')
1✔
2941
                                if modulable_param_parser is not None:
1!
2942
                                    param_value = modulable_param_parser(param_name, param_value)
1✔
2943
                                    target_set[param_name] = param_value
1✔
2944
                                else:
2945
                                    target_set[param_name] = param_value.copy()
×
2946

2947
                        else:
2948
                            target_set[param_name] = copy.copy(param_value)
1✔
2949

2950
            # If param is a function_type (or it has a function attribute that is one), allow any other function_type
2951
            elif callable(param_value):
1!
2952
                target_set[param_name] = param_value
×
2953
            elif hasattr(param_value, FUNCTION) and callable(param_value.function):
1!
UNCOV
2954
                target_set[param_name] = param_value
×
2955

2956
            # It has already passed as the name of a valid param, so let it pass;
2957
            #    value should be validated in subclass _validate_params override
2958
            elif isinstance(param_name, str):
1!
2959
                # FIX: 10/3/17 - THIS IS A HACK;  IT SHOULD BE HANDLED EITHER
2960
                # FIX:           MORE GENERICALLY OR LOCALLY (E.G., IN OVERRIDE OF _validate_params)
2961
                if param_name == 'matrix':
1!
UNCOV
2962
                    if is_matrix(getattr(self.defaults, param_name)):
×
2963
                        # FIX:  ?? ASSIGN VALUE HERE, OR SIMPLY ALLOW AND ASSUME IT WILL BE PARSED ELSEWHERE
2964
                        # param_value = getattr(self.defaults, param_name)
2965
                        # target_set[param_name] = param_value
UNCOV
2966
                        target_set[param_name] = param_value
×
2967
                    else:
2968
                        raise ComponentError("Value of {} param for {} ({}) must be a valid matrix specification".
2969
                                             format(param_name, self.name, param_value))
2970
                target_set[param_name] = param_value
1✔
2971

2972
            # Parameter is not a valid type
2973
            else:
2974
                if type(getattr(self.defaults, param_name)) is type:
×
2975
                    type_name = 'the name of a subclass of ' + getattr(self.defaults, param_name).__base__.__name__
×
2976
                raise ComponentError("Value of {} param for {} ({}) is not compatible with {}".
2977
                                    format(param_name, self.name, param_value, type_name))
2978

2979
    def _get_param_value_for_modulatory_spec(self, param_name, param_value):
1✔
2980
        from psyneulink.core.globals.keywords import MODULATORY_SPEC_KEYWORDS
×
2981
        if isinstance(param_value, str):
×
2982
            param_spec = param_value
×
2983
        elif isinstance(param_value, Component):
×
2984
            param_spec = param_value.__class__.__name__
×
2985
        elif isinstance(param_value, type):
×
2986
            param_spec = param_value.__name__
×
2987
        else:
2988
            raise ComponentError("PROGRAM ERROR: got {} instead of string, Component, or Class".format(param_value))
2989

2990
        if param_spec not in MODULATORY_SPEC_KEYWORDS:
×
2991
            return (param_value)
×
2992

2993
        try:
×
2994
            param_default_value = getattr(self.defaults, param_name)
×
2995
            # Only assign default value if it is not None
2996
            if param_default_value is not None:
×
2997
                return param_default_value
×
2998
            else:
2999
                return param_value
×
3000
        except:
×
3001
            raise ComponentError("PROGRAM ERROR: Could not get default value for {} of {} (to replace spec as {})".
3002
                                 format(param_name, self.name, param_value))
3003

3004
    def _get_param_value_from_tuple(self, param_spec):
1✔
3005
        """Returns param value (first item) of a (value, projection) tuple;
3006
        """
3007
        from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism import ModulatoryMechanism_Base
×
3008
        from psyneulink.core.components.projections.modulatory.modulatoryprojection import ModulatoryProjection_Base
×
3009
        from psyneulink.core.components.ports.modulatorysignals.modulatorysignal import ModulatorySignal
×
3010

3011
        ALLOWABLE_TUPLE_SPEC_KEYWORDS = MODULATORY_SPEC_KEYWORDS
×
3012
        ALLOWABLE_TUPLE_SPEC_CLASSES = (ModulatoryProjection_Base, ModulatorySignal, ModulatoryMechanism_Base)
×
3013

3014
        # If the 2nd item is a CONTROL or LEARNING SPEC, return the first item as the value
3015
        if (isinstance(param_spec, tuple) and len(param_spec) == 2 and
×
3016
                not isinstance(param_spec[1], (dict, list, np.ndarray)) and
3017
                (param_spec[1] in ALLOWABLE_TUPLE_SPEC_KEYWORDS or
3018
                 isinstance(param_spec[1], ALLOWABLE_TUPLE_SPEC_CLASSES) or
3019
                 (inspect.isclass(param_spec[1]) and issubclass(param_spec[1], ALLOWABLE_TUPLE_SPEC_CLASSES)))
3020
            ):
3021
            value = param_spec[0]
×
3022

3023
        # Otherwise, just return the tuple
3024
        else:
3025
            value = param_spec
×
3026

3027
        return value
×
3028

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

3032
        # FROM _validate_params:
3033
        # It also checks FUNCTION:
3034
        #     if it is specified and is a type reference (rather than an instance),
3035
        #     it instantiates the reference (using FUNCTION_PARAMS if present)
3036
        #     and puts a reference to the instance in target_set[FUNCTION]
3037
        #
3038
        This checks for an execute method in function
3039
        If a specification is not present or valid:
3040
            - it checks self.execute and, if present, kwExecute is assigned to it
3041
            - if self.execute is not present or valid, an exception is raised
3042
        When completed, there is guaranteed to be a valid method in self.function and/or self.execute;
3043
            otherwise, an exception is raised
3044

3045
        Notes:
3046
            * no new assignments (to FUNCTION or self.execute) are made here, except:
3047
            * if FUNCTION is missing, it is assigned to self.execute (if it is present)
3048
            * no instantiations are done here;
3049
            * any assignment(s) to and/or instantiation(s) of self.execute and/or params[FUNCTION]
3050
                is/are carried out in _instantiate_function
3051

3052
        :return:
3053
        """
3054

3055
        from psyneulink.core.components.shellclasses import Function
×
3056

3057
        # FUNCTION is not specified, so try to assign self.function to it
3058
        if function is None:
×
3059
            try:
×
3060
                function = self.function
×
3061
            except AttributeError:
×
3062
                # self.function is also missing, so raise exception
3063
                raise ComponentError("{0} must either implement a function method or specify one in {0}.Parameters".
3064
                                    format(self.__class__.__name__))
3065

3066
        # self.function is None
3067
        # IMPLEMENTATION NOTE:  This is a coding error;  self.function should NEVER be assigned None
3068
        if function is None:
×
3069
            raise ComponentError("PROGRAM ERROR: either {0} must be specified or {1}.function must be implemented for {2}".
3070
                  format(FUNCTION,self.__class__.__name__, self.name))
3071
        # self.function is OK, so return
3072
        elif (
×
3073
            isinstance(function, types.FunctionType)
3074
            or isinstance(function, types.MethodType)
3075
            or is_instance_or_subclass(function, Function)
3076
        ):
3077
            self.parameters.function._set(function, context)
×
3078
            return
×
3079
        # self.function is NOT OK, so raise exception
3080
        else:
3081
            raise ComponentError("{0} not specified and {1}.function is not a Function object or class "
3082
                                "or valid method in {2}".
3083
                                format(FUNCTION, self.__class__.__name__, self.name))
3084

3085
    def _validate_value(self):
1✔
3086
        pass
1✔
3087

3088
    def _instantiate_attributes_before_function(self, function=None, context=None):
1✔
3089
        pass
1✔
3090

3091
    def _instantiate_function(self, function, function_params=None, context=None):
1✔
3092
        """Instantiate function defined in <subclass>.function or <subclass>.function
3093

3094
        Instantiate params[FUNCTION] if present, and assign it to self.function
3095

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

3099
        If FUNCTION IS in params:
3100
            - if it is a Function object, it is simply assigned to self.function;
3101
            - if it is a Function class reference:
3102
                it is instantiated using self.defaults.variable and, if present, params[FUNCTION_PARAMS]
3103
        If FUNCTION IS NOT in params:
3104
            - if self.function IS implemented, it is assigned to params[FUNCTION]
3105
            - if self.function IS NOT implemented: program error (should have been caught in _validate_function)
3106
        Upon successful completion:
3107
            - self._function === self.function
3108
            - self.execute should always return the output of self.function in the first item of its output array;
3109
                 this is done by Function.execute;  any subclass override should do the same, so that...
3110
            - value is value[0] returned by self.execute
3111

3112
        """
3113
        from psyneulink.core.components.functions.userdefinedfunction import UserDefinedFunction
1✔
3114
        from psyneulink.core.components.shellclasses import Function
1✔
3115

3116
        function_variable = copy.deepcopy(
1✔
3117
            self._parse_function_variable(
3118
                self.defaults.variable,
3119
                context
3120
            )
3121
        )
3122

3123
        # Specification is the function of a (non-instantiated?) Function class
3124
        # KDM 11/12/18: parse an instance of a Function's .function method to itself
3125
        # (not sure how worth it this is, but it existed in Scripts/Examples/Reinforcement-Learning REV)
3126
        # purposely not attempting to parse a class Function.function
3127
        # JDC 3/6/19:  ?what about parameter ports for its parameters (see python function problem below)?
3128
        if isinstance(function, types.MethodType):
1!
3129
            try:
×
3130
                if isinstance(function.__self__, Function):
×
3131
                    function = function.__self__
×
3132
            except AttributeError:
×
3133
                pass
×
3134

3135
        # Specification is a standard python function, so wrap as a UserDefnedFunction
3136
        # Note:  parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
3137
        if isinstance(function, (types.FunctionType, str)):
1✔
3138
            function = UserDefinedFunction(
1✔
3139
                default_variable=function_variable,
3140
                custom_function=function,
3141
                owner=self,
3142
                context=context,
3143
                **function_params,
3144
            )
3145

3146
        # Specification is an already implemented Function
3147
        elif isinstance(function, Function):
1✔
3148
            if function_variable.shape != function.defaults.variable.shape:
1✔
3149
                owner_str = ''
1✔
3150
                if hasattr(self, 'owner') and self.owner is not None:
1✔
3151
                    owner_str = f' of {repr(self.owner.name)}'
1✔
3152
                if function._variable_shape_flexibility is DefaultsFlexibility.RIGID:
1✔
3153
                    raise ComponentError(f'Variable format ({function.defaults.variable}) of {function.name} '
3154
                                         f'is not compatible with the variable format ({function_variable}) '
3155
                                         f'of {repr(self.name)}{owner_str} to which it is being assigned.')
3156
                                         # f'Make sure variable for {function.name} is 2d.')
3157
                elif function._variable_shape_flexibility is DefaultsFlexibility.INCREASE_DIMENSION:
1✔
3158
                    function_increased_dim = np.asarray([function.defaults.variable])
1✔
3159
                    if function_variable.shape != function_increased_dim.shape:
1✔
3160
                        raise ComponentError(f'Variable format ({function.defaults.variable}) of {function.name} '
3161
                                             f'is not compatible with the variable format ({function_variable})'
3162
                                             f' of {repr(self.name)}{owner_str} to which it is being assigned.')
3163
                                             # f'Make sure variable for {function.name} is 2d.')
3164

3165
            # class default functions should always be copied, otherwise anything this component
3166
            # does with its function will propagate to anything else that wants to use
3167
            # the default
3168
            if function.owner is self:
1✔
3169
                try:
1✔
3170
                    if function._is_pnl_inherent:
1!
3171
                        # This will most often occur if a Function instance is
3172
                        # provided as a default argument in a constructor. These
3173
                        # should instead be added as default values for the
3174
                        # corresponding Parameter.
3175
                        # Adding the function as a default constructor argument
3176
                        # will lead to incorrect setting of
3177
                        # Parameter._user_specified
3178
                        warnings.warn(
×
3179
                            f'{function} is generated once during import of'
3180
                            ' psyneulink, and is now being reused. Please report'
3181
                            ' this, including the script you were using, to the'
3182
                            ' psyneulink developers at'
3183
                            ' psyneulinkhelp@princeton.edu or'
3184
                            ' https://github.com/PrincetonUniversity/PsyNeuLink/issues'
3185
                        )
3186
                        function = copy.deepcopy(function)
×
3187
                except AttributeError:
1✔
3188
                    pass
1✔
3189
            elif function.owner is not None:
1✔
3190
                function = copy.deepcopy(function)
1✔
3191

3192
            # set owner first because needed for is_initializing calls
3193
            function.owner = self
1✔
3194
            function._update_default_variable(function_variable, context)
1✔
3195

3196
        # Specification is Function class
3197
        # Note:  parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
3198
        elif inspect.isclass(function) and issubclass(function, Function):
1✔
3199
            kwargs_to_instantiate = {}
1✔
3200
            if function_params is not None:
1✔
3201
                kwargs_to_instantiate.update(**function_params)
1✔
3202
                # default_variable should not be in any function_params but sometimes it is
3203
                kwargs_to_remove = ['default_variable']
1✔
3204

3205
                for arg in kwargs_to_remove:
1✔
3206
                    try:
1✔
3207
                        del kwargs_to_instantiate[arg]
1✔
3208
                    except KeyError:
1✔
3209
                        pass
1✔
3210

3211
                try:
1✔
3212
                    kwargs_to_instantiate.update(self.initial_shared_parameters[FUNCTION])
1✔
3213
                except KeyError:
×
3214
                    pass
×
3215

3216
                # matrix is determined from ParameterPort based on string value in function_params
3217
                # update it here if needed
3218
                if MATRIX in kwargs_to_instantiate:
1✔
3219
                    try:
1✔
3220
                        kwargs_to_instantiate[MATRIX] = copy_parameter_value(self.parameter_ports[MATRIX].defaults.value)
1✔
3221
                    except (AttributeError, KeyError, TypeError):
×
3222
                        pass
×
3223

3224
            try:
1✔
3225
                function = function(default_variable=function_variable, owner=self, **kwargs_to_instantiate)
1✔
3226
            except TypeError as e:
1✔
3227
                if 'unexpected keyword argument' in str(e):
1✔
3228
                    raise ComponentError(f'(function): {function} {e}', component=self) from e
3229
                else:
3230
                    raise
1✔
3231

3232
        else:
3233
            raise ComponentError(f'Unsupported function type: {type(function)}, function={function}.')
3234

3235
        self.parameters.function._set(function, context)
1✔
3236

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

3243
        self._parse_param_port_sources()
1✔
3244

3245
    def _instantiate_attributes_after_function(self, context=None):
1✔
3246
        if hasattr(self, "_parameter_ports"):
1✔
3247
            shared_params = [p for p in self.parameters if isinstance(p, (ParameterAlias, SharedParameter))]
1✔
3248
            sources = [p.source for p in shared_params]
1✔
3249

3250
            for param_port in self._parameter_ports:
1✔
3251
                property_names = {param_port.name}
1✔
3252
                try:
1✔
3253
                    alias_index = sources.index(param_port.source)
1✔
3254
                    property_names.add(shared_params[alias_index].name)
1✔
3255
                except ValueError:
1✔
3256
                    pass
1✔
3257

3258
                for property_name in property_names:
1✔
3259
                    setattr(self.__class__, "mod_" + property_name, make_property_mod(property_name, param_port.name))
1✔
3260
                    setattr(self.__class__, "get_mod_" + property_name, make_stateful_getter_mod(property_name, param_port.name))
1✔
3261

3262
    def _instantiate_value(self, context=None):
1✔
3263
        #  - call self.execute to get value, since the value of a Component is defined as what is returned by its
3264
        #    execute method, not its function
3265
        default_variable = copy.deepcopy(self.defaults.variable)
1✔
3266
        try:
1✔
3267
            value = self.execute(variable=default_variable, context=context)
1✔
3268
        except TypeError as e:
1✔
3269
            # don't hide other TypeErrors
3270
            if "execute() got an unexpected keyword argument 'variable'" not in str(e):
1!
3271
                raise
×
3272

3273
            try:
1✔
3274
                value = self.execute(input=default_variable, context=context)
1✔
3275
            except TypeError as e:
1✔
3276
                if "execute() got an unexpected keyword argument 'input'" != str(e):
×
3277
                    raise
×
3278

3279
                value = self.execute(context=context)
×
3280
        if value is None:
1✔
3281
            raise ComponentError(f"PROGRAM ERROR: Execute method for {self.name} must return a value.")
3282

3283
        self.parameters.value._set(value, context=context, skip_history=True)
1✔
3284
        try:
1✔
3285
            # Could be mutable, so assign copy
3286
            self.defaults.value = value.copy()
1✔
3287
        except AttributeError:
1✔
3288
            # Immutable, so just assign value
3289
            self.defaults.value = value
1✔
3290

3291
    def _update_default_variable(self, new_default_variable, context=None):
1✔
3292
        from psyneulink.core.components.shellclasses import Function
1✔
3293

3294
        new_default_variable = convert_all_elements_to_np_array(new_default_variable)
1✔
3295
        self.defaults.variable = copy.deepcopy(new_default_variable)
1✔
3296

3297
        # exclude value from validation because it isn't updated until
3298
        # _instantiate_value is called
3299
        call_with_pruned_args(
1✔
3300
            self._validate_params,
3301
            variable=new_default_variable,
3302
            request_set={
3303
                k: v.default_value
3304
                for k, v in self.parameters.values(True).items()
3305
                if k not in {'value'} and not isinstance(v, ParameterAlias)
3306
            },
3307
            target_set={},
3308
            context=context
3309
        )
3310
        self._instantiate_value(context)
1✔
3311

3312
        for p in self.parameters._in_dependency_order:
1✔
3313
            val = p._get(context)
1✔
3314
            if (
1✔
3315
                not p.reference
3316
                and isinstance(val, Function)
3317
                and not isinstance(p, SharedParameter)
3318
            ):
3319
                function_default_variable = self._get_parsed_variable(p, context=context)
1✔
3320

3321
                try:
1✔
3322
                    val._update_default_variable(function_default_variable, context)
1✔
3323
                    if isinstance(p.default_value, Component):
1✔
3324
                        p.default_value._update_default_variable(function_default_variable, context)
1✔
3325
                except (AttributeError, TypeError):
×
3326
                    pass
×
3327

3328
                # TODO: is it necessary to call _validate_value here?
3329

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

3333
    def _check_for_composition(self, context=None):
1✔
3334
        """Allow Component to check whether it or its attributes are suitable for inclusion in a Composition
3335
        Called by Composition.add_node.
3336
        """
3337
        pass
1✔
3338

3339
    @handle_external_context(fallback_most_recent=True)
1✔
3340
    def reset(self, *args, context=None, **kwargs):
1✔
3341
        """
3342
            If the component's execute method involves execution of an `IntegratorFunction` Function, this method
3343
            effectively begins the function's accumulation over again at the specified value, and may update related
3344
            values on the component, depending on the component type.  Otherwise, it simply reassigns the Component's
3345
            value based on its default_variable.
3346
        """
3347
        from psyneulink.core.components.functions.stateful.integratorfunctions import IntegratorFunction
×
3348
        if isinstance(self.function, IntegratorFunction):
×
3349
            new_value = self.function.reset(*args, **kwargs, context=context)
×
3350
            self.parameters.value.set(np.atleast_2d(new_value), context, override=True)
×
3351
        else:
3352
            raise ComponentError(f"Resetting {self.name} is not allowed because this Component is not stateful. "
3353
                                 "(It does not have an accumulator to reset).")
3354

3355
    @handle_external_context()
1✔
3356
    def execute(self, variable=None, context=None, runtime_params=None):
1✔
3357
        """Executes Component's `function <Component_Function>`.  See Component-specific execute method for details.
3358
        """
3359

3360
        if context is None:
1!
3361
            try:
×
3362
                context = self.owner.most_recent_context
×
3363
            except AttributeError:
×
3364
                context = self.most_recent_context
×
3365

3366
        if context.source is ContextFlags.COMMAND_LINE:
1✔
3367
            self._initialize_from_context(context, override=False)
1✔
3368
            if is_numeric(variable):
1✔
3369
                variable = convert_all_elements_to_np_array(variable)
1✔
3370

3371
        value = self._execute(variable=variable, context=context, runtime_params=runtime_params)
1✔
3372
        self.parameters.value._set(value, context=context)
1✔
3373

3374
        return value
1✔
3375

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

3379
        self.parameters.variable._set(variable, context=context)
1✔
3380

3381
        if self.initialization_status & ~(ContextFlags.VALIDATING | ContextFlags.INITIALIZING):
1✔
3382
            self._increment_execution_count()
1✔
3383

3384
            # Functions don't have Logs or maintain time
3385
            if not isinstance(self, Function):
1✔
3386
                self._update_current_execution_time(context=context)
1✔
3387
                self._increment_num_executions(
1✔
3388
                    context,
3389
                    [TimeScale.TIME_STEP, TimeScale.PASS, TimeScale.TRIAL, TimeScale.RUN, TimeScale.LIFE]
3390
                )
3391

3392
        value = None
1✔
3393

3394
        # GET VALUE if specified in runtime_params
3395
        if runtime_params and VALUE in runtime_params:
1✔
3396
            # Get value and then pop from runtime_param, as no need to restore to previous value
3397
            value = np.atleast_1d(runtime_params.pop(VALUE))
1✔
3398
            # Eliminate any other params (including ones for function),
3399
            #  since they will not be assigned and therefore should not be restored to previous value below
3400
            #  (doing so would restore them to the previous previous value)
3401
            runtime_params = {}
1✔
3402

3403
        # CALL FUNCTION if value is not specified
3404
        if value is None:
1✔
3405
            # IMPLEMENTATION NOTE:  **kwargs is included to accommodate required arguments
3406
            #                     that are specific to particular class of Functions
3407
            #                     (e.g., error_matrix for LearningMechanism and controller for EVCControlMechanism)
3408
            function_variable = self._parse_function_variable(variable, context=context)
1✔
3409
            # IMPLEMENTATION NOTE: Need to pass full runtime_params (and not just function's params) since
3410
            #                      Mechanisms with secondary functions (e.g., IntegratorMechanism) seem them
3411
            value = self.function(variable=function_variable, context=context, params=runtime_params, **kwargs)
1✔
3412
            try:
1✔
3413
                self.function.parameters.value._set(value, context)
1✔
3414
            except AttributeError:
1✔
3415
                pass
1✔
3416

3417
        self.most_recent_context = context
1✔
3418
        self._reset_runtime_parameters(context)
1✔
3419

3420
        return value
1✔
3421

3422
    def is_finished(self, context=None):
1✔
3423
        """
3424
            Set by a Component to signal completion of its `execution <Component_Execution>` in a `TRIAL
3425
            <TimeScale.TRIAL>`; used by `Component-based Conditions <Conditions_Component_Based>` to predicate the
3426
            execution of one or more other Components on a Component.
3427
        """
3428
        return self.parameters.is_finished_flag._get(context)
1✔
3429

3430
    def _parse_param_port_sources(self):
1✔
3431
        if hasattr(self, '_parameter_ports'):
1✔
3432
            for param_port in self._parameter_ports:
1✔
3433
                try:
1✔
3434
                    orig_source = param_port.source
1✔
3435
                    param_port.source = param_port.source(self)
1✔
3436
                    del self.parameter_ports.parameter_mapping[orig_source]
1✔
3437
                    self.parameter_ports.parameter_mapping[param_port.source] = param_port
1✔
3438
                except TypeError:
1✔
3439
                    pass
1✔
3440
                param_port.source.port = param_port
1✔
3441

3442
    def _get_current_parameter_value(self, parameter, context=None):
1✔
3443
        from psyneulink.core.components.ports.parameterport import ParameterPortError
1✔
3444

3445
        if parameter == "variable" or parameter == self.parameters.variable:
1✔
3446
            raise ComponentError(
3447
                f"The method '_get_current_parameter_value' is intended for retrieving the current "
3448
                f"value of a modulable parameter; 'variable' is not a modulable parameter. If looking "
3449
                f"for {self.name}'s default variable, try '{self.name}.defaults.variable'."
3450
            )
3451

3452
        try:
1✔
3453
            parameter = getattr(self.parameters, parameter)
1✔
3454
        # just fail now if string and no corresponding parameter (AttributeError)
3455
        except TypeError:
1✔
3456
            pass
1✔
3457

3458
        parameter_port_list = None
1✔
3459
        try:
1✔
3460
            # parameter is SharedParameter and ultimately points to
3461
            # something with a corresponding ParameterPort
3462
            parameter_port_list = parameter.final_source._owner._owner.parameter_ports
1✔
3463
        except AttributeError:
1✔
3464
            # prefer parameter ports from self over owner
3465
            try:
1✔
3466
                parameter_port_list = self._parameter_ports
1✔
3467
            except AttributeError:
1✔
3468
                try:
1✔
3469
                    parameter_port_list = self.owner._parameter_ports
1✔
3470
                except AttributeError:
1✔
3471
                    pass
1✔
3472

3473
        if parameter_port_list is not None:
1✔
3474
            try:
1✔
3475
                return parameter_port_list[parameter].parameters.value._get(context)
1✔
3476
            # *parameter* string or Parameter didn't correspond to a parameter port
3477
            except TypeError:
1✔
3478
                pass
×
3479
            except ParameterPortError as e:
1✔
3480
                if 'Multiple ParameterPorts' in str(e):
1!
3481
                    raise
×
3482

3483
        return parameter._get(context, modulated=True)
1✔
3484

3485
    def _reset_runtime_parameters(self, context):
1✔
3486
        if context.execution_id in self._runtime_params_reset:
1✔
3487
            for key in self._runtime_params_reset[context.execution_id]:
1✔
3488
                self._set_parameter_value(
1✔
3489
                    key,
3490
                    self._runtime_params_reset[context.execution_id][key],
3491
                    context
3492
                )
3493
            self._runtime_params_reset[context.execution_id] = {}
1✔
3494

3495
    def _try_execute_param(self, param, var, context=None):
1✔
3496
        def execute_if_callable(value, context=None):
1✔
3497
            try:
1✔
3498
                return value(context=context)
1✔
3499
            except TypeError:
1✔
3500
                try:
1✔
3501
                    return value()
1✔
3502
                except TypeError:
1✔
3503
                    return value
1✔
3504

3505
        def fill_recursively(arr, value, indices=()):
1✔
3506
            try:
1✔
3507
                is_scalar = arr.ndim == 0
1✔
NEW
3508
            except AttributeError:
×
NEW
3509
                is_scalar = True
×
3510

3511
            if is_scalar:
1✔
3512
                return execute_if_callable(value, context)
1✔
3513

3514
            try:
1✔
3515
                len_value = len(value)
1✔
3516
                len_arr = safe_len(arr)
1✔
3517

3518
                if len_value > len_arr:
1!
3519
                    if len_arr == len_value - 1:
×
3520
                        ignored_items_str = f'Item {len_value - 1}'
×
3521
                    else:
3522
                        ignored_items_str = f'The items {len_arr} to {len_value - 1}'
×
3523

3524
                    warnings.warn(
×
3525
                        f'The length of {value} is greater than that of {arr}.'
3526
                        f'{ignored_items_str} will be ignored for index {indices}'
3527
                    )
3528
            except TypeError:
1✔
3529
                # if noise value is not an iterable, ignore shape warnings
3530
                pass
1✔
3531

3532
            for i, _ in enumerate(arr):
1✔
3533
                new_indices = indices + (i,)  # for error reporting
1✔
3534
                try:
1✔
3535
                    arr[i] = fill_recursively(arr[i], value[i], new_indices)
1✔
3536
                except (IndexError, TypeError):
1✔
3537
                    arr[i] = fill_recursively(arr[i], value, new_indices)
1✔
3538

3539
            return arr
1✔
3540

3541
        var = convert_all_elements_to_np_array(copy.deepcopy(var), cast_from=int, cast_to=float)
1✔
3542

3543
        # ignore zero-length variables (e.g. empty Buffer)
3544
        if var.shape == (0, ):
1✔
3545
            return var
1✔
3546

3547
        # handle simple wrapping of a Component (e.g. from ParameterPort in
3548
        # case of set after Component instantiation)
3549
        if (
1✔
3550
            (isinstance(param, list) and len(param) == 1)
3551
            or (isinstance(param, np.ndarray) and param.shape == (1,))
3552
        ):
3553
            if isinstance(param[0], Component) or len(var) > 1:
1✔
3554
                param = param[0]
1✔
3555

3556
        # Currently most noise functions do not return noise in the same
3557
        # shape as their variable:
3558
        if isinstance(param, Component):
1✔
3559
            try:
1✔
3560
                if param.defaults.value.shape == var.shape:
1✔
3561
                    return param(context=context)
1✔
UNCOV
3562
            except AttributeError:
×
UNCOV
3563
                pass
×
3564

3565
        # special case where var is shaped same as param, but with extra dims
3566
        # assign param elements to deepest dim of var (ex: param [1, 2, 3], var [[0, 0, 0]])
3567
        try:
1✔
3568
            if param.shape != var.shape:
1✔
3569
                if param.shape == np.squeeze(var).shape:
1✔
3570
                    param = param.reshape(var.shape)
1✔
3571
        except AttributeError:
1✔
3572
            pass
1✔
3573

3574
        try:
1✔
3575
            # try to broadcast param to variable if param is numeric and regular
3576
            param_arr = np.asarray(param)
1✔
3577
            if param_arr.dtype != object:
1✔
3578
                return np.broadcast_to(param_arr, var.shape)
1✔
3579
        except ValueError:
×
3580
            # param not directly compatible with variable, continue elementwise
3581
            pass
×
3582

3583
        param = try_extract_0d_array_item(param)
1✔
3584
        fill_recursively(var, param)
1✔
3585
        return var
1✔
3586

3587
    def _increment_execution_count(self, count=1):
1✔
3588
        self.parameters.execution_count.set(self.execution_count + count, override=True)
1✔
3589
        return self.execution_count
1✔
3590

3591
    def _increment_num_executions(self, context, time_scales:(list, TimeScale), count=1):
1✔
3592
        # get relevant Time object:
3593
        time_scales = list(time_scales)
1✔
3594
        assert [isinstance(i, TimeScale) for i in time_scales], \
1✔
3595
            'non-TimeScale value provided in time_scales argument of _increment_num_executions'
3596
        curr_num_execs = self.parameters.num_executions._get(context)
1✔
3597
        for time_scale in time_scales:
1✔
3598
            new_val = curr_num_execs._get_by_time_scale(time_scale) + count
1✔
3599
            curr_num_execs._set_by_time_scale(time_scale, new_val)
1✔
3600
        self.parameters.num_executions.set(curr_num_execs, override=True)
1✔
3601
        return curr_num_execs
1✔
3602

3603
    @property
1✔
3604
    def current_execution_time(self):
1✔
3605
        try:
×
3606
            return self._current_execution_time
×
3607
        except AttributeError:
×
3608
            self._update_current_execution_time(self.most_recent_context.string)
×
3609

3610
    def get_current_execution_time(self, context=None):
1✔
3611
        if context is None:
1!
3612
            return self.current_execution_time
×
3613
        else:
3614
            try:
1✔
3615
                return context.composition.scheduler.get_clock(context).time
1✔
3616
            except AttributeError:
×
3617
                return None
×
3618
    # MODIFIED 9/22/19 END
3619

3620
    def _get_current_execution_time(self, context):
1✔
3621
        return _get_time(self, context=context)
1✔
3622

3623
    def _update_current_execution_time(self, context):
1✔
3624
        self._current_execution_time = self._get_current_execution_time(context=context)
1✔
3625

3626
    def _change_function(self, to_function):
1✔
3627
        pass
×
3628

3629
    @property
1✔
3630
    def _sender_ports(self):
1✔
3631
        """
3632
        Returns:
3633
            ContentAddressableList: list containing Ports on this object
3634
            that can send Projections
3635
        """
3636
        from psyneulink.core.components.shellclasses import Port
×
3637

3638
        ports = []
×
3639
        try:
×
3640
            ports.extend(self.output_ports)
×
3641
        except AttributeError:
×
3642
            pass
×
3643

3644
        return ContentAddressableList(Port, list=ports)
×
3645

3646
    @property
1✔
3647
    def _receiver_ports(self):
1✔
3648
        """
3649
        Returns:
3650
            ContentAddressableList: list containing Ports on this object
3651
            that can receive Projections
3652
        """
3653
        from psyneulink.core.components.shellclasses import Port
1✔
3654

3655
        ports = []
1✔
3656
        try:
1✔
3657
            ports.extend(self.input_ports)
1✔
3658
        except AttributeError:
×
3659
            pass
×
3660

3661
        try:
1✔
3662
            ports.extend(self.parameter_ports)
1✔
3663
        except AttributeError:
1✔
3664
            pass
1✔
3665

3666
        return ContentAddressableList(Port, list=ports)
1✔
3667

3668
    def _get_matching_projections(self, component, projections, filter_component_is_sender):
1✔
3669
        from psyneulink.core.components.shellclasses import Projection
1✔
3670

3671
        if filter_component_is_sender:
1!
3672
            def proj_matches_component(proj):
1✔
3673
                return proj.sender.owner == component or proj.sender == component
1✔
3674
        else:
3675
            def proj_matches_component(proj):
×
3676
                return proj.receiver.owner == component or proj.receiver == component
×
3677

3678
        if component:
1!
3679
            projections = filter(proj_matches_component, projections)
1✔
3680

3681
        return ContentAddressableList(Projection, list=list(projections))
1✔
3682

3683
    def get_afferents(self, from_component=None):
1✔
3684
        """
3685
        Args:
3686
            from_component (Component, optional): if specified, filters
3687
            returned list to contain only afferents originating from
3688
            *from_component* or one of its Ports. Defaults to None.
3689

3690
        Returns:
3691
            ContentAddressableList: list of afferent Projections to this
3692
            Component
3693
        """
3694
        projections = itertools.chain(*[p.all_afferents for p in self._receiver_ports])
1✔
3695
        return self._get_matching_projections(
1✔
3696
            from_component, projections, filter_component_is_sender=True
3697
        )
3698

3699
    def get_efferents(self, to_component=None):
1✔
3700
        """
3701
        Args:
3702
            to_component (Component, optional): if specified, filters
3703
            returned list to contain only efferents ending at
3704
            *to_component* or one of its Ports. Defaults to None.
3705

3706
        Returns:
3707
            ContentAddressableList: list of efferent Projections from
3708
            this Component
3709
        """
3710
        projections = itertools.chain(*[p.efferents for p in self._sender_ports])
×
3711
        return self._get_matching_projections(
×
3712
            to_component, projections, filter_component_is_sender=False
3713
        )
3714

3715
    @property
1✔
3716
    def name(self):
1✔
3717
        try:
1✔
3718
            return self._name
1✔
UNCOV
3719
        except AttributeError:
×
UNCOV
3720
            return 'unnamed {0}'.format(self.__class__)
×
3721

3722
    @name.setter
1✔
3723
    def name(self, value):
1✔
3724
        if not isinstance(value, str):
1✔
3725
            raise ComponentError(f"Name assigned to {self.__class__.__name__} ({value}) must be a string constant.")
3726

3727
        self._name = value
1✔
3728

3729
    @property
1✔
3730
    def input_shapes(self):
1✔
3731
        s = []
1✔
3732

3733
        try:
1✔
3734
            v = np.atleast_2d(self.defaults.variable)
1✔
3735
        except AttributeError:
×
3736
            return None
×
3737

3738
        for i in range(len(v)):
1✔
3739
            s.append(len(v[i]))
1✔
3740
        return np.array(s)
1✔
3741

3742
    @property
1✔
3743
    def prefs(self):
1✔
3744
        # Whenever pref is accessed, use current owner as context (for level checking)
3745
        self._prefs.owner = self
1✔
3746
        return self._prefs
1✔
3747

3748
    @prefs.setter
1✔
3749
    def prefs(self, pref_set):
1✔
3750
        if (isinstance(pref_set, PreferenceSet)):
1✔
3751
            # IMPLEMENTATION NOTE:
3752
            # - Complements dynamic assignment of owner in getter (above)
3753
            # - Needed where prefs are assigned before they've been gotten (e.g., in PreferenceSet.__init__()
3754
            # - owner needs to be assigned for call to get_pref_setting_for_level below
3755
            # MODIFIED 6/1/16
3756
            try:
1✔
3757
                pref_set.owner = self
1✔
3758
            except:
×
3759
            # MODIFIED 6/1/16 END
3760
                pass
×
3761
            self._prefs = pref_set
1✔
3762
            if self.prefs.verbosePref:
1✔
3763
                warnings.warn('PreferenceSet {0} assigned to {1}'.format(pref_set.name, self.name))
1✔
3764
            # Make sure that every pref attrib in PreferenceSet is OK
3765
            for pref_name, pref_entry in self.prefs.__dict__.items():
1✔
3766
                if '_pref' in pref_name:
1✔
3767
                    value, err_msg = self.prefs.get_pref_setting_for_level(pref_name, pref_entry.level)
1✔
3768
                    if err_msg and self.prefs.verbosePref:
1!
3769
                        warnings.warn(err_msg)
×
3770
                    # FIX: VALUE RETURNED SHOULD BE OK, SO ASSIGN IT INSTEAD OF ONE IN pref_set??
3771
                    # FIX: LEVEL SHOULD BE LOWER THAN REQUESTED;  REPLACE RAISE WITH WARNING TO THIS EFFECT
3772
        else:
3773
            raise ComponentError("Attempt to assign non-PreferenceSet {0} to {0}.prefs".
3774
                                format(pref_set, self.name))
3775

3776
    @property
1✔
3777
    def verbosePref(self):
1✔
3778
        return self.prefs.verbosePref
1✔
3779

3780
    @verbosePref.setter
1✔
3781
    def verbosePref(self, setting):
1✔
3782
        self.prefs.verbosePref = setting
1✔
3783

3784
    @property
1✔
3785
    def paramValidationPref(self):
1✔
3786
        return self.prefs.paramValidationPref
1✔
3787

3788
    @paramValidationPref.setter
1✔
3789
    def paramValidationPref(self, setting):
1✔
3790
        self.prefs.paramValidationPref = setting
×
3791

3792
    @property
1✔
3793
    def reportOutputPref(self):
1✔
3794
        from psyneulink.core.compositions.report import ReportOutput
1✔
3795
        if self.prefs.reportOutputPref is False:
1!
3796
            return ReportOutput.OFF
×
3797
        elif self.prefs.reportOutputPref is True:
1!
3798
            return ReportOutput.TERSE
×
3799
        return self.prefs.reportOutputPref
1✔
3800

3801
    @reportOutputPref.setter
1✔
3802
    def reportOutputPref(self, setting):
1✔
3803
        from psyneulink.core.compositions.report import ReportOutput
1✔
3804
        if setting is False:
1✔
3805
            setting = ReportOutput.OFF
1✔
3806
        elif setting is True:
1✔
3807
            setting = ReportOutput.TERSE
1✔
3808
        self.prefs.reportOutputPref = setting
1✔
3809

3810
    @property
1✔
3811
    def logPref(self):
1✔
3812
        return self.prefs.logPref
×
3813

3814
    @logPref.setter
1✔
3815
    def logPref(self, setting):
1✔
3816
        self.prefs.logPref = setting
×
3817

3818
    @property
1✔
3819
    def runtimeParamModulationPref(self):
1✔
3820
        return self.prefs.runtimeParamModulationPref
×
3821

3822
    @runtimeParamModulationPref.setter
1✔
3823
    def runtimeParamModulationPref(self, setting):
1✔
3824
        self.prefs.runtimeParamModulationPref = setting
×
3825

3826
    @property
1✔
3827
    def initialization_status(self):
1✔
3828
        try:
1✔
3829
            return self._initialization_status
1✔
3830
        except AttributeError:
1✔
3831
            self._initialization_status = ContextFlags.INITIALIZING
1✔
3832
        return self._initialization_status
1✔
3833

3834
    @initialization_status.setter
1✔
3835
    def initialization_status(self, flag):
1✔
3836
        """Check that a flag is one and only one status flag
3837
        """
3838
        if flag in INITIALIZATION_STATUS_FLAGS:
1!
3839
            self._initialization_status = flag
1✔
3840
        elif not flag:
×
3841
            self._initialization_status = ContextFlags.UNINITIALIZED
×
3842
        elif not (flag & ContextFlags.INITIALIZATION_MASK):
×
3843
            raise ContextError("Attempt to assign a flag ({}) to initialization_status "
3844
                               "that is not an initialization status flag".
3845
                               format(str(flag)))
3846
        else:
3847
            raise ContextError("Attempt to assign more than one flag ({}) to initialization_status".
3848
                               format(str(flag)))
3849

3850
    @property
1✔
3851
    def is_initializing(self):
1✔
3852
        try:
1✔
3853
            owner_initializing = self.owner.initialization_status == ContextFlags.INITIALIZING
1✔
3854
        except AttributeError:
1✔
3855
            owner_initializing = False
1✔
3856

3857
        return self.initialization_status == ContextFlags.INITIALIZING or owner_initializing
1✔
3858

3859
    @property
1✔
3860
    def log(self):
1✔
3861
        try:
1✔
3862
            return self._log
1✔
3863
        except AttributeError:
×
3864
            if self.initialization_status == ContextFlags.DEFERRED_INIT:
×
3865
                raise ComponentError("Initialization of {} is deferred; try assigning {} after it is complete "
3866
                                     "or appropriately configuring a Composition to which it belongs".
3867
                                     format(self.name, 'log'))
3868
            else:
3869
                raise AttributeError
3870

3871
    @log.setter
1✔
3872
    def log(self, log):
1✔
3873
        self._log = log
1✔
3874

3875
    @property
1✔
3876
    def loggable_items(self):
1✔
3877
        """Diciontary of items that can be logged in the Component's `log <Component.log>` and their current `ContextFlags`.
3878
        This is a convenience method that calls the `loggable_items <Log.loggable_items>` property of the Component's
3879
        `log <Component.log>`.
3880
        """
3881
        return self.log.loggable_items
1✔
3882

3883
    def set_log_conditions(self, items, log_condition=LogCondition.EXECUTION):
1✔
3884
        """
3885
        set_log_conditions(          \
3886
            items                    \
3887
            log_condition=EXECUTION  \
3888
        )
3889

3890
        Specifies items to be logged; these must be be `loggable_items <Component.loggable_items>` of the Component's
3891
        `log <Component.log>`. This is a convenience method that calls the `set_log_conditions <Log.set_log_conditions>`
3892
        method of the Component's `log <Component.log>`.
3893
        """
3894
        self.log.set_log_conditions(items=items, log_condition=log_condition)
1✔
3895

3896
    def set_delivery_conditions(self, items, delivery_condition=LogCondition.EXECUTION):
1✔
3897
        """
3898
        _set_delivery_conditions(          \
3899
            items                    \
3900
            delivery_condition=EXECUTION  \
3901
        )
3902

3903
        Specifies items to be delivered to external application via gRPC; these must be be `loggable_items <Component.loggable_items>`
3904
        of the Component's `log <Component.log>`. This is a convenience method that calls the `_set_delivery_conditions <Log._set_delivery_conditions>`
3905
        method of the Component's `log <Component.log>`.
3906
        """
3907
        self.log._set_delivery_conditions(items=items, delivery_condition=delivery_condition)
1✔
3908

3909
    def log_values(self, entries):
1✔
3910
        """
3911
        log_values(              \
3912
            entries              \
3913
        )
3914

3915
        Specifies items to be logged; ; these must be be `loggable_items <Component.loggable_items>` of the Component's
3916
        `log <Component.log>`. This is a convenience method that calls the `log_values <Log.log_values>` method
3917
        of the Component's `log <Component.log>`.
3918
        """
3919
        self.log.log_values(entries)
×
3920

3921
    def _propagate_most_recent_context(self, context=None, visited=None):
1✔
3922
        if visited is None:
1✔
3923
            visited = set([self])
1✔
3924

3925
        if context is None:
1!
3926
            context = self.most_recent_context
×
3927

3928
        self.most_recent_context = context
1✔
3929

3930
        # TODO: avoid duplicating objects in _dependent_components
3931
        # throughout psyneulink or at least condense these methods
3932
        for obj in self._dependent_components:
1✔
3933
            if obj not in visited:
1✔
3934
                visited.add(obj)
1✔
3935
                obj._propagate_most_recent_context(context, visited)
1✔
3936

3937
    def all_dependent_parameters(
1✔
3938
        self,
3939
        filter_name: typing.Union[str, typing.Iterable[str]] = None,
3940
        filter_regex: typing.Union[str, typing.Iterable[str]] = None,
3941
    ):
3942
        """Dictionary of Parameters of this Component and its \
3943
        `_dependent_components` filtered by **filter_name** and \
3944
        **filter_regex**. If no filter is specified, all Parameters \
3945
        are included.
3946

3947
        Args:
3948
            filter_name (Union[str, Iterable[str]], optional): The \
3949
                exact name or names of Parameters to include. Defaults \
3950
                to None.
3951
            filter_regex (Union[str, Iterable[str]], optional): \
3952
                Regular expression patterns. If any pattern matches a \
3953
                Parameter name (using re.match), it will be included \
3954
                in the result. Defaults to None.
3955

3956
        Returns:
3957
            dict[Parameter:Component]: Dictionary of filtered Parameters
3958
        """
3959
        def _all_dependent_parameters(obj, filter_name, filter_regex, visited):
1✔
3960
            parameters = {}
1✔
3961

3962
            if isinstance(filter_name, str):
1✔
3963
                filter_name = [filter_name]
1✔
3964

3965
            if isinstance(filter_regex, str):
1✔
3966
                filter_regex = [filter_regex]
1✔
3967

3968
            if filter_name is not None:
1✔
3969
                filter_name = set(filter_name)
1✔
3970

3971
            try:
1✔
3972
                filter_regex = [re.compile(r) for r in filter_regex]
1✔
3973
            except TypeError:
1✔
3974
                pass
1✔
3975

3976
            for p in obj.parameters:
1✔
3977
                include = filter_name is None and filter_regex is None
1✔
3978

3979
                if filter_name is not None:
1✔
3980
                    if p.name in filter_name:
1✔
3981
                        include = True
1✔
3982

3983
                if not include and filter_regex is not None:
1✔
3984
                    for r in filter_regex:
1✔
3985
                        if r.match(p.name):
1✔
3986
                            include = True
1✔
3987
                            break
1✔
3988

3989
                # owner check is primarily for value parameter on
3990
                # Composition which is deleted in
3991
                # ba56af82585e2d61f5b5bd13d9a19b7ee3b60124 presumably
3992
                # for clarity (results is used instead)
3993
                if include and p._owner._owner is obj:
1✔
3994
                    parameters[p] = obj
1✔
3995

3996
            for c in obj._dependent_components:
1✔
3997
                if c not in visited:
1✔
3998
                    visited.add(c)
1✔
3999
                    parameters.update(
1✔
4000
                        _all_dependent_parameters(
4001
                            c,
4002
                            filter_name,
4003
                            filter_regex,
4004
                            visited
4005
                        )
4006
                    )
4007

4008
            return parameters
1✔
4009

4010
        return _all_dependent_parameters(self, filter_name, filter_regex, set())
1✔
4011

4012
    def _get_mdf_parameters(self):
1✔
4013
        import modeci_mdf.mdf as mdf
1✔
4014

4015
        from psyneulink.core.compositions.composition import Composition
1✔
4016
        from psyneulink.core.components.ports.port import Port
1✔
4017
        from psyneulink.core.components.ports.outputport import OutputPort
1✔
4018
        from psyneulink.core.components.functions.nonstateful.transformfunctions import MatrixTransform
1✔
4019

4020
        def parse_parameter_value(value, no_expand_components=False, functions_as_dill=False):
1✔
4021
            if isinstance(value, (list, tuple)):
1✔
4022
                try:
1✔
4023
                    # treat as a collections.namedtuple
4024
                    type(value)._fields
1✔
4025
                except AttributeError:
1✔
4026
                    pass
1✔
4027
                else:
4028
                    # cannot expect MDF/neuromllite to handle our namedtuples
4029
                    value = tuple(value)
1✔
4030

4031
                new_item = []
1✔
4032
                for item in value:
1✔
4033
                    new_item.append(
1✔
4034
                        parse_parameter_value(
4035
                            item,
4036
                            no_expand_components,
4037
                            functions_as_dill
4038
                        )
4039
                    )
4040
                try:
1✔
4041
                    value = type(value)(new_item)
1✔
4042
                except TypeError:
×
4043
                    value = type(value)(*new_item)
×
4044
            elif isinstance(value, dict):
1✔
4045
                value = {
1✔
4046
                    parse_parameter_value(k, no_expand_components, functions_as_dill): parse_parameter_value(v, no_expand_components, functions_as_dill)
4047
                    for k, v in value.items()
4048
                }
4049
            elif isinstance(value, Composition):
1✔
4050
                value = value.name
1✔
4051
            elif isinstance(value, Port):
1✔
4052
                if isinstance(value, OutputPort):
1✔
4053
                    state_port_name = MODEL_SPEC_ID_OUTPUT_PORTS
1✔
4054
                else:
4055
                    state_port_name = MODEL_SPEC_ID_INPUT_PORTS
1✔
4056

4057
                # assume we will use the identifier on reconstitution
4058
                value = '{0}.{1}.{2}'.format(
1✔
4059
                    value.owner.name,
4060
                    state_port_name,
4061
                    value.name
4062
                )
4063
            elif isinstance(value, Component):
1✔
4064
                # could potentially create duplicates when it should
4065
                # create a reference to an already existent Component like
4066
                # with Compositions, but in a vacuum the full specification
4067
                # is necessary.
4068
                # in fact this would happen unless the parser specifically
4069
                # handles it like ours does
4070
                if no_expand_components:
1✔
4071
                    value = parse_valid_identifier(value.name)
1✔
4072
                else:
4073
                    try:
1✔
4074
                        value = value.as_mdf_model(simple_edge_format=False)
1✔
4075
                    except TypeError as e:
1✔
4076
                        if "got an unexpected keyword argument 'simple_edge_format'" not in str(e):
1!
4077
                            raise
×
4078
                        value = value.as_mdf_model()
1✔
4079
            elif isinstance(value, ComponentsMeta):
1✔
4080
                value = value.__name__
1✔
4081
            elif isinstance(value, (type, types.BuiltinFunctionType)):
1✔
4082
                if value.__module__ == 'builtins':
1!
4083
                    # just give standard type, like float or int
4084
                    value = f'{value.__name__}'
1✔
4085
                elif value is np.ndarray:
×
4086
                    value = f'{value.__module__}.array'
×
4087
                else:
4088
                    # some builtin modules are internally "_module"
4089
                    # but are imported with "module"
4090
                    value = f"{value.__module__.lstrip('_')}.{value.__name__}"
×
4091
            elif isinstance(value, types.MethodType):
1✔
4092
                if isinstance(value.__self__, Component):
1!
4093
                    # assume reference to a method on a Component is
4094
                    # automatically assigned (may not be totally
4095
                    # accurate but is in current known cases)
4096
                    value = None
1✔
4097
                else:
4098
                    value = value.__qualname__
×
4099

4100
            # numpy functions are no longer "FunctionType" since numpy
4101
            # moved dispatch implementation from Python to C in
4102
            # https://github.com/numpy/numpy/commit/60a858a372b14b73547baacf4a472eccfade1073
4103
            # Use np.sum as a representative of these functions
4104
            elif isinstance(value, (types.FunctionType, type(np.sum))):
1✔
4105
                if functions_as_dill:
1!
4106
                    value = base64.encodebytes(dill.dumps(value)).decode('utf-8')
1✔
4107
                elif '.' in value.__qualname__:
×
4108
                    value = value.__qualname__
×
4109
                else:
4110
                    try:
×
4111
                        if value is getattr(eval(value.__module__.replace('numpy', 'np')), value.__qualname__):
×
4112
                            return f'{value.__module__}.{value.__qualname__}'
×
4113
                    except (AttributeError, NameError, TypeError):
×
4114
                        pass
×
4115

4116
                    value = str(value)
×
4117
            elif isinstance(value, SampleIterator):
1✔
4118
                value = f'{value.__class__.__name__}({repr(value.specification)})'
1✔
4119
            elif value is NotImplemented:
1!
4120
                value = None
×
4121
            # IntEnum gets treated as int
4122
            elif isinstance(value, (Enum, types.SimpleNamespace)):
1✔
4123
                value = str(value)
1✔
4124
            elif not isinstance(value, (float, int, str, bool, mdf.Base, type(None), np.ndarray)):
1✔
4125
                value = str(value)
1✔
4126

4127
            return value
1✔
4128

4129
        # attributes that aren't Parameters but are psyneulink-specific
4130
        # and are stored in the PNL parameters section
4131
        implicit_parameter_attributes = ['node_ordering', 'required_node_roles']
1✔
4132

4133
        parameters_dict = {}
1✔
4134
        pnl_specific_parameters = {}
1✔
4135
        deferred_init_values = {}
1✔
4136

4137
        if self.initialization_status is ContextFlags.DEFERRED_INIT:
1✔
4138
            deferred_init_values = copy.copy(self._init_args)
1✔
4139
            try:
1✔
4140
                deferred_init_values.update(deferred_init_values['params'])
1✔
4141
            except (KeyError, TypeError):
1✔
4142
                pass
1✔
4143

4144
            # .parameters still refers to class parameters during deferred init
4145
            assert self.parameters._owner is not self
1✔
4146

4147
        for p in self.parameters:
1✔
4148
            if (
1✔
4149
                p.name not in self._model_spec_parameter_blacklist
4150
                and not isinstance(p, (ParameterAlias, SharedParameter))
4151
            ):
4152
                if self.initialization_status is ContextFlags.DEFERRED_INIT:
1✔
4153
                    try:
1✔
4154
                        val = deferred_init_values[p.name]
1✔
4155
                    except KeyError:
1✔
4156
                        # class default
4157
                        val = p.default_value
1✔
4158
                else:
4159
                    # special handling because MatrixTransform default values
4160
                    # can be PNL-specific keywords. In future, generalize
4161
                    # this workaround
4162
                    if (
1✔
4163
                        isinstance(self, MatrixTransform)
4164
                        and p.name == 'matrix'
4165
                    ):
4166
                        val = self.parameters.matrix.values[None]
1✔
4167
                    elif p.spec is not None:
1✔
4168
                        val = p.spec
1✔
4169
                    else:
4170
                        val = None
1✔
4171
                        if not p.stateful and not p.structural:
1✔
4172
                            val = p.get(None)
1✔
4173
                        if val is None:
1✔
4174
                            val = p.default_value
1✔
4175

4176
                val = parse_parameter_value(val, functions_as_dill=True)
1✔
4177

4178
                # split parameters designated as PsyNeuLink-specific and
4179
                # parameters that are universal
4180
                if p.pnl_internal:
1✔
4181
                    target_param_dict = pnl_specific_parameters
1✔
4182
                else:
4183
                    target_param_dict = parameters_dict
1✔
4184

4185
                if p.mdf_name is not None:
1✔
4186
                    target_param_dict[p.mdf_name] = val
1✔
4187
                else:
4188
                    target_param_dict[p.name] = val
1✔
4189

4190
        for attr in implicit_parameter_attributes:
1✔
4191
            try:
1✔
4192
                pnl_specific_parameters[attr] = parse_parameter_value(getattr(self, attr), no_expand_components=True, functions_as_dill=True)
1✔
4193
            except AttributeError:
1✔
4194
                pass
1✔
4195

4196
        if len(pnl_specific_parameters) > 0:
1!
4197
            parameters_dict[MODEL_SPEC_ID_PSYNEULINK] = pnl_specific_parameters
1✔
4198

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

4201
    @property
1✔
4202
    def _mdf_model_parameters(self):
1✔
4203
        params = self._get_mdf_parameters()
1✔
4204
        try:
1✔
4205
            del params[self._model_spec_id_parameters][MODEL_SPEC_ID_PSYNEULINK]
1✔
4206
        except KeyError:
×
4207
            pass
×
4208

4209
        params[self._model_spec_id_parameters] = {
1✔
4210
            k: v for k, v in params[self._model_spec_id_parameters].items()
4211
            if (
4212
                k == MODEL_SPEC_ID_MDF_VARIABLE
4213
                or (
4214
                    isinstance(v, (numbers.Number, np.ndarray))
4215
                    and not isinstance(v, bool)
4216
                )
4217
            )
4218
        }
4219

4220
        return params
1✔
4221

4222
    @property
1✔
4223
    def _mdf_model_nonstateful_parameters(self):
1✔
4224
        parameters = self._mdf_model_parameters
1✔
4225

4226
        return {
1✔
4227
            self._model_spec_id_parameters: {
4228
                k: v for k, v in parameters[self._model_spec_id_parameters].items()
4229
                if (k not in self.parameters or getattr(self.parameters, k).initializer is None)
4230
            }
4231
        }
4232

4233
    @property
1✔
4234
    def _mdf_metadata(self):
1✔
4235
        all_parameters = self._get_mdf_parameters()
1✔
4236
        try:
1✔
4237
            params = all_parameters[self._model_spec_id_parameters][MODEL_SPEC_ID_PSYNEULINK]
1✔
4238
        except KeyError:
×
4239
            params = {}
×
4240

4241
        params = {
1✔
4242
            **params,
4243
            **{
4244
                k: v for k, v in all_parameters[self._model_spec_id_parameters].items()
4245
                if (
4246
                    k not in {MODEL_SPEC_ID_MDF_VARIABLE, MODEL_SPEC_ID_PSYNEULINK}
4247
                    and (
4248
                        not isinstance(v, (numbers.Number, np.ndarray))
4249
                        or isinstance(v, bool)
4250
                    )
4251
                )
4252
            }
4253
        }
4254

4255
        return {
1✔
4256
            MODEL_SPEC_ID_METADATA: {
4257
                'type': type(self).__name__,
4258
                **params
4259
            }
4260
        }
4261

4262
    def _set_mdf_arg(self, model, arg, value):
1✔
4263
        # must set both args attr and args in function because otherwise
4264
        # mdf dependency tracking doesn't work
4265
        try:
1✔
4266
            if model.function is not None:
1✔
4267
                model.function[list(model.function.keys())[0]][arg] = value
1✔
4268
        except AttributeError:
1✔
4269
            pass
1✔
4270

4271
        model.args[arg] = value
1✔
4272

4273
    def _add_to_composition(self, composition):
1✔
4274
        self.compositions.add(composition)
1✔
4275

4276
        for obj in self._parameter_components:
1✔
4277
            obj._add_to_composition(composition)
1✔
4278

4279
    def _remove_from_composition(self, composition):
1✔
4280
        self.compositions.discard(composition)
1✔
4281

4282
        for obj in self._parameter_components:
1✔
4283
            obj._remove_from_composition(composition)
1✔
4284

4285
    @property
1✔
4286
    def logged_items(self):
1✔
4287
        """Dictionary of all items that have entries in the log, and their currently assigned `ContextFlags`\\s
4288
        This is a convenience method that calls the `logged_items <Log.logged_items>` property of the Component's
4289
        `log <Component.log>`.
4290
        """
4291
        return self.log.logged_items
1✔
4292

4293
    @property
1✔
4294
    def _loggable_parameters(self):
1✔
4295
        return [param.name for param in self.parameters if param.loggable and param.user]
1✔
4296

4297
    @property
1✔
4298
    def _variable_shape_flexibility(self):
1✔
4299
        try:
1✔
4300
            return self.__variable_shape_flexibility
1✔
4301
        except AttributeError:
1✔
4302
            self.__variable_shape_flexibility = DefaultsFlexibility.FLEXIBLE
1✔
4303
            return self.__variable_shape_flexibility
1✔
4304

4305
    @_variable_shape_flexibility.setter
1✔
4306
    def _variable_shape_flexibility(self, value):
1✔
4307
        self.__variable_shape_flexibility = value
1✔
4308

4309
    @property
1✔
4310
    def class_parameters(self):
1✔
4311
        return self.__class__.parameters
1✔
4312

4313
    @property
1✔
4314
    def stateful_parameters(self):
1✔
4315
        """
4316
            A list of all of this object's `parameters <Parameters>` whose values
4317
            may change during runtime
4318
        """
4319
        return [param for param in self.parameters if param.stateful]
1✔
4320

4321
    @property
1✔
4322
    def stateful_attributes(self):
1✔
4323
        return [p.name for p in self.parameters if p.initializer is not None]
1✔
4324

4325
    @property
1✔
4326
    def initializers(self):
1✔
4327
        return [getattr(self.parameters, p).initializer for p in self.stateful_attributes]
1✔
4328

4329
    @property
1✔
4330
    def function_parameters(self):
1✔
4331
        """
4332
            The `parameters <Parameters>` object of this object's `function`
4333
        """
4334
        try:
1✔
4335
            return self.function.parameters
1✔
4336
        except AttributeError:
×
4337
            return None
×
4338

4339
    @property
1✔
4340
    def class_defaults(self):
1✔
4341
        """
4342
            Refers to the defaults of this object's class
4343
        """
4344
        return self.__class__.defaults
1✔
4345

4346
    @property
1✔
4347
    def is_pnl_inherent(self):
1✔
4348
        try:
×
4349
            return self._is_pnl_inherent
×
4350
        except AttributeError:
×
4351
            self._is_pnl_inherent = False
×
4352
            return self._is_pnl_inherent
×
4353

4354
    @property
1✔
4355
    def _parameter_components(self):
1✔
4356
        """
4357
            Returns a set of Components that are values of this object's
4358
            Parameters
4359
        """
4360
        try:
1✔
4361
            return self.__parameter_components
1✔
4362
        except AttributeError:
1✔
4363
            self.__parameter_components = set()
1✔
4364
            return self.__parameter_components
1✔
4365

4366
    @handle_external_context()
1✔
4367
    def _update_parameter_components(self, context=None):
1✔
4368
        # store all Components in Parameters to be used in
4369
        # _dependent_components for _initialize_from_context
4370
        for p in self.parameters:
1✔
4371
            param_value = p._get(context)
1✔
4372
            try:
1✔
4373
                param_value = param_value.__self__
1✔
4374
            except AttributeError:
1✔
4375
                pass
1✔
4376

4377
            if isinstance(param_value, Component) and param_value is not self:
1✔
4378
                self._parameter_components.add(param_value)
1✔
4379

4380
    @property
1✔
4381
    def _dependent_components(self):
1✔
4382
        """
4383
            Returns a set of Components that will be executed if this Component is executed
4384
        """
4385
        return list(self._parameter_components)
1✔
4386

4387
    @property
1✔
4388
    def most_recent_context(self):
1✔
4389
        """
4390
            used to set a default behavior for attributes that correspond to parameters
4391
        """
4392
        try:
1✔
4393
            return self._most_recent_context
1✔
4394
        except AttributeError:
1✔
4395
            self._most_recent_context = Context(source=ContextFlags.COMMAND_LINE, execution_id=None)
1✔
4396
            return self._most_recent_context
1✔
4397

4398
    @most_recent_context.setter
1✔
4399
    def most_recent_context(self, value):
1✔
4400
        self._most_recent_context = value
1✔
4401

4402
    @property
1✔
4403
    def _model_spec_parameter_blacklist(self):
1✔
4404
        """
4405
            A set of Parameter names that should not be added to the generated
4406
            constructor string
4407
        """
4408
        return {'function', 'value', 'execution_count', 'is_finished_flag', 'num_executions', 'num_executions_before_finished'}
1✔
4409

4410

4411
COMPONENT_BASE_CLASS = Component
1✔
4412

4413

4414
def make_property_mod(param_name, parameter_port_name=None):
1✔
4415
    if parameter_port_name is None:
1!
4416
        parameter_port_name = param_name
×
4417

4418
    def getter(self):
1✔
4419
        warnings.warn(
1✔
4420
            f'Getting modulated parameter values with <object>.mod_<param_name>'
4421
            ' may be removed in a future release. It is replaced with,'
4422
            f' for example, <object>.{param_name}.modulated',
4423
            FutureWarning
4424
        )
4425
        try:
1✔
4426
            return self._parameter_ports[parameter_port_name].value
1✔
4427
        except TypeError:
×
4428
            raise ComponentError("{} does not have a '{}' ParameterPort."
4429
                                 .format(self.name, param_name))
4430

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

4435
    prop = property(getter).setter(setter)
1✔
4436

4437
    return prop
1✔
4438

4439

4440
def make_stateful_getter_mod(param_name, parameter_port_name=None):
1✔
4441
    if parameter_port_name is None:
1!
4442
        parameter_port_name = param_name
×
4443

4444
    def getter(self, context=None):
1✔
4445
        try:
1✔
4446
            return self._parameter_ports[parameter_port_name].parameters.value.get(context)
1✔
4447
        except TypeError:
1✔
4448
            raise ComponentError("{} does not have a '{}' ParameterPort."
4449
                                 .format(self.name, param_name))
4450

4451
    return getter
1✔
4452

4453

4454
class ParameterValue:
1✔
4455
    def __init__(self, owner, parameter):
1✔
4456
        self._owner = owner
1✔
4457
        self._parameter = parameter
1✔
4458

4459
    def __repr__(self):
4460
        return f'{self._owner}:\n\t{self._parameter.name}.base: {self.base}\n\t{self._parameter.name}.modulated: {self.modulated}'
4461

4462
    @property
1✔
4463
    def modulated(self):
1✔
4464
        # TODO: consider using self._parameter.port.has_modulation
4465
        # because the port existing doesn't necessarily mean modulation
4466
        # is actually happening
4467
        if self._parameter.port is not None:
1✔
4468
            res = self._parameter.port.owner._get_current_parameter_value(
1✔
4469
                self._parameter,
4470
                self._owner.most_recent_context
4471
            )
4472
            if is_array_like(res):
1!
4473
                res = copy_parameter_value(res)
1✔
4474
            return res
1✔
4475
        else:
4476
            warnings.warn(
1✔
4477
                f'{self._parameter.name} is not currently modulated in most'
4478
                f' recent context {self._owner.most_recent_context}'
4479
            )
4480
            return None
1✔
4481

4482
    @modulated.setter
1✔
4483
    def modulated(self, value):
1✔
4484
        raise ComponentError(
4485
            f"Cannot set {self._owner.name}'s modulated {self._parameter.name}"
4486
            ' value directly because it is computed by the ParameterPort.'
4487
        )
4488

4489
    @property
1✔
4490
    def base(self):
1✔
4491
        return self._parameter.get(self._owner.most_recent_context)
1✔
4492

4493
    @base.setter
1✔
4494
    def base(self, value):
1✔
4495
        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

© 2026 Coveralls, Inc