From d0f401244b2466ad53a0733ad9afc778e0ee5b1f Mon Sep 17 00:00:00 2001 From: TheLemonPig Date: Fri, 18 Aug 2023 16:13:02 -0400 Subject: [PATCH 1/9] Add Task Switching to synthetic models --- .../synthetic/neuroscience/task_switching.py | 235 ++++++++++++++++++ tests/test_bundled_models.py | 3 + 2 files changed, 238 insertions(+) create mode 100644 src/autora/experiment_runner/synthetic/neuroscience/task_switching.py diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py new file mode 100644 index 00000000..d9743e6c --- /dev/null +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -0,0 +1,235 @@ +from functools import partial +from typing import Optional + +import numpy as np + +from autora.experiment_runner.synthetic.utilities import SyntheticExperimentCollection +from autora.variable import DV, IV, ValueType, VariableCollection + + +def task_switching( + name="Task Switching", + resolution=50, + priming_default=0.3, + temperature=0.2, + minimum_task_control=0.15, + constant=1.5, + added_noise=0.01, + random_state: Optional[int] = None, +): + """ + Weber-Fechner Law + + Args: + name: name of the experiment + resolution: number of allowed values for stimulus + priming_default: default for task priming + temperature: temperature for softmax when computing performance of current task + constant: constant for task activation + minimum_task_control: minimum task control + added_noise: standard deviation of normally distributed noise added to y-values + random_state: integer used to seed the random number generator + """ + + params = dict( + name=name, + resolution=resolution, + priming_default=priming_default, + temperature=temperature, + minimum_task_control=minimum_task_control, + constant=constant, + added_noise=added_noise, + random_state=random_state, + ) + + current_task_strength = IV( + name="cur_task_strength", + allowed_values=np.linspace(1 / resolution, 1, resolution), # + value_range=(0, 1), + units="intensity", + variable_label="Strength of Current Task", + type=ValueType.REAL + ) + + alt_task_strength = IV( + name="alt_task_strength", + allowed_values=np.linspace(1 / resolution, 1, resolution), + value_range=(0, 1), + units="intensity", + variable_label="Strength of Alternative Task", + type=ValueType.REAL + ) + + is_switch = IV( + name="is_switch", + allowed_values=[0, 1], + value_range=(0, 1), + units="indicator", + variable_label="Is Switch", + type=ValueType.PROBABILITY_SAMPLE + ) + + cur_task_performance = DV( + name="cur_task_performance", + value_range=(0, 1), + units="performance", + variable_label="Accuray of Current Task", + type=ValueType.PROBABILITY + ) + + variables = VariableCollection( + independent_variables=[current_task_strength, + alt_task_strength, + is_switch], + dependent_variables=[cur_task_performance], + ) + + rng = np.random.default_rng(random_state) + + def inverse(x, A, B): + y = 1 / (A * x + B) + return y + + def experiment_runner( + X: np.ndarray, + std: float = 0.01, + ): + Y = np.zeros((X.shape[0], 1)) + for idx, x in enumerate(X): + cur_task_strength = x[0] + alt_task_strength = x[1] + is_switch = x[2] + + # determine current task control + + input_ratio = (cur_task_strength + priming_default * (1 - is_switch)) / \ + (alt_task_strength + priming_default * (is_switch)) + + cur_task_control = inverse(input_ratio, 2.61541389, 0.7042097) + cur_task_control = np.max([cur_task_control, minimum_task_control]) + + cur_task_input = cur_task_strength + \ + priming_default * (1 - is_switch) + \ + cur_task_control + \ + rng.random.normal(0, std) + + alt_task_input = alt_task_strength + \ + priming_default * (is_switch) + \ + rng.random.normal(0, std) + + cur_task_activation = 1 - np.exp(-constant * cur_task_input) + alt_task_activation = 1 - np.exp(-constant * alt_task_input) + + cur_task_performance = np.exp(cur_task_activation * 1 / temperature) / \ + (np.exp(cur_task_activation * 1 / temperature) + + np.exp(alt_task_activation * 1 / temperature)) + + # word switch + # word nonswitch + # color switch + # color nonswitch + + Y[idx] = cur_task_performance + + return Y + + ground_truth = partial(experiment_runner, std=0.0) + + def domain(): + s1_values = variables.independent_variables[0].allowed_values + s2_values = variables.independent_variables[1].allowed_values + X = np.array(np.meshgrid(s1_values, s2_values)).T.reshape(-1, 2) + # remove all combinations where s1 > s2 + X = X[X[:, 0] <= X[:, 1]] + return X + + def plotter( + model=None, + ): + X = np.zeros((4, 3)) + + # Values taken from Table 4 in Yeung & Monsell (2003) + + # word switch + X[0, 0] = 0.5 # current task strength + X[0, 1] = 0.1 # alternative task strength + # X[0, 2] = 0.2 # current task control + X[0, 2] = 1 # is switch + + # word repetition + X[1, 0] = 0.5 # current task strength + X[1, 1] = 0.1 # alternative task strength + # X[1, 2] = 0.15 # current task control + X[1, 2] = 0 # is switch + + # color switch + X[2, 0] = 0.1 # current task strength + X[2, 1] = 0.5 # alternative task strength + # X[2, 2] = 0.97 # current task control + X[2, 2] = 1 # is switch + + # color repetition + X[3, 0] = 0.1 # current task strength + X[3, 1] = 0.5 # alternative task strength + # X[3, 2] = 0.38 # current task control + X[3, 2] = 0 # is switch + + y = ground_truth(X, priming_constant=0.3, std=0) + + word_switch_performance = y[0, 0] + word_repetition_performance = y[1, 0] + color_switch_performance = y[2, 0] + color_repetition_performance = y[3, 0] + + x_data = [1, 2] + word_performance = (1 - np.array([word_repetition_performance, + word_switch_performance])) * 100 + color_performance = (1 - np.array([color_repetition_performance, + color_switch_performance])) * 100 + + if model is not None: + y_pred = model.predict(X) + word_switch_performance_pred = y_pred[0][0] + word_repetition_performance_pred = y_pred[1][0] + color_switch_performance_pred = y_pred[2][0] + color_repetition_performance_pred = y_pred[3][0] + word_performance_recovered = (1 - np.array([word_repetition_performance_pred, + word_switch_performance_pred])) * 100 + color_performance_recovered = (1 - np.array([color_repetition_performance_pred, + color_switch_performance_pred])) * 100 + + legend = ('Word Task (Original)', 'Color Task (Original)', + 'Word Task (Recovered)', 'Color Task (Recovered)',) + + # plot + import matplotlib.pyplot as plt + import matplotlib.colors as mcolors + + colors = mcolors.TABLEAU_COLORS + col_keys = list(colors.keys()) + + plt.plot(x_data, word_performance, label=legend[0], c=colors[col_keys[0]]) + plt.plot(x_data, color_performance, label=legend[1], c=colors[col_keys[1]]) + if model is not None: + plt.plot(x_data, word_performance_recovered, '--', label=legend[2], c=colors[col_keys[0]]) + plt.plot(x_data, color_performance_recovered, '--', label=legend[3], c=colors[col_keys[1]]) + plt.xlim([0.5, 2.5]) + plt.ylim([0, 50]) + plt.ylabel("Error Rate (%)", fontsize="large") + plt.legend(loc=2, fontsize="large") + plt.title("Task Switching", fontsize="large") + plt.xticks(x_data, ['Repetition', 'Switch'], rotation='horizontal') + plt.show() + + collection = SyntheticExperimentCollection( + name=name, + description=task_switching.__doc__, + variables=variables, + experiment_runner=experiment_runner, + ground_truth=ground_truth, + domain=domain, + plotter=plotter, + params=params, + factory_function=task_switching, + ) + return collection diff --git a/tests/test_bundled_models.py b/tests/test_bundled_models.py index 0f619b6b..f4b507f7 100644 --- a/tests/test_bundled_models.py +++ b/tests/test_bundled_models.py @@ -11,6 +11,9 @@ from autora.experiment_runner.synthetic.psychology.luce_choice_ratio import ( luce_choice_ratio, ) +from autora.experiment_runner.synthetic.neuroscience.task_switching import ( + task_switching, +) from autora.experiment_runner.synthetic.psychophysics.weber_fechner_law import ( weber_fechner_law, ) From 51401ffb749b6493e6cc741648bcd78b51dc6126 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:49:32 -0400 Subject: [PATCH 2/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index d9743e6c..d42d5d5f 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -27,7 +27,6 @@ def task_switching( temperature: temperature for softmax when computing performance of current task constant: constant for task activation minimum_task_control: minimum task control - added_noise: standard deviation of normally distributed noise added to y-values random_state: integer used to seed the random number generator """ From 3ad88eae2b7dd68978572ab318c62dc7cab099a1 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:49:38 -0400 Subject: [PATCH 3/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index d42d5d5f..23e40087 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -14,7 +14,6 @@ def task_switching( temperature=0.2, minimum_task_control=0.15, constant=1.5, - added_noise=0.01, random_state: Optional[int] = None, ): """ From 7c5745d4277d0eafc1e24808a5a0111461f2f8e3 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:49:43 -0400 Subject: [PATCH 4/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index 23e40087..1cffca2c 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -36,7 +36,6 @@ def task_switching( temperature=temperature, minimum_task_control=minimum_task_control, constant=constant, - added_noise=added_noise, random_state=random_state, ) From 2dfc29efe8acd5a9b43f619614159de552f54515 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:49:49 -0400 Subject: [PATCH 5/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../synthetic/neuroscience/task_switching.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index 1cffca2c..024a56c3 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -87,10 +87,11 @@ def inverse(x, A, B): y = 1 / (A * x + B) return y - def experiment_runner( - X: np.ndarray, - std: float = 0.01, +def experiment_runner( + conditions: Union[pd.DataFrame, np.ndarray, np.recarray], + observation_noise: float = 0.01, ): + X = np.array(conditions) Y = np.zeros((X.shape[0], 1)) for idx, x in enumerate(X): cur_task_strength = x[0] From b5af074769af4d632231dfc939e671b10e381a8f Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:50:30 -0400 Subject: [PATCH 6/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index 024a56c3..057f4378 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -87,7 +87,7 @@ def inverse(x, A, B): y = 1 / (A * x + B) return y -def experiment_runner( + def experiment_runner( conditions: Union[pd.DataFrame, np.ndarray, np.recarray], observation_noise: float = 0.01, ): From fa9ef41b6504aec2d9090c38c6894db5bad29fa5 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:51:23 -0400 Subject: [PATCH 7/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index 057f4378..6caa4412 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -2,6 +2,7 @@ from typing import Optional import numpy as np +import pandas as pd from autora.experiment_runner.synthetic.utilities import SyntheticExperimentCollection from autora.variable import DV, IV, ValueType, VariableCollection From 0d067eb64e0a1c0b9bd5f0613ddc00b710160700 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:53:16 -0400 Subject: [PATCH 8/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index 6caa4412..c5824977 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -1,5 +1,5 @@ from functools import partial -from typing import Optional +from typing import Optional, Union import numpy as np import pandas as pd From 9e038295cdf27338b6ea0b93c7a8de9e72f205b0 Mon Sep 17 00:00:00 2001 From: TheLemonPig <43144407+TheLemonPig@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:53:40 -0400 Subject: [PATCH 9/9] Update src/autora/experiment_runner/synthetic/neuroscience/task_switching.py --- .../experiment_runner/synthetic/neuroscience/task_switching.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py index c5824977..d3fb9441 100644 --- a/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py +++ b/src/autora/experiment_runner/synthetic/neuroscience/task_switching.py @@ -132,7 +132,7 @@ def experiment_runner( return Y - ground_truth = partial(experiment_runner, std=0.0) + ground_truth = partial(experiment_runner, observation_noise=0.0) def domain(): s1_values = variables.independent_variables[0].allowed_values