Skip to content

Commit

Permalink
Merge pull request #5 from AutoResearch/4-add-bandit-task
Browse files Browse the repository at this point in the history
update: clear outputs of jupiter notebook
  • Loading branch information
younesStrittmatter authored Oct 13, 2024
2 parents 4fc1faa + 99bf3ce commit 2529e0d
Show file tree
Hide file tree
Showing 5 changed files with 428 additions and 0 deletions.
8 changes: 8 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def create_autora_example_project(requirements_file):
questions = [inquirer.List('project_type',
message='What type of project do you want to create?',
choices=['Blank', 'JsPsych - Stroop', 'JsPsych - RDK',
'JsPsych - Bandit',
'SuperExperiment', 'SweetBean',
'Mathematical Model Discovery']
)]
Expand All @@ -122,6 +123,11 @@ def create_autora_example_project(requirements_file):
f.write(f'\nsweetbean')
if answers['project_type'] == 'Mathematical Model Discovery':
example_file = 'mathematical_model_discovery'
if answers['project_type'] == 'JsPsych - Bandit':
example_file = 'js_psych_bandit'
parent = os.path.join(os.getcwd(), 'testing_zone/css')
os.mkdir(parent)
shutil.move(f'example_css/js_psych_bandit.css', 'testing_zone/css/slot-machine.css')

shutil.move(f'example_mains/{example_file}.js', 'testing_zone/src/design/main.js')
shutil.move(f'example_workflows/{example_file}.py', 'researcher_hub/autora_workflow.py')
Expand All @@ -133,6 +139,8 @@ def create_autora_example_project(requirements_file):
shutil.rmtree(to_remove)
to_remove = os.path.join(os.getcwd(), 'example_mains')
shutil.rmtree(to_remove)
to_remove = os.path.join(os.getcwd(), 'example_css')
shutil.rmtree(to_remove)
to_remove = os.path.join(os.getcwd(), 'readmes')
shutil.rmtree(to_remove)

Expand Down
Empty file.
122 changes: 122 additions & 0 deletions {{ cookiecutter.__project_slug }}/example_mains/js_psych_bandit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// To use the jsPsych package first install jspsych using `npm install jspsych`
// This example uses the rdk plugin. Install it via `npm install @jspsych-contrib/plugin-rdk`
// This example uses the html-keyboard-response plugin. Install it via `npm install @jspsych/plugin-html-keyboard-response`
// Here is documentation on how to program a jspsych experiment using npm:
// https://www.jspsych.org/7.3/tutorials/hello-world/#option-3-using-npm

import {initJsPsych} from 'jspsych';
import 'jspsych/css/jspsych.css'
import htmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response';
import jsPsychHtmlChoice from '@jspsych-contrib/plugin-html-choice';
import fullscreen from '@jspsych/plugin-fullscreen';
import '../css/slot-machine.css'


/**
* This is the main function where you program your experiment. Install jsPsych via node and
* use functions from there
* @param id this is a number between 0 and number of participants. You can use it for example to counterbalance between subjects
* @param condition this is a condition (4-32. Here we want to find out how the training length impacts the accuracy in a testing phase)
* @returns {Promise<*>} the accuracy in the post-trainging phase relative to the pre-training phase
*/
const main = async (id, condition) => {

const timeline_variables = JSON.parse(condition)
const nr_trials = timeline_variables.length
let current_trial = 0

const jsPsych = initJsPsych({
show_progress_bar: true,
auto_update_progress_bar: false,
on_finish: function () {
jsPsych.data.displayData();
}
});

// create timeline (enter fullscreen)
const timeline = [{
type: fullscreen,
fullscreen_mode: true
}];


// create html divs
// let sm_blue = '<div style="position: absolute; top:0; left:0" class="slotmachine blue"></div>'
// let sm_red = '<div style="position: absolute; top:0; right:0" class="slotmachine red"></div>'

const sm_blue = (pos) => {
let _pos = 'left: 0'
if (pos === 'right') {
_pos = 'right: 0'
}
return `<div style="position: absolute; top:10vh; height: 60vh; ${_pos}" class="slotmachine blue"></div>`
}
const sm_red = (pos) => {
let _pos = 'left: 0'
if (pos === 'right') {
_pos = 'right: 0'
}
return `<div style="position: absolute; top:10vh; height: 60vh; ${_pos}" class="slotmachine red"></div>`
}
let score = 0

const test = {
timeline: [
{
type: jsPsychHtmlChoice,
html_array: () => {
return [
sm_blue(jsPsych.timelineVariable('pos')[0]),
sm_red(jsPsych.timelineVariable('pos')[1])
]
},
on_load: () => {
let content = document.getElementById('jspsych-content')
let score_div = document.createElement('div')
score_div.style.position = 'fixed'
score_div.style.left = '50vw'
score_div.style.bottom = '3vh'
score_div.style.transform = 'translateX(-50%)'
score_div.innerText = `Score: ${score} of ${nr_trials}`
content.appendChild(score_div)
},
trial_duration: null,
values: jsPsych.timelineVariable('values'),
response_ends_trial: true,
time_after_response: 800,
on_finish: (data) => {
current_trial += 1
score += data.value
let progress = current_trial / nr_trials
jsPsych.setProgressBar(progress)
}
},
{
type: htmlKeyboardResponse,
stimulus: '',
trial_duration: 100,
on_load: () => {
let content = document.getElementById('jspsych-content')
let score_div = document.createElement('div')
score_div.style.position = 'fixed'
score_div.style.left = '50vw'
score_div.style.bottom = '3vh'
score_div.innerText = `Score: ${score} of ${nr_trials}`
score_div.style.transform = 'translateX(-50%)'
content.appendChild(score_div)
},
},
],
timeline_variables: timeline_variables
}

timeline.push(test)

await jsPsych.run(timeline)

const observation = jsPsych.data.get().filter({trial_type: "html-choice"}).select('response')
return JSON.stringify({condition: condition, observation: observation})
}


export default main
133 changes: 133 additions & 0 deletions {{ cookiecutter.__project_slug }}/example_workflows/js_psych_bandit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
Basic Workflow
Single condition Variable (0-1), Single Observation Variable(0-1)
Theorist: LinearRegression
Experimentalist: Random Sampling
Runner: Firebase Runner (no prolific recruitment)
"""
import json

from autora.variable import VariableCollection, Variable
from autora.experimentalist.random import pool
from autora.experiment_runner.firebase_prolific import firebase_runner
from autora.state import StandardState, on_state, Delta

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

# *** Set up variables *** #
# independent variable is coherence in percent (0 - 100)
# dependent variable is accuracy (0 - 1)
variables = VariableCollection(
independent_variables=[Variable(name="coherence", allowed_values=np.linspace(0, 1, 101))],
dependent_variables=[Variable(name="accuracy", value_range=(0, 1))])

# *** State *** #
# With the variables, we can set up a state. The state object represents the state of our
# closed loop experiment.


state = StandardState(
variables=variables,
)

# *** Components/Agents *** #
# Components are functions that run on the state. The main components are:
# - theorist
# - experiment-runner
# - experimentalist
# See more about components here: https://autoresearch.github.io/autora/


# ** Theorist ** #
# Here we use a linear regression as theorist, but you can use other theorists included in
# autora (for a list: https://autoresearch.github.io/autora/theorist/)

theorist = LinearRegression()

# To use the theorist on the state object, we wrap it with the on_state functionality and return a
# Delta object.
# Note: The if the input arguments of the theorist_on_state function are state-fields like
# experiment_data, variables, ... , then using this function on a state object will automatically
# use those state fields.
# The output of these functions is always a Delta object. The keyword argument in this case, tells
# the state object witch field to update.


@on_state()
def theorist_on_state(experiment_data, variables):
ivs = [iv.name for iv in variables.independent_variables]
dvs = [dv.name for dv in variables.dependent_variables]
x = experiment_data[ivs]
y = experiment_data[dvs]
return Delta(models=[theorist.fit(x, y)])

# ** Experimentalist ** #
# Here, we use a random pool and use the wrapper to create a on state function
# Note: The argument num_samples is not a state field. Instead, we will pass it in when calling
# the function


@on_state()
def experimentalist_on_state(variables, num_samples):
return Delta(conditions=pool(variables, num_samples))

# ** Experiment Runner ** #
# We will run our experiment on firebase and need credentials. You will find them here:
# (https://console.firebase.google.com/)
# -> project -> project settings -> service accounts -> generate new private key

firebase_credentials = {
"type": "type",
"project_id": "project_id",
"private_key_id": "private_key_id",
"private_key": "private_key",
"client_email": "client_email",
"client_id": "client_id",
"auth_uri": "auth_uri",
"token_uri": "token_uri",
"auth_provider_x509_cert_url": "auth_provider_x509_cert_url",
"client_x509_cert_url": "client_x509_cert_url"
}

# simple experiment runner that runs the experiment on firebase
experiment_runner = firebase_runner(
firebase_credentials=firebase_credentials,
time_out=100,
sleep_time=5)


# Again, we need to wrap the runner to use it on the state. Here, we send the raw conditions.
@on_state()
def runner_on_state(conditions):
data = experiment_runner(conditions)
# Here, parse the return value of the runner. The return value depends on the specific
# implementation of your online experiment (see testing_zone/src/design/main.js).
# In this example, the experiment runner returns a list of strings, that contain json formatted
# dictionaries.
# Example:
# data = ['{'coherence':.3, accuracy':.8}', ...]
result = []
for item in data:
result.append(json.loads(item))
return Delta(experiment_data=pd.DataFrame(result))


# Now, we can run our components
for _ in range(3):
state = experimentalist_on_state(state, num_samples=2) # Collect 2 conditions per iteration
state = runner_on_state(state)
state = theorist_on_state(state)


# *** Report the data *** #
# If you changed the theorist, also change this part
def report_linear_fit(m: LinearRegression, precision=4):
s = f"y = {np.round(m.coef_[0].item(), precision)} x " \
f"+ {np.round(m.intercept_.item(), 4)}"
return s


print(report_linear_fit(state.models[0]))
print(report_linear_fit(state.models[-1]))
Loading

0 comments on commit 2529e0d

Please sign in to comment.