From 2ebfef6c74d7f3cf6c902cfe55a56615f82f6458 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 18 Mar 2018 02:16:43 -0400 Subject: [PATCH 1/4] Function, LinearCombination: Refactor to improve code reuse Signed-off-by: Jan Vesely --- psyneulink/components/functions/function.py | 53 ++++++++------------- 1 file changed, 19 insertions(+), 34 deletions(-) diff --git a/psyneulink/components/functions/function.py b/psyneulink/components/functions/function.py index 2487e0a4792..d5dc3f8e524 100644 --- a/psyneulink/components/functions/function.py +++ b/psyneulink/components/functions/function.py @@ -2155,45 +2155,30 @@ def function(self, variable = self._update_variable(variable * weights) # CALCULATE RESULT USING RELEVANT COMBINATION OPERATION AND MODULATION - if operation is SUM: - if isinstance(scale, numbers.Number): - # Scalar scale and offset - if isinstance(offset, numbers.Number): - result = np.sum(variable, axis=0) * scale + offset - # Scalar scale and Hadamard offset - else: - result = np.sum(np.append([variable * scale], [offset], axis=0), axis=0) - else: - # Hadamard scale, scalar offset - if isinstance(offset, numbers.Number): - result = np.product([np.sum([variable], axis=0), scale], axis=0) + offset - # Hadamard scale and offset - else: - hadamard_product = np.product([np.sum([variable], axis=0), scale], axis=0) - result = np.sum(np.append([hadamard_product], [offset], axis=0), axis=0) - + combination = np.sum(variable, axis=0) elif operation is PRODUCT: - product = np.product([variable], axis=0) - if isinstance(scale, numbers.Number): - # Scalar scale and offset - if isinstance(offset, numbers.Number): - result = product * scale + offset - # Scalar scale and Hadamard offset - else: - result = np.sum(np.append([product * scale], [offset], axis=0), axis=0) - else: - # Hadamard scale, scalar offset - if isinstance(offset, numbers.Number): - result = np.product([product, scale], axis=0) + offset - # Hadamard scale and offset - else: - hadamard_product = np.product(np.append([product], [scale], axis=0), axis=0) - result = np.sum(np.append([hadamard_product], [offset], axis=0), axis=0) - + combination = np.product(variable, axis=0) else: raise FunctionError("Unrecognized operator ({0}) for LinearCombination function". format(operation.self.Operation.SUM)) + if isinstance(scale, numbers.Number): + product = combination * scale + # Scalar scale and offset + if isinstance(offset, numbers.Number): + result = product + offset + # Scalar scale and Hadamard offset + else: + result = np.sum([product, offset], axis=0) + else: + hadamard_product = np.product([combination, scale], axis=0) + # Hadamard scale, scalar offset + if isinstance(offset, numbers.Number): + result = hadamard_product + offset + # Hadamard scale and offset + else: + result = np.sum([hadamard_product, offset], axis=0) + return result @property From ccea48decf60fe182240dec0929cd8f5b1b9c1da Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 18 Mar 2018 02:17:00 -0400 Subject: [PATCH 2/4] Function, LinearCombination: More code reuse refactoring Signed-off-by: Jan Vesely --- psyneulink/components/functions/function.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/psyneulink/components/functions/function.py b/psyneulink/components/functions/function.py index d5dc3f8e524..621fd74b087 100644 --- a/psyneulink/components/functions/function.py +++ b/psyneulink/components/functions/function.py @@ -2138,17 +2138,19 @@ def function(self, # FIX FOR EFFICIENCY: CHANGE THIS AND WEIGHTS TO TRY/EXCEPT // OR IS IT EVEN NECESSARY, GIVEN VALIDATION ABOVE?? # Apply exponents if they were specified if exponents is not None: + try: + variable = self._update_variable(variable ** exponents) # Avoid divide by zero warning: # make sure there are no zeros for an element that is assigned a negative exponent - # Allow during initialization because 0s are common in default_variable argument - if context is not None and INITIALIZING in context: # cxt-test - try: - variable = self._update_variable(variable ** exponents) - except ZeroDivisionError: + except ZeroDivisionError: + # Allow during initialization because 0s are common in + # default_variable argument + if context is not None and INITIALIZING in context: # cxt-test variable = self._update_variable(np.ones_like(variable)) - else: - # if this fails with ZeroDivisionError it should not be caught outside of initialization - variable = self._update_variable(variable ** exponents) + else: + # if this fails with ZeroDivisionError it should not be caught + # outside of initialization + raise # Apply weights if they were specified if weights is not None: From ab8df90bcab1fc22210eabd5e4b5d3e0fb300d3b Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Sun, 18 Mar 2018 18:05:29 -0400 Subject: [PATCH 3/4] tests, LinearCombination: Use custom naming function instead of name list Signed-off-by: Jan Vesely --- tests/functions/test_combination.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/tests/functions/test_combination.py b/tests/functions/test_combination.py index f5e64719304..ee89c5890f4 100644 --- a/tests/functions/test_combination.py +++ b/tests/functions/test_combination.py @@ -90,22 +90,24 @@ def test_matrix(self): (Function.LinearCombination, test_var, {'scale':RAND1_V, 'offset':RAND2_V, 'operation':pnl.PRODUCT}, test_var * RAND1_V + RAND2_V), ] -# use list, naming function produces ugly names -linear_combination_names = [ - "COMBINE-1 SUM", - "COMBINE-1 SUM VECTOR OFFSET", - "COMBINE-1 SUM VECTOR SCALE", - "COMBINE-1 SUM VECTOR OFFSET SCALE", - - "COMBINE-1 PRODUCT", - "COMBINE-1 PRODUCT VECTOR OFFSET", - "COMBINE-1 PRODUCT VECTOR SCALE", - "COMBINE-1 PRODUCT VECTOR OFFSET SCALE", -] +# pytest naming function produces ugly names +def _naming_function(config): + _, var, params, _ = config + inputs = var.shape[0] + op = params['operation'] + vector_string = "" + if not np.isscalar(params['scale']): + vector_string += " SCALE" + if not np.isscalar(params['offset']): + vector_string += " OFFSET" + if vector_string != "": + vector_string = " VECTOR" + vector_string + return "COMBINE-{} {}{}".format(inputs, op, vector_string) + @pytest.mark.function @pytest.mark.combination_function -@pytest.mark.parametrize("func, variable, params, expected", test_linear_combination_data, ids=linear_combination_names) +@pytest.mark.parametrize("func, variable, params, expected", test_linear_combination_data, ids=list(map(_naming_function, test_linear_combination_data))) @pytest.mark.benchmark def test_linear_combination_function(func, variable, params, expected, benchmark): f = func(default_variable=variable, **params) From d679fcbc3fc912e32124972d904432887ce1d572 Mon Sep 17 00:00:00 2001 From: Jan Vesely Date: Mon, 19 Mar 2018 17:18:39 -0400 Subject: [PATCH 4/4] test, LinearCombination Function: Add combination step tests Signed-off-by: Jan Vesely --- tests/functions/test_combination.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/functions/test_combination.py b/tests/functions/test_combination.py index ee89c5890f4..d33a9d037e6 100644 --- a/tests/functions/test_combination.py +++ b/tests/functions/test_combination.py @@ -69,6 +69,7 @@ def test_matrix(self): SIZE=5 #This gives us the correct 2d array test_var = np.random.rand(1, SIZE) +test_var2 = np.random.rand(2, SIZE) RAND1_V = np.random.rand(1, SIZE) RAND2_V = np.random.rand(1, SIZE) @@ -88,6 +89,17 @@ def test_matrix(self): (Function.LinearCombination, test_var, {'scale':RAND1_S, 'offset':RAND2_V, 'operation':pnl.PRODUCT}, test_var * RAND1_S + RAND2_V), (Function.LinearCombination, test_var, {'scale':RAND1_V, 'offset':RAND2_S, 'operation':pnl.PRODUCT}, test_var * RAND1_V + RAND2_S), (Function.LinearCombination, test_var, {'scale':RAND1_V, 'offset':RAND2_V, 'operation':pnl.PRODUCT}, test_var * RAND1_V + RAND2_V), + + (Function.LinearCombination, test_var2, {'scale':RAND1_S, 'offset':RAND2_S, 'operation':pnl.SUM}, np.sum(test_var2, axis=0) * RAND1_S + RAND2_S), +# TODO: enable vector scale/offset when the validation is fixed +# (Function.LinearCombination, test_var2, {'scale':RAND1_S, 'offset':RAND2_V, 'operation':pnl.SUM}, np.sum(test_var2, axis=0) * RAND1_S + RAND2_V), +# (Function.LinearCombination, test_var2, {'scale':RAND1_V, 'offset':RAND2_S, 'operation':pnl.SUM}, np.sum(test_var2, axis=0) * RAND1_V + RAND2_S), +# (Function.LinearCombination, test_var2, {'scale':RAND1_V, 'offset':RAND2_V, 'operation':pnl.SUM}, np.sum(test_var2, axis=0) * RAND1_V + RAND2_V), + + (Function.LinearCombination, test_var2, {'scale':RAND1_S, 'offset':RAND2_S, 'operation':pnl.PRODUCT}, np.product(test_var2, axis=0) * RAND1_S + RAND2_S), +# (Function.LinearCombination, test_var2, {'scale':RAND1_S, 'offset':RAND2_V, 'operation':pnl.PRODUCT}, np.product(test_var2, axis=0) * RAND1_S + RAND2_V), +# (Function.LinearCombination, test_var2, {'scale':RAND1_V, 'offset':RAND2_S, 'operation':pnl.PRODUCT}, np.product(test_var2, axis=0) * RAND1_V + RAND2_S), +# (Function.LinearCombination, test_var2, {'scale':RAND1_V, 'offset':RAND2_V, 'operation':pnl.PRODUCT}, np.product(test_var2, axis=0) * RAND1_V + RAND2_V), ] # pytest naming function produces ugly names