experiment_server 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- experiment_server/__init__.py +31 -0
- experiment_server/_api.py +200 -0
- experiment_server/_client.py +76 -0
- experiment_server/_participant_ordering.py +127 -0
- experiment_server/_process_config.py +348 -0
- experiment_server/_server.py +403 -0
- experiment_server/cli.py +72 -0
- experiment_server/static/css/bootstrap-5.2.3.min.css +7 -0
- experiment_server/static/index.html +217 -0
- experiment_server/static/initconfig.html +143 -0
- experiment_server/static/js/alpinejs3.min.js +5 -0
- experiment_server/static/js/bootstrap-4.5.0.min.js +7 -0
- experiment_server/static/js/fontawesome-1e694dd391.js +2 -0
- experiment_server/static/js/htmx-1.9.10.js +1 -0
- experiment_server/static/js/jquery-3.5.1.min.js +2 -0
- experiment_server/static/js/popper-1.16.0.min.js +5 -0
- experiment_server/static/js/sweetalert2-11.js +6 -0
- experiment_server/utils.py +99 -0
- experiment_server-0.3.0.dist-info/LICENSE.md +21 -0
- experiment_server-0.3.0.dist-info/METADATA +295 -0
- experiment_server-0.3.0.dist-info/RECORD +24 -0
- experiment_server-0.3.0.dist-info/WHEEL +4 -0
- experiment_server-0.3.0.dist-info/entry_points.txt +3 -0
- sample_config.toml +57 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from importlib_metadata import version
|
|
2
|
+
import logging
|
|
3
|
+
from loguru import logger
|
|
4
|
+
|
|
5
|
+
__version__ = version(__package__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class __InterceptHandler(logging.Handler):
|
|
9
|
+
def emit(self, record):
|
|
10
|
+
# Get corresponding Loguru level if it exists
|
|
11
|
+
try:
|
|
12
|
+
level = logger.level(record.levelname).name
|
|
13
|
+
except ValueError:
|
|
14
|
+
level = record.levelno
|
|
15
|
+
|
|
16
|
+
# Find caller from where originated the logged message
|
|
17
|
+
frame, depth = logging.currentframe(), 2
|
|
18
|
+
while frame.f_code.co_filename == logging.__file__:
|
|
19
|
+
frame = frame.f_back
|
|
20
|
+
depth += 1
|
|
21
|
+
|
|
22
|
+
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(handlers=[__InterceptHandler()], level=0)
|
|
26
|
+
|
|
27
|
+
from experiment_server._server import server_process
|
|
28
|
+
from experiment_server._client import Client
|
|
29
|
+
from experiment_server._api import Experiment
|
|
30
|
+
|
|
31
|
+
__all__ = [server_process, Client, Experiment]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from sys import stdout
|
|
2
|
+
from typing import Any, Dict, Iterable, List, Union
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from experiment_server._process_config import process_config_file
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from experiment_server.utils import ExperimentServerExcetion, FileModifiedWatcher
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ParticipantState:
|
|
13
|
+
def __init__(self, config, participant_index, active):
|
|
14
|
+
self.participant_index = participant_index
|
|
15
|
+
self._block_id: int = -1
|
|
16
|
+
self.config = config
|
|
17
|
+
self.active = active # if not started or has ended, then this will be False
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def block_id(self) -> int:
|
|
21
|
+
return self._block_id
|
|
22
|
+
|
|
23
|
+
@block_id.setter
|
|
24
|
+
def block_id(self, block_id: int):
|
|
25
|
+
if block_id < 0:
|
|
26
|
+
self._block_id = -1
|
|
27
|
+
self.active = False
|
|
28
|
+
elif block_id >= len(self.config):
|
|
29
|
+
self._block_id = len(self.config)
|
|
30
|
+
self.active = False
|
|
31
|
+
else:
|
|
32
|
+
self.active = True
|
|
33
|
+
self._block_id = block_id
|
|
34
|
+
if self.active:
|
|
35
|
+
assert self._block_id == self.config[self._block_id]["config"]["block_id"]
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def block(self) -> Dict[str, Any]|None:
|
|
39
|
+
if self.block_id >= len(self.config) or self.block_id < 0:
|
|
40
|
+
return None
|
|
41
|
+
else:
|
|
42
|
+
return self.config[self.block_id]
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def block_name(self) -> str:
|
|
46
|
+
if self.block_id >= len(self.config):
|
|
47
|
+
return "END"
|
|
48
|
+
elif self.block_id < 0:
|
|
49
|
+
return "START"
|
|
50
|
+
return self.config[self.block_id]["name"]
|
|
51
|
+
|
|
52
|
+
def move_to_next_block(self) -> str:
|
|
53
|
+
self.block_id += 1
|
|
54
|
+
return self.block_name
|
|
55
|
+
|
|
56
|
+
def status_string(self):
|
|
57
|
+
return f'Participant index: {self.participant_index} \nBlock: {self._block_id} / {len(self.config)} \n Name: {self.block["name"] if self.block is not None else "N/A"}'
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Experiment:
|
|
61
|
+
"""Load and manage experiemnt from a local file."""
|
|
62
|
+
def __init__(self, config_file:str, default_participant_index:int=1) -> None:
|
|
63
|
+
assert default_participant_index > 0, "Default participant index should be >0"
|
|
64
|
+
self.global_state: Dict[int, ParticipantState] = {}
|
|
65
|
+
self.default_participant_index = default_participant_index
|
|
66
|
+
self.watchdog = FileModifiedWatcher(config_file, self._config_file_modified_callback)
|
|
67
|
+
self.config_file = config_file
|
|
68
|
+
|
|
69
|
+
if default_participant_index not in self.global_state:
|
|
70
|
+
self.global_state[default_participant_index] = ParticipantState(process_config_file(config_file, default_participant_index),
|
|
71
|
+
default_participant_index,
|
|
72
|
+
False)
|
|
73
|
+
|
|
74
|
+
def _config_file_modified_callback(self):
|
|
75
|
+
logger.info("Reloading config")
|
|
76
|
+
for participantState in self.global_state.values():
|
|
77
|
+
config = process_config_file(self.config_file, participantState.participant_index)
|
|
78
|
+
participantState.config = config
|
|
79
|
+
|
|
80
|
+
def get_next_participant(self) -> int:
|
|
81
|
+
"""Return the index of the next participant."""
|
|
82
|
+
new_participant_index = max(self.global_state.keys()) + 1
|
|
83
|
+
self.add_participant_index(new_participant_index)
|
|
84
|
+
return new_participant_index
|
|
85
|
+
|
|
86
|
+
def add_participant_index(self, participant_index) -> bool:
|
|
87
|
+
"""Add a participant with `participant_index`.
|
|
88
|
+
If `participant_index` already exists, returns False, else return True"""
|
|
89
|
+
if participant_index in self.global_state:
|
|
90
|
+
return False
|
|
91
|
+
self.global_state[participant_index] = ParticipantState(process_config_file(self.config_file, participant_index),
|
|
92
|
+
participant_index,
|
|
93
|
+
False)
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
def get_participant_state(self, participant_index) -> ParticipantState:
|
|
97
|
+
if participant_index is None:
|
|
98
|
+
participant_index = self.default_participant_index
|
|
99
|
+
if participant_index not in self.global_state:
|
|
100
|
+
raise ExperimentServerExcetion(f"participant with index {participant_index} is not set. Consider using `add_participant_index`")
|
|
101
|
+
return self.global_state[participant_index]
|
|
102
|
+
|
|
103
|
+
def get_state(self, participant_index:int|None=None):
|
|
104
|
+
if participant_index is None:
|
|
105
|
+
participant_index = self.default_participant_index
|
|
106
|
+
return self.global_state[participant_index].active
|
|
107
|
+
|
|
108
|
+
def move_to_next(self, participant_index:int|None=None) -> str:
|
|
109
|
+
"""Moves the pointer to the current block to the next block for `participant_index`.
|
|
110
|
+
if `participant_index` is None, seld.default_participant_index is used.
|
|
111
|
+
"""
|
|
112
|
+
if participant_index is None:
|
|
113
|
+
participant_index = self.default_participant_index
|
|
114
|
+
return self.global_state[participant_index].move_to_next_block()
|
|
115
|
+
|
|
116
|
+
def get_config(self, participant_index:int|None=None) -> Union[Dict[str, Any], None]:
|
|
117
|
+
"""Return the config of the current block for `participant_index`.
|
|
118
|
+
if `participant_index` is None, seld.default_participant_index is used.
|
|
119
|
+
If the experiment has not started (`move_to_next` has not
|
|
120
|
+
been called atleast once), this will return `None`."""
|
|
121
|
+
if participant_index is None:
|
|
122
|
+
participant_index = self.default_participant_index
|
|
123
|
+
block = self.global_state[participant_index].block
|
|
124
|
+
if block is None:
|
|
125
|
+
return None
|
|
126
|
+
else:
|
|
127
|
+
return block["config"]
|
|
128
|
+
|
|
129
|
+
def reset_participant(self, participant_index:int|None=None) -> bool:
|
|
130
|
+
"""Reset the participant's config to that was loaded from the file."""
|
|
131
|
+
if participant_index is None:
|
|
132
|
+
participant_index = self.default_participant_index
|
|
133
|
+
self.global_state[participant_index].config = process_config_file(self.config_file, participant_index)
|
|
134
|
+
return True
|
|
135
|
+
|
|
136
|
+
def get_blocks_count(self, participant_index:int|None=None) -> int:
|
|
137
|
+
"""Return the total number of blocks."""
|
|
138
|
+
if participant_index is None:
|
|
139
|
+
participant_index = self.default_participant_index
|
|
140
|
+
return len(self.global_state[participant_index].config)
|
|
141
|
+
|
|
142
|
+
def get_all_configs(self, participant_index:int|None=None) -> List[dict]:
|
|
143
|
+
"""Return all configs in order for `participant_index`.
|
|
144
|
+
if `participant_index` is None, seld.default_participant_index is used.
|
|
145
|
+
"""
|
|
146
|
+
if participant_index is None:
|
|
147
|
+
participant_index = self.default_participant_index
|
|
148
|
+
return [c["config"] for c in self.global_state[participant_index].config]
|
|
149
|
+
|
|
150
|
+
def move_to_block(self, block_id: int, participant_index:int|None=None) -> str:
|
|
151
|
+
"""For `participant_index` move the pointer of the current
|
|
152
|
+
block to the block in index a `block_id` in the list of
|
|
153
|
+
blocks. If `participant_index` is None, seld.default_participant_index is used.
|
|
154
|
+
"""
|
|
155
|
+
assert isinstance(block_id, int), "`block` should be an int"
|
|
156
|
+
if participant_index is None:
|
|
157
|
+
participant_index = self.default_participant_index
|
|
158
|
+
self.global_state[participant_index].block_id = block_id
|
|
159
|
+
return self.global_state[participant_index].block_name
|
|
160
|
+
|
|
161
|
+
def move_all_to_block(self, block_id: int) -> str:
|
|
162
|
+
"""For active participants move the pointer of the current
|
|
163
|
+
block to the block in index a `block_id` in the list of
|
|
164
|
+
blocks.
|
|
165
|
+
"""
|
|
166
|
+
assert isinstance(block_id, int), "`block` should be an int"
|
|
167
|
+
for participantState in self.global_state.values():
|
|
168
|
+
participantState.block_id = block_id
|
|
169
|
+
return list(self.global_state.values())[0].block_name
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _generate_config_json(config_file: Union[str, Path], participant_indices:Iterable[int], out_dir: Union[str, Path, None] = None) -> None:
|
|
173
|
+
"""
|
|
174
|
+
Write out the json config from the `config_file` for participants in `participant_indices`
|
|
175
|
+
in the `out_dir`. The `out_dir` should be a directory or should be None.
|
|
176
|
+
If it is None, it will write the config's, one line per participant. If the passed directory
|
|
177
|
+
does not exist, it will be created.
|
|
178
|
+
"""
|
|
179
|
+
if out_dir is not None:
|
|
180
|
+
out_dir = Path(out_dir)
|
|
181
|
+
if not out_dir.exists():
|
|
182
|
+
logger.info(f"Creating direcotry {out_dir}")
|
|
183
|
+
out_dir.mkdir(parents=True)
|
|
184
|
+
elif not out_dir.is_dir():
|
|
185
|
+
raise ExperimentServerExcetion(f"`out_file_location` should be a directory. Got {out_dir}")
|
|
186
|
+
|
|
187
|
+
out_files = []
|
|
188
|
+
for participant_index in participant_indices:
|
|
189
|
+
config = process_config_file(config_file, participant_index, supress_message=True)
|
|
190
|
+
if out_dir is not None:
|
|
191
|
+
out_file = Path(out_dir) / f"{Path(config_file).stem}-participant_{participant_index}.json"
|
|
192
|
+
out_files.append(out_file)
|
|
193
|
+
|
|
194
|
+
with open(out_file, "w") as f:
|
|
195
|
+
json.dump([c["config"] for c in config], f, indent=2)
|
|
196
|
+
else:
|
|
197
|
+
stdout.write(json.dumps([c["config"] for c in config]))
|
|
198
|
+
|
|
199
|
+
if len(out_files) != 0:
|
|
200
|
+
logger.info("Generated files: \n" + "\n".join([str(f) for f in out_files]))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Tuple, Union
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from experiment_server.utils import ExperimentServerExcetion
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Client:
|
|
9
|
+
def __init__(self, server_host:str ="127.0.0.1", server_port:Union[str, int]="5000") -> None:
|
|
10
|
+
self._server_url = f"http://{server_host}:{server_port}"
|
|
11
|
+
|
|
12
|
+
def _request(self, end_point:str, verb:str) -> Tuple[bool, dict]:
|
|
13
|
+
url = self._server_url + f"/api/{end_point}"
|
|
14
|
+
if verb == "GET":
|
|
15
|
+
r = requests.get(url)
|
|
16
|
+
elif verb == "POST":
|
|
17
|
+
r = requests.post(url)
|
|
18
|
+
elif verb == "PUT":
|
|
19
|
+
r = requests.put(url)
|
|
20
|
+
else:
|
|
21
|
+
raise ExperimentServerExcetion("huh?")
|
|
22
|
+
|
|
23
|
+
if r.status_code != 200:
|
|
24
|
+
return False, {"message": f"status {r.status_code} with text: {r.text}"}
|
|
25
|
+
return True, json.loads(r.text) if len(r.text) > 0 else ""
|
|
26
|
+
|
|
27
|
+
def _get(self, end_point:str) -> Tuple[bool, dict]:
|
|
28
|
+
return self._request(end_point, "GET")
|
|
29
|
+
|
|
30
|
+
def _post(self, end_point:str) -> Tuple[bool, dict]:
|
|
31
|
+
return self._request(end_point, "POST")
|
|
32
|
+
|
|
33
|
+
def _put(self, end_point:str) -> Tuple[bool, dict]:
|
|
34
|
+
return self._request(end_point, "PUT")
|
|
35
|
+
|
|
36
|
+
def move_to_next(self, participant_index:int|None=None) -> Tuple[bool, dict]:
|
|
37
|
+
url = _process_participant_index("move-to-next", participant_index)
|
|
38
|
+
return self._post(url)
|
|
39
|
+
|
|
40
|
+
def get_config(self, participant_index:int|None=None) -> Tuple[bool, dict]:
|
|
41
|
+
url = _process_participant_index("config", participant_index)
|
|
42
|
+
return self._get(url)
|
|
43
|
+
|
|
44
|
+
def server_is_active(self) -> Tuple[bool, dict]:
|
|
45
|
+
return self._get("active")
|
|
46
|
+
|
|
47
|
+
def get_blocks_count(self, participant_index:int|None=None) -> Tuple[bool, dict]:
|
|
48
|
+
url = _process_participant_index("blocks-count", participant_index)
|
|
49
|
+
return self._get(url)
|
|
50
|
+
|
|
51
|
+
def get_all_configs(self, participant_index:int|None=None) -> Tuple[bool, dict]:
|
|
52
|
+
url = _process_participant_index("all-configs", participant_index)
|
|
53
|
+
return self._get(url)
|
|
54
|
+
|
|
55
|
+
def move_to_block(self, block_id:int, participant_index:int|None=None) -> Tuple[bool, dict]:
|
|
56
|
+
assert isinstance(block_id, int), "`block` should be a int"
|
|
57
|
+
url = _process_participant_index("move-to-block", participant_index)
|
|
58
|
+
return self._post(f"{url}/{block_id}")
|
|
59
|
+
|
|
60
|
+
def new_participant(self) -> Tuple[bool, dict]:
|
|
61
|
+
return self._put("new-participant");
|
|
62
|
+
|
|
63
|
+
def add_participant(self, participant_index:int) -> Tuple[bool, dict]:
|
|
64
|
+
assert participant_index is not None
|
|
65
|
+
url = _process_participant_index("add-participant", participant_index)
|
|
66
|
+
return self._put(url);
|
|
67
|
+
|
|
68
|
+
def shutdown(self) -> Tuple[bool, dict]:
|
|
69
|
+
return self._post("shutdown")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _process_participant_index(url:str, participant_index:int|None) -> str:
|
|
73
|
+
if participant_index is not None:
|
|
74
|
+
assert isinstance(participant_index, int), "`participant_index` should be a int"
|
|
75
|
+
url += f"/{participant_index}"
|
|
76
|
+
return url
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import itertools
|
|
3
|
+
from typing import Dict, List, Union
|
|
4
|
+
from easydict import EasyDict as edict
|
|
5
|
+
|
|
6
|
+
from experiment_server.utils import ExperimentServerConfigurationExcetion, balanced_latin_square
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ORDERING_BEHAVIOUR = edict({v:v for v in ["randomize", "latin_square", "as_is"]})
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def construct_participant_condition(config: List[Dict], participant_index: int, order: Union[dict, list], within_groups:str =None, groups:str =None) -> List:
|
|
13
|
+
if within_groups is None:
|
|
14
|
+
within_groups = ORDERING_BEHAVIOUR.as_is
|
|
15
|
+
if groups is None:
|
|
16
|
+
groups = ORDERING_BEHAVIOUR.as_is
|
|
17
|
+
|
|
18
|
+
names = {}
|
|
19
|
+
|
|
20
|
+
# To make sure the string based indexing works
|
|
21
|
+
for idx, c in enumerate(config):
|
|
22
|
+
c["name"] = str(c["name"])
|
|
23
|
+
names[c["name"]] = idx
|
|
24
|
+
|
|
25
|
+
if len(set(names.values()).symmetric_difference(list(range(len(config))))) != 0:
|
|
26
|
+
raise ExperimentServerConfigurationExcetion("Duplicate block names: {}".format(set([c["name"] for idx, c in enumerate(config) if idx not in names.values()])))
|
|
27
|
+
|
|
28
|
+
if within_groups not in list(ORDERING_BEHAVIOUR.values()):
|
|
29
|
+
raise ExperimentServerConfigurationExcetion(f"Allowed values for `within_groups` are {ORDERING_BEHAVIOUR.values()}, for {within_groups}")
|
|
30
|
+
if groups not in list(ORDERING_BEHAVIOUR.values()):
|
|
31
|
+
raise ExperimentServerConfigurationExcetion(f"Allowed values for `groups` are {ORDERING_BEHAVIOUR.values()}, for {groups}")
|
|
32
|
+
|
|
33
|
+
if isinstance(order, list):
|
|
34
|
+
if not all([isinstance(group, list) for group in order]):
|
|
35
|
+
order = [order,]
|
|
36
|
+
# Making sure the stratergy set for groups is used for within groups
|
|
37
|
+
within_groups = groups
|
|
38
|
+
if not all([isinstance(g, int) for group in order for g in group]) and not all([isinstance(g, str) for group in order for g in group]):
|
|
39
|
+
raise ExperimentServerConfigurationExcetion(f"Each group in the order needs to be a list of `int` or list of `str`, got {order}")
|
|
40
|
+
|
|
41
|
+
_filtered_order = order
|
|
42
|
+
|
|
43
|
+
elif isinstance(order, dict):
|
|
44
|
+
order = {int(k):v for k, v in order.items()}
|
|
45
|
+
if not all([isinstance(_order, list) for _order in order.values()]) or not all([isinstance(group, list) for _order in order.values() for group in _order]):
|
|
46
|
+
raise ExperimentServerConfigurationExcetion(f"Each group in orders for all participants needs to be list, got {order}")
|
|
47
|
+
if not all([isinstance(g, int) for _order in order.values() for group in _order for g in group]) and not all([isinstance(g, str) for _order in order.values() for group in _order for g in group]):
|
|
48
|
+
raise ExperimentServerConfigurationExcetion(f"Each group in orders for all participants needs to be a list of `int` or list of `str`, got {order}")
|
|
49
|
+
|
|
50
|
+
if not all([idx+1 in order.keys() for idx in range(len(order))]):
|
|
51
|
+
raise ExperimentServerConfigurationExcetion(f"Keys order oredr should match the consecutive indices starting from 1. Got keys {list(order.keys())}, expected keys {list(range(len(order)))}")
|
|
52
|
+
|
|
53
|
+
if groups != ORDERING_BEHAVIOUR.as_is:
|
|
54
|
+
raise ExperimentServerConfigurationExcetion(f"Ordering behaviour for groups should be {ORDERING_BEHAVIOUR.as_is} when order is a dictionary. Got {groups}")
|
|
55
|
+
_key = ((participant_index - 1) % len(order)) + 1
|
|
56
|
+
_filtered_order = order[_key]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if groups == ORDERING_BEHAVIOUR.randomize:
|
|
60
|
+
random.shuffle(_filtered_order)
|
|
61
|
+
elif groups == ORDERING_BEHAVIOUR.latin_square:
|
|
62
|
+
_latin_square = balanced_latin_square(len(_filtered_order))
|
|
63
|
+
_participant_order = _latin_square[(participant_index - 1) % len(_filtered_order)]
|
|
64
|
+
|
|
65
|
+
_filtered_order = [_filtered_order[idx] for idx in _participant_order]
|
|
66
|
+
|
|
67
|
+
if within_groups == ORDERING_BEHAVIOUR.randomize:
|
|
68
|
+
for group in _filtered_order:
|
|
69
|
+
random.shuffle(group)
|
|
70
|
+
elif within_groups == ORDERING_BEHAVIOUR.latin_square:
|
|
71
|
+
elements_in_group = set([len(_g) for _g in _filtered_order])
|
|
72
|
+
if len(elements_in_group) != 1:
|
|
73
|
+
raise ExperimentServerConfigurationExcetion(f"Currently {ORDERING_BEHAVIOUR.latin_square} not supported for `within_groups` when the number of elements in all groups are not the same")
|
|
74
|
+
else:
|
|
75
|
+
_elements_count = elements_in_group.pop()
|
|
76
|
+
_latin_square = balanced_latin_square(_elements_count)
|
|
77
|
+
_group_order = _latin_square[(participant_index - 1) % _elements_count]
|
|
78
|
+
|
|
79
|
+
_filtered_order = [[_g[idx] for idx in _group_order] for _g in _filtered_order]
|
|
80
|
+
|
|
81
|
+
chained_order = list(itertools.chain(*_filtered_order))
|
|
82
|
+
if isinstance(chained_order[0], int):
|
|
83
|
+
return [config[i] for i in chained_order]
|
|
84
|
+
else:
|
|
85
|
+
return [config[names[i]] for i in chained_order]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# def _construct_participant_condition_old(config, participant_index, use_latin_square=False, latin_square=None, config_categorization=None, default_configuration=None, randomize=True):
|
|
89
|
+
# if participant_index < 1:
|
|
90
|
+
# participant_index = 1
|
|
91
|
+
# if use_latin_square:
|
|
92
|
+
# _config = [config[i - 1] for i in latin_square[(participant_index - 1) % len(config)]]
|
|
93
|
+
# else:
|
|
94
|
+
# assert len(config_categorization) == 2
|
|
95
|
+
# if not randomize or participant_index % len(config_categorization) == 0:
|
|
96
|
+
# init_condition = config_categorization[0][:]
|
|
97
|
+
# other_condition = config_categorization[1][:]
|
|
98
|
+
# else:
|
|
99
|
+
# init_condition = config_categorization[1][:]
|
|
100
|
+
# other_condition = config_categorization[0][:]
|
|
101
|
+
# init_condition = [config[i] for i in init_condition]
|
|
102
|
+
# other_condition = [config[i] for i in other_condition]
|
|
103
|
+
# random.shuffle(init_condition)
|
|
104
|
+
# random.shuffle(other_condition)
|
|
105
|
+
|
|
106
|
+
# if default_configuration is not None:
|
|
107
|
+
# default_configuration_config = default_configuration[0]["config"]
|
|
108
|
+
# non_default_keys = [k for k in default_configuration_config.keys() if k not in ["conditionId"]]
|
|
109
|
+
|
|
110
|
+
# init_condition_train = init_condition[0].copy()
|
|
111
|
+
# init_condition_train["config"] = init_condition_train["config"].copy()
|
|
112
|
+
# init_condition_train["config"]["conditionId"] = "training1"
|
|
113
|
+
# for k in non_default_keys:
|
|
114
|
+
# init_condition_train["config"][k] = default_configuration_config[k]
|
|
115
|
+
|
|
116
|
+
# other_condition_train = other_condition[0].copy()
|
|
117
|
+
# other_condition_train["config"] = other_condition_train["config"].copy()
|
|
118
|
+
# other_condition_train["config"]["conditionId"] = "training2"
|
|
119
|
+
# for k in non_default_keys:
|
|
120
|
+
# other_condition_train["config"][k] = default_configuration_config[k]
|
|
121
|
+
|
|
122
|
+
# _config = [init_condition_train] + init_condition + [other_condition_train] + other_condition
|
|
123
|
+
|
|
124
|
+
# else:
|
|
125
|
+
# _config = init_condition + other_condition
|
|
126
|
+
# # _config = [config[i] for i in _config_list]
|
|
127
|
+
# return _config
|