-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun_grid.py
executable file
·229 lines (202 loc) · 7.42 KB
/
run_grid.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env python3
"""
Interactive tool for creation/removal of multiple subfolders. This is useful
for gridsearches and like.
Usage:
run_grid.py <path>
run_grid.py <path> (remove|purge) [--symlinks]
Options:
--symlinks Follow symlinks and remove output dirs too
File format:
<path> should contain a grid.yml file of following structure:
vars:
<NAME>: <V>
...
Depending on <V> type different stuff happens:
- list, we take values from list
- string, we eval(V)
- int, we take range(V)
"""
import shutil
import pprint
import yaml
import logging
import numpy as np # type: ignore
import itertools
from pathlib import Path
from docopt import docopt # type: ignore
import vst
from vst.exp import (resolve_clean_exp_path)
from dervo.config import (FILENAME_YML, FILENAME_PY)
DEFAULT_GRID_CFG = 'grid.yml'
log = logging.getLogger(__name__)
def _content_match(file, file_contents):
"""Makes sure that file contains certain text"""
with file.open('r') as f:
real_contents = f.read()
return real_contents == file_contents
def resolve_endnode_vars(var):
if type(var) is list:
all_values = var
elif type(var) is str:
all_values = eval(var)
elif type(var) is int:
all_values = list(range(var))
else:
raise NotImplementedError(f'Unknown type for variable {var}')
return all_values
def resolve_node(k, v):
if k == 'ALIGN':
child_kvs = [resolve_node(k, v) for k, v in v.items()]
assert len(set([len(x) for x in child_kvs])) == 1, \
f'align children must be same size,\n{child_kvs}'
key_values = []
for x in zip(*child_kvs):
key_values.append(dict(z for y in x for z in y.items()))
elif k == 'CROSS':
child_kvs = [resolve_node(k, v) for k, v in v.items()]
key_values = []
for x in itertools.product(*child_kvs):
key_values.append(dict(z for y in x for z in y.items()))
else:
values = resolve_endnode_vars(v)
python_vars = [np.asscalar(v)
if isinstance(v, np.generic) else v for v in values]
key_values = [{k: v} for v in python_vars]
return key_values
class GridManager(object):
def __init__(self, path):
self.path = resolve_clean_exp_path(path)
self.description_path = self.path/DEFAULT_GRID_CFG
if not self.description_path.exists():
raise ValueError('No grid description found at {}'.format(
self.description_path))
self.build_grid_structure()
def build_grid_structure(self):
with open(self.description_path, 'r') as f:
descr = yaml.safe_load(f)
# Variables and their values (sorted)
self.key_values = []
for k, v in descr.items():
self.key_values.extend(resolve_node(k, v))
# Folders and cfg values
self.folds_and_cfgs = []
for ind, kvdict in enumerate(self.key_values):
foldname = 'grid:{}:'.format(':'.join([
f'{k}={v}' for k, v in kvdict.items()]))
if ' ' in foldname:
log.warning('Spaces in folder name. Trying to fix')
foldname = foldname.replace(' ', '')
file_contents = yaml.dump(
vst.exp.unflatten_nested_dict(kvdict))
self.folds_and_cfgs.append((foldname, file_contents, kvdict))
@staticmethod
def folder_match(cfg_fold, file_contents):
"""Makes sure that folder contains config file and (optionally)
symlinks"""
cfg_file = cfg_fold/FILENAME_YML
if not cfg_file.exists():
return False, 'cfg_file not existing'
if not _content_match(cfg_file, file_contents):
return False, 'cfg_file content mismatch'
for it in cfg_fold.iterdir():
if it.is_symlink() or it == cfg_file:
continue
else:
return False, 'cfg_fold contains extra file'
return True, None
def install_folders(self):
N = 0
for foldname, file_contents, _ in self.folds_and_cfgs:
cfg_fold = self.path/foldname
cfg_fold.mkdir(exist_ok=True)
cfg_file = cfg_fold/FILENAME_YML
if cfg_file.exists():
if not _content_match(cfg_file, file_contents):
log.warning('Grid file contents do not match '
'what must be inside')
else:
with cfg_file.open('w') as f:
print(file_contents, file=f, end='')
N += 1
log.info(f'Created {N} cfgs')
def remove_folders(self):
N = 0
for foldname, file_contents, _ in self.folds_and_cfgs:
fold = self.path/foldname
if fold.exists():
match, reason = self.folder_match(fold, file_contents)
if match:
for file in fold.iterdir():
file.unlink()
fold.rmdir()
N += 1
else:
log.warning(f'Unable to delete {fold} because {reason}')
log.info(f'Removed {N} folders')
def purge_folders(self):
N = 0
to_purge_folders = []
to_purge_files = []
for file in self.path.glob('*'):
if file.name in (
FILENAME_YML,
FILENAME_PY,
DEFAULT_GRID_CFG):
continue
else:
if file.is_dir():
to_purge_folders.append(file)
else:
to_purge_files.append(file)
print('Remove folders:\n' +
'\n'.join(map(str, to_purge_folders)),
'\nRemove files:\n' +
'\n'.join(map(str, to_purge_files)))
s = input('Remove surely (Y/N)? --> ')
if s in ('Y', 'y'):
for file in to_purge_files:
file.unlink()
N += 1
for file in to_purge_folders:
shutil.rmtree(file)
N += 1
log.info(f'Purged {N} files/folders')
def purge_symlinks(self):
N = 0
to_purge_symlinks = []
to_purge_real_files = []
for file in self.path.rglob('*'):
if file.is_symlink():
to_purge_symlinks.append(file)
to_purge_real_files.append(file.resolve())
print('Remove folders:\n' +
'\n'.join(map(str, to_purge_symlinks)),
'\nRemove files:\n' +
'\n'.join(map(str, to_purge_real_files)))
s = input('Remove surely (Y/N)? --> ')
if s in ('Y', 'y'):
for file in to_purge_symlinks:
file.unlink()
N += 1
for file in to_purge_real_files:
shutil.rmtree(file)
N += 1
log.info(f'Purged {N} files/folders')
def main(args):
path = Path(args['<path>'])
grid = GridManager(path)
if args.get('remove') or args.get('purge'):
if args['--symlinks']:
grid.purge_symlinks()
if args.get('remove'):
grid.remove_folders()
elif args.get('purge'):
grid.purge_folders()
else:
log.info('Values are {}'.format(pprint.pformat(grid.key_values)))
grid.install_folders()
if __name__ == '__main__':
args = docopt(__doc__)
log = vst.reasonable_logging_setup(logging.INFO)
main(args)