Skip to content

Commit

Permalink
Merge pull request #67 from vsoch/master
Browse files Browse the repository at this point in the history
Adding local battery deployment without psiturk
  • Loading branch information
vsoch committed Feb 25, 2016
2 parents bd8c658 + 3de391d commit 8d8f9b2
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 45 deletions.
43 changes: 41 additions & 2 deletions doc/source/deployment.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,50 @@
Deployments
===========

You should proceed with this steps after after `installation <http://expfactory.readthedocs.org/en/latest/installation.html>`_ of the Experiment Factory command line tool.

Local Battery
-------------

You can deploy a battery of experiments (meaning one or more experiments presented in a row) simply by using the expfactory command line tool. The basic function to generate a battery is the following:

::

expfactory --runbat --experiments local_global_shape,test_task

The command "--runbat" will tell the application that you want to run a local battery. The "experiments" variable is required, and should be a comma separated list of experiment unique ids (exp_id), meaning folder names in the experiment repo. If the experiment id is not found it will not trigger an error, but the experiment will not be added to your battery. You can also specify a subject id that will be embedded in the output data, and give a name for the data file (please don't use spaces):

::

expfactory --runbat --experiments local_global_shape,test_task --subid id_123


You can also specify a maximum running time (in minutes), in the case that you want to randomly select from experiments up to some time:

::

expfactory --runbat --experiments local_global_shape,test_task --time 30


The default is a very large number that (we hope) a battery would never go over. Finally, it could be the case that you want to use modified experiments, and in this case you can provide a folder of experiments as an argument:


::

expfactory --runbat --experiments local_global_shape,test_task --folder /path/to/your/expfactory-experiments


or a battery folder as an argument:

::

expfactory --runbat --experiments local_global_shape,test_task --battery /path/to/your/expfactory-battery



Psiturk
-------

You should proceed with this steps after after `installation <http://expfactory.readthedocs.org/en/latest/installation.html>`_ of the Experiment Factory command line tool.


The Experiment Factory Application Portal
.........................................
Expand Down
148 changes: 120 additions & 28 deletions expfactory/battery.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,82 @@
from expfactory.utils import copy_directory, get_template, \
sub_template, get_installdir, save_template
import numpy
import uuid
import os
import re


def generate(battery_dest,battery_repo=None,experiment_repo=None,experiments=None,config=None,make_config=True,warning=True):
def generate_base(battery_dest,experiments=None,experiment_repo=None,battery_repo=None,warning=True):
'''generate_base returns a folder with downloaded experiments and battery, either specified by the user or a temporary directory, to be used by generate_local and generate (for psiturk)
:param battery_dest: [required] is the output folder for your battery. This folder MUST NOT EXIST.
:param battery_repo: location of psiturk-battery repo to use as a template. If not specified, will be downloaded to a temporary directory
:param experiment_repo: location of a expfactory-experiments repo to check for valid experiments. If not specified, will be downloaded to a temporary directory
:param experiments: a list of experiments, meaning the "exp_id" variable in the config.json, to include. This variable also conincides with the experiment folder name.
:param warning: show warnings when validating experiments (default True)
'''
if experiment_repo == None or battery_repo == None:
tmpdir = custom_battery_download()
if experiment_repo == None:
experiment_repo = "%s/experiments" %(tmpdir)
if battery_repo == None:
battery_repo = "%s/battery" %(tmpdir)

# Copy battery skeleton to destination
copy_directory(battery_repo,battery_dest)
valid_experiments = get_experiments(experiment_repo,warning=warning)

# If the user wants to select a subset
if experiments != None:
subset_experiments = [x for x in valid_experiments if os.path.basename(x) in [os.path.basename(e) for e in experiments]]
valid_experiments = subset_experiments

return battery_repo,experiment_repo,valid_experiments

def generate_local(battery_dest,subject_id=None,battery_repo=None,experiment_repo=None,experiments=None,warning=True,time=30):
'''generate_local deploys a local battery
will create a battery from a template and list of experiments
:param battery_dest: [required] is the output folder for your battery. This folder MUST NOT EXIST.
:param battery_repo: location of psiturk-battery repo to use as a template. If not specified, will be downloaded to a temporary directory
:param experiment_repo: location of a expfactory-experiments repo to check for valid experiments. If not specified, will be downloaded to a temporary directory
:param experiments: a list of experiments, meaning the "exp_id" variable in the config.json, to include. This variable also conincides with the experiment folder name.
:param subject_id: The subject id to embed in the experiment, and the name of the results file. If none is provided, a unique ID will be generated.
:param time: Maximum amount of time for battery to endure, to select experiments
'''
# We can only generate a battery to a folder that does not exist, to be safe
if not os.path.exists(battery_dest):

battery_repo,experiment_repo,valid_experiments = generate_base(battery_dest=battery_dest,
experiments=experiments,
experiment_repo=experiment_repo,
battery_repo=battery_repo,
warning=warning)

# We will output a local battery template (without psiturk)
template_exp = "%s/templates/localbattery.html" %get_installdir()
template_exp_output = "%s/index.html" %(battery_dest)

# Generate a unique id
if subject_id == None:
subject_id = uuid.uuid4()

# Add custom variable "subject ID" to the battery - will be added to data
custom_variables = dict()
custom_variables["exp"] = [("[SUB_SUBJECT_ID_SUB]",subject_id)]
custom_variables["load"] = [("[SUB_TOTALTIME_SUB]",time)]

# Fill in templates with the experiments
template_experiments(battery_dest=battery_dest,
battery_repo=battery_repo,
valid_experiments=valid_experiments,
template_exp=template_exp,
template_exp_output=template_exp_output,
custom_variables=custom_variables)
return battery_dest
else:
print "Folder exists at %s, cannot generate." %(battery_dest)


def generate(battery_dest,battery_repo=None,experiment_repo=None,experiments=None,config=None,make_config=True,warning=True,time=30):
'''generate
will create a battery from a template and list of experiments
:param battery_dest: [required] is the output folder for your battery. This folder MUST NOT EXIST.
Expand All @@ -22,27 +93,26 @@ def generate(battery_dest,battery_repo=None,experiment_repo=None,experiments=Non
:param config: A dictionary with keys that coincide with parameters in the config.txt file for a expfactory experiment. If not provided, a dummy config will be generated.
:param make_config: A boolean (default True) to control generation of the config. If there is a config generated before calling this function, this should be set to False.
:param warning: Show config.json warnings when validating experiments. Default is True
:param time: maximum amount of time for battery to endure (default 30 minutes) to select experiments
'''
# We can only generate a battery to a folder that does not exist, to be safe
if not os.path.exists(battery_dest):
if experiment_repo == None or battery_repo == None:
tmpdir = custom_battery_download()
if experiment_repo == None:
experiment_repo = "%s/experiments" %(tmpdir)
if battery_repo == None:
battery_repo = "%s/battery" %(tmpdir)

# Copy battery skeleton to destination
copy_directory(battery_repo,battery_dest)
valid_experiments = get_experiments(experiment_repo,warning=warning)

# If the user wants to select a subset
if experiments != None:
subset_experiments = [x for x in valid_experiments if os.path.basename(x) in [os.path.basename(e) for e in experiments]]
valid_experiments = subset_experiments

# Fill in templates with the experiments, generate config
template_experiments(battery_dest,battery_repo,valid_experiments)

battery_repo,experiment_repo,valid_experiments = generate_base(battery_dest=battery_dest,
experiments=experiments,
experiment_repo=experiment_repo,
battery_repo=battery_repo,
warning=warning)
custom_variables = dict()
custom_variables["load"] = [("[SUB_TOTALTIME_SUB]",time)]

# Fill in templates with the experiments
template_experiments(battery_dest=battery_dest,
battery_repo=battery_repo,
valid_experiments=valid_experiments,
custom_variables=custom_variables)

# Generte config
if make_config:
if config == None:
config = dict()
Expand All @@ -51,20 +121,27 @@ def generate(battery_dest,battery_repo=None,experiment_repo=None,experiments=Non
print "Folder exists at %s, cannot generate." %(battery_dest)


def template_experiments(battery_dest,battery_repo,valid_experiments,template_file=None):
def template_experiments(battery_dest,battery_repo,valid_experiments,template_load=None,template_exp=None,
template_exp_output=None,custom_variables=None):
'''template_experiments:
For each valid experiment, copies the entire folder into the battery destination directory, and generates templates with appropriate paths to run them
:param battery_dest: full path to destination folder of battery
:param battery_repo: full path to psiturk-battery repo template
:param valid_experiments: a list of full paths to experiment folders to include
:param template_file: the load_experiments.js template file. If not specified, the file from the battery repo is used.
:param template_load: the load_experiments.js template file. If not specified, the file from the battery repo is used.
:param template_exp: the exp.html template file that runs load_experiment.js. If not specified, the psiturk file from the battery repo is used.
:param template_exp_output: The output file for template_exp. if not specified, the default psiturk templates/exp.html is used
:param custom_variables: A dictionary of custom variables to add to templates. Keys should either be "exp" or "load", and values should be tuples with the first index the thing to sub (eg, [SUB_THIS_SUB]) and the second the substitition to make.
'''
# Generate run template, make substitutions
if template_file == None:
template_file = "%s/static/js/load_experiments.js" %(battery_repo)
exp_template = "%s/templates/exp.html" %(battery_repo)
load_template = get_template(template_file)
exp_template = get_template(exp_template)
if template_load == None:
template_load = "%s/static/js/load_experiments.js" %(battery_repo)
if template_exp == None:
template_exp = "%s/templates/exp.html" %(battery_repo)
if template_exp_output == None:
template_exp_output = "%s/templates/exp.html" %(battery_dest)
load_template = get_template(template_load)
exp_template = get_template(template_exp)
valid_experiments = move_experiments(valid_experiments,battery_dest)
loadstatic = get_load_static(valid_experiments)
concatjs = get_concat_js(valid_experiments)
Expand All @@ -73,19 +150,34 @@ def template_experiments(battery_dest,battery_repo,valid_experiments,template_fi
exp_template = sub_template(exp_template,"[SUB_EXPERIMENTSTATIC_SUB]",loadstatic)
load_template = sub_template(load_template,"[SUB_EXPERIMENTTIMES_SUB]",str(timingjs))

# Add custom user variables
if custom_variables != None:
if "exp" in custom_variables:
exp_template = add_custom_variables(custom_variables["exp"],exp_template)
if "load" in custom_variables:
load_template = add_custom_variables(custom_variables["load"],load_template)

# load experiment scripts
template_output = "%s/static/js/load_experiments.js" %(battery_dest)
filey = open(template_output,'w')
filey.writelines(load_template)
filey.close()

# exp.html template
exp_template_output = "%s/templates/exp.html" %(battery_dest)
filey = open(exp_template_output,'w')
filey = open(template_exp_output,'w')
filey.writelines(exp_template)
filey.close()


def add_custom_variables(custom_variables,template):
'''add_custom_variables takes a list of tuples and a template, where each tuple is a ("[TAG]","substitution") and the template is an open file with the tag.
:param custom_variables: a list of tuples (see description above)
:param template: an open file to replace "tag" with "substitute"
'''
for custom_var in custom_variables:
template = sub_template(template,custom_var[0],str(custom_var[1]))
return template

def move_experiments(valid_experiments,battery_dest):
'''move_experiments
Moves valid experiments into the experiments folder in battery repo
Expand Down
31 changes: 27 additions & 4 deletions expfactory/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Runtime executable
'''
from expfactory.views import preview_experiment
from expfactory.views import preview_experiment, run_battery
from expfactory.experiment import validate
from glob import glob
import argparse
Expand All @@ -15,10 +15,14 @@
def main():
parser = argparse.ArgumentParser(
description="generate experiments and infrastructure to serve them.")
parser.add_argument("--folder", dest='folder', help="full path to experiment folder", type=str, default=None)
parser.add_argument("--folder", dest='folder', help="full path to single experiment folder (for single experiment run with --run) or folder with many experiments (for battery run with --runbat)", type=str, default=None)
parser.add_argument("--subid", dest='subid', help="subject id to embed in experiments data in the case of a battery run with --runbat", type=str, default=None)
parser.add_argument("--experiments", dest='experiments', help="comma separated list of experiments for a local battery", type=str, default=None)
parser.add_argument("--port", dest='port', help="port to preview experiment", type=int, default=None)
parser.add_argument("--battery", dest='battery_folder', help="full path to local battery folder to use as template", type=str, default=None)
parser.add_argument("--time", dest='time', help="maximum number of minutes for battery to endure, to select experiments", type=int, default=99999)
parser.add_argument('--run', help="run an experiment locally", dest='run', default=False, action='store_true')
parser.add_argument('--runbat', help="run a battery locally", dest='runbat', default=False, action='store_true')
parser.add_argument('--validate', dest='validate', help="validate an experiment folder", default=False, action='store_true')
parser.add_argument('--test', dest='test', help="test an experiment folder with the experiment robot", default=False, action='store_true')

Expand All @@ -27,15 +31,34 @@ def main():
except:
parser.print_help()
sys.exit(0)
# Check if the person wants to preview experiment

# Check if the person wants to preview experiment or battery
if args.run == True:
preview_experiment(folder=args.folder,battery_folder=args.battery_folder,port=args.port)

# Run a local battery
elif args.runbat == True:
if args.experiments != None:
experiments = args.experiments.split(",")
run_battery(experiments=experiments,
experiment_folder=args.folder,
subject_id=args.subid,
battery_folder=args.battery_folder,
port=args.port,
time=args.time)
else:
print "Please specify list of comma separated experiments with --experiments"

# Validate a config.json
elif args.validate == True:
validate(experiment_folder=args.folder)

# Run the experiment robot
elif args.test == True:
from expfactory.tests import test_experiment
test_experiment(folder=args.folder,battery_folder=args.battery_folder,port=args.port)

# Otherwise, just open the expfactory interface
else:
from expfactory.interface import start
start(port=args.port)
Expand Down

0 comments on commit 8d8f9b2

Please sign in to comment.