Skip to content

Commit

Permalink
Merge pull request #1902 from PrincetonUniversity/devel
Browse files Browse the repository at this point in the history
Devel
  • Loading branch information
kmantel authored Feb 3, 2021
2 parents 7718996 + 2184ccc commit 5bc0cf5
Show file tree
Hide file tree
Showing 35 changed files with 520 additions and 463 deletions.
2 changes: 1 addition & 1 deletion .github/actions/cleanup-pip-cache/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ runs:
for P in $CACHED; do
# Remove cached and not installed
if [ `echo $INSTALLED | grep -o $P | wc -l` == "0" ] ; then
pip cache remove -v $P
pip cache remove -v $P || true
fi
done
77 changes: 74 additions & 3 deletions .github/workflows/pnl-ci-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,33 @@ name: PsyNeuLink Docs CI
on: push

jobs:
build:
docs-build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7] # Doesn't work in 3.8 or 3.9
python-version: [3.6, 3.7, 3.8]
python-architecture: ['x64']
os: [ubuntu-latest, macos-latest, windows-latest]

outputs:
on_master: ${{ steps.on_master.outputs.on_master }}

steps:
- name: Checkout sources
uses: actions/checkout@v2
with:
fetch-depth: 10
fetch-depth: 0

- name: Check if on master
id: on_master
shell: bash
run: |
git branch -a --contains $GITHUB_REF
git describe --always --tags
export ON_MASTER=$(git branch -a --contains $GITHUB_REF | grep -q '^ remotes/origin/master$' && echo "master" || echo "")
echo "Found out: ${ON_MASTER}"
echo ::set-output name=on_master::$ON_MASTER
- name: Set up Python ${{ matrix.python-version }}
uses: actions/[email protected]
Expand Down Expand Up @@ -76,3 +89,61 @@ jobs:
name: Documentation-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.python-architecture }}
retention-days: 1
path: pnl-html

docs-deploy:
strategy:
fail-fast: false
matrix:
python-version: [3.7]
os: [ubuntu-latest]

runs-on: ${{ matrix.os }}
needs: [docs-build]
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/devel' || github.ref == 'refs/heads/docs' || (contains(github.ref, 'tags') && contains(needs.*.outputs.on_master, 'master'))

steps:
- name: Checkout docs
uses: actions/checkout@v2
with:
ref: gh-pages

- name: Download branch docs
uses: actions/download-artifact@v2
with:
name: Documentation-${{ matrix.os }}-${{ matrix.python-version }}-x64
path: _built_docs/${{ github.ref }}
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/devel' || github.ref == 'refs/heads/docs'

- name: Update branch docs
shell: bash
run: |
mkdir -p branch
rm -rf "branch/${GITHUB_REF##*/}"
# Remove '.doctrees' and move to correct location
rm -rf "_built_docs/${GITHUB_REF}/.doctrees"
mv -f "_built_docs/${GITHUB_REF}" branch/
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/devel' || github.ref == 'refs/heads/docs'

- name: Download main docs
uses: actions/download-artifact@v2
with:
name: Documentation-${{ matrix.os }}-${{ matrix.python-version }}-x64
# This overwrites files in current directory
if: contains(github.ref, 'tags') && contains(needs.*.outputs.on_master, 'master')

- name: Update main docs
shell: bash
run: |
# Remove '.doctrees'
rm -rf ".doctrees"
if: contains(github.ref, 'tags') && contains(needs.*.outputs.on_master, 'master')

- name: Commit docs changes
shell: bash
run: |
# Commit changes to git
git add .
git config user.name "Documentation Bot"
git config user.email "[email protected]"
git commit -m "Docs changes for $GITHUB_REF $GITHUB_SHA"
git push
2 changes: 1 addition & 1 deletion .github/workflows/pnl-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
os: [ubuntu-latest, macos-latest, windows-latest]
include:
# add 32-bit build on windows
- python-version: 3.6
- python-version: 3.8
python-architecture: 'x86'
os: windows-latest

Expand Down
2 changes: 1 addition & 1 deletion Scripts/Examples/LC Control Mechanism Composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

A = TransferMechanism(function=Logistic(gain=user_specified_gain), name='A')
B = TransferMechanism(function=Logistic(gain=user_specified_gain), name='B')
# B.output_ports[0].value *= 0.0 # Reset after init | Doesn't matter here b/c default var = zero, no intercept
# B.output_ports[0].parameters.value.set(0.0, override=True) # Reset after init | Doesn't matter here b/c default var = zero, no intercept

LC = LCControlMechanism(
modulated_mechanisms=[A, B],
Expand Down
4 changes: 2 additions & 2 deletions dev_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
jupyter<=1.0.0
pytest<6.2.2
pytest<6.2.3
pytest-benchmark<=3.2.3
pytest-cov<=2.10.1
pytest-cov<2.11.2
pytest-helpers-namespace<=2019.1.8
pytest-profiling<=1.7.0
pytest-pycodestyle<=2.2.0
Expand Down
3 changes: 2 additions & 1 deletion docs/source/Preferences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Standard prefereces:

- paramValidation (bool): enables/disables run-time validation of the execute method of a Function object

- reportOutput (bool): enables/disables reporting of execution of execute method
- reportOutput ([bool, str]): enables/disables reporting of execution of execute method:
True prints input/output, 'params' or 'parameters' includes parameter values

- log (bool): sets LogCondition for a given Component

Expand Down
134 changes: 106 additions & 28 deletions psyneulink/core/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@
from psyneulink.core.globals.utilities import \
ContentAddressableList, convert_all_elements_to_np_array, convert_to_np_array, get_deepcopy_with_shared,\
is_instance_or_subclass, is_matrix, iscompatible, kwCompatibilityLength, prune_unused_args, \
get_all_explicit_arguments, call_with_pruned_args, safe_equals
get_all_explicit_arguments, call_with_pruned_args, safe_equals, safe_len
from psyneulink.core.scheduling.condition import Never

__all__ = [
Expand Down Expand Up @@ -650,7 +650,14 @@ def setter(self, value):
f' for example, <object>.{param.name}.base = {value}',
FutureWarning,
)
getattr(self.parameters, p.name)._set(value, self.most_recent_context)
try:
getattr(self.parameters, p.name).set(value, self.most_recent_context)
except ParameterError as e:
if 'Pass override=True to force set.' in str(e):
raise ParameterError(
f"Parameter '{p.name}' is read-only. Set at your own risk."
f' Use .parameters.{p.name}.set with override=True to force set.'
) from None

return property(getter).setter(setter)

Expand Down Expand Up @@ -1002,7 +1009,10 @@ def _validate_variable(self, variable):
return None

def _parse_modulable(self, param_name, param_value):
from psyneulink.core.components.functions.distributionfunctions import DistributionFunction
from psyneulink.core.components.mechanisms.modulatory.modulatorymechanism import ModulatoryMechanism_Base
from psyneulink.core.components.ports.modulatorysignals import ModulatorySignal
from psyneulink.core.components.projections.modulatory.modulatoryprojection import ModulatoryProjection_Base

# assume 2-tuple with class/instance as second item is a proper
# modulatory spec, can possibly add in a flag on acceptable
# classes in the future
Expand All @@ -1018,14 +1028,10 @@ def _parse_modulable(self, param_name, param_value):
)
):
value = param_value[0]
# assume a DistributionFunction is allowed to persist, for noise
elif (
(
is_instance_or_subclass(param_value, Component)
and not is_instance_or_subclass(
param_value,
DistributionFunction
)
is_instance_or_subclass(
param_value,
(ModulatoryMechanism_Base, ModulatorySignal, ModulatoryProjection_Base)
)
or (
isinstance(param_value, str)
Expand Down Expand Up @@ -1286,9 +1292,9 @@ def _get_compilation_state(self):
if not hasattr(self, 'ports'):
blacklist.add("value")
def _is_compilation_state(p):
val = p.get() # memoize for this function
return val is not None and p.name not in blacklist and \
(p.name in whitelist or isinstance(val, Component))
#FIXME: This should use defaults instead of 'p.get'
return p.name not in blacklist and \
(p.name in whitelist or isinstance(p.get(), Component))

return filter(_is_compilation_state, self.parameters)

Expand Down Expand Up @@ -2040,8 +2046,9 @@ def _is_user_specified(parameter):
if isinstance(val, Function):
val.owner = self

val = p._parse(val)
p._validate(val)
p.set(val, context=context, skip_history=True, override=True)
p._set(val, context=context, skip_history=True, override=True)

if isinstance(p.default_value, Function):
p.default_value.owner = p
Expand Down Expand Up @@ -2704,7 +2711,7 @@ def _get_param_value_from_tuple(self, param_spec):

return value

def _validate_function(self, function):
def _validate_function(self, function, context=None):
"""Check that either params[FUNCTION] and/or self.execute are implemented
# FROM _validate_params:
Expand Down Expand Up @@ -2752,7 +2759,7 @@ def _validate_function(self, function):
or isinstance(function, types.MethodType)
or is_instance_or_subclass(function, Function)
):
self.function = function
self.parameters.function._set(function, context)
return
# self.function is NOT OK, so raise exception
else:
Expand Down Expand Up @@ -2813,7 +2820,7 @@ def _instantiate_function(self, function, function_params=None, context=None):
# Specification is a standard python function, so wrap as a UserDefnedFunction
# Note: parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
if isinstance(function, types.FunctionType):
self.function = UserDefinedFunction(default_variable=function_variable,
function = UserDefinedFunction(default_variable=function_variable,
custom_function=function,
owner=self,
context=context)
Expand All @@ -2840,9 +2847,7 @@ def _instantiate_function(self, function, function_params=None, context=None):
# class default functions should always be copied, otherwise anything this component
# does with its function will propagate to anything else that wants to use
# the default
if function.owner is None:
self.function = function
elif function.owner is self:
if function.owner is self:
try:
if function._is_pnl_inherent:
# This will most often occur if a Function instance is
Expand All @@ -2860,15 +2865,15 @@ def _instantiate_function(self, function, function_params=None, context=None):
' [email protected] or'
' https://github.com/PrincetonUniversity/PsyNeuLink/issues'
)
self.function = copy.deepcopy(function)
function = copy.deepcopy(function)
except AttributeError:
self.function = function
else:
self.function = copy.deepcopy(function)
pass
elif function.owner is not None:
function = copy.deepcopy(function)

# set owner first because needed for is_initializing calls
self.function.owner = self
self.function._update_default_variable(function_variable, context)
function.owner = self
function._update_default_variable(function_variable, context)

# Specification is Function class
# Note: parameter_ports for function's parameters will be created in_instantiate_attributes_after_function
Expand Down Expand Up @@ -2899,11 +2904,13 @@ def _instantiate_function(self, function, function_params=None, context=None):
pass

_, kwargs = prune_unused_args(function.__init__, args=[], kwargs=kwargs_to_instantiate)
self.function = function(default_variable=function_variable, owner=self, **kwargs)
function = function(default_variable=function_variable, owner=self, **kwargs)

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

self.parameters.function._set(function, context)

# KAM added 6/14/18 for functions that do not pass their has_initializers status up to their owner via property
# FIX: need comprehensive solution for has_initializers; need to determine whether ports affect mechanism's
# has_initializers status
Expand Down Expand Up @@ -3155,6 +3162,77 @@ def _get_current_parameter_value(self, parameter, context=None):

return parameter._get(context)

def _try_execute_param(self, param, var, context=None):
def fill_recursively(arr, value, indices=()):
if arr.ndim == 0:
try:
value = value(context=context)
except TypeError:
try:
value = value()
except TypeError:
pass
return value

try:
len_value = len(value)
len_arr = safe_len(arr)

if len_value > len_arr:
if len_arr == len_value - 1:
ignored_items_str = f'Item {len_value - 1}'
else:
ignored_items_str = f'The items {len_arr} to {len_value - 1}'

warnings.warn(
f'The length of {value} is greater than that of {arr}.'
f'{ignored_items_str} will be ignored for index {indices}'
)
except TypeError:
# if noise value is not an iterable, ignore shape warnings
pass

for i, _ in enumerate(arr):
new_indices = indices + (i,) # for error reporting
try:
arr[i] = fill_recursively(arr[i], value[i], new_indices)
except (IndexError, TypeError):
arr[i] = fill_recursively(arr[i], value, new_indices)

return arr

var = convert_all_elements_to_np_array(var, cast_from=np.integer, cast_to=float)

# handle simple wrapping of a Component (e.g. from ParameterPort in
# case of set after Component instantiation)
if (
(isinstance(param, list) and len(param) == 1)
or (isinstance(param, np.ndarray) and param.shape == (1,))
):
if isinstance(param[0], Component):
param = param[0]

# Currently most noise functions do not return noise in the same
# shape as their variable:
if isinstance(param, Component):
try:
if param.defaults.value.shape == var.shape:
return param(context=context)
except AttributeError:
pass

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

fill_recursively(var, param)
return var

def _increment_execution_count(self, count=1):
self.parameters.execution_count.set(self.execution_count + count, override=True)
return self.execution_count
Expand Down Expand Up @@ -3768,4 +3846,4 @@ def base(self):

@base.setter
def base(self, value):
self._parameter._set(value, self._owner.most_recent_context)
self._parameter.set(value, self._owner.most_recent_context)
Loading

0 comments on commit 5bc0cf5

Please sign in to comment.