-
Notifications
You must be signed in to change notification settings - Fork 2
/
autosix.py
406 lines (300 loc) · 13.5 KB
/
autosix.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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
"""
AutoSix
-------
``AutoSix`` is a wrapper to automatically perform the necessary setup and steps needed for ``SixDesk`` use.
The functionality is similar to the ``pylhc_submitter.job_submitter`` in that the inner product of a ``replace_dict``
is used to automatically create a set of job-directories to gather the data.
To avoid conflicts, each of these job-directories is a ``SixDesk`` workspace,
meaning there can be only one study per directory.
Beware that the ``max_materialize`` limit is set for each of these workspaces
individually, not for all Jobs together (i.e. it should be <=MAX_USER_JOBS / NUMBER_OF_WORKSPACES).
The ``replace_dict`` contains variables for your mask as well as variables for the SixDesk environment.
See the description of ``replace_dict`` below.
In any other way, these *special* variables behave like normal variables and can also be inserted in your mask.
They are also looped over in the same manner as any other variable (if given as a list).
For additional information and guides, see the `AutoSix page
<https://pylhc.github.io/packages/pylhcsubmitter/autosix/>`_
on the ``OMC`` documentation site.
.. admonition:: Problems with *search.f90*
:class: warning
If you run into
.. code-block:: bash
ImportError: cannot import name 'search' from partially initialized module 'sixdeskdb'
that means that the :file:`SixDesk/utilities/externals/SixDeskDB/sixdeskdb/search.f90`
has not been compiled for your current python and OS version (indicated by XXX below).
There are two ways to do this:
**a)** Run
.. code-block:: bash
python -m numpy.f2py -c search.f90 -f search
with your desired python version
(a shortcut ``f2py`` might be available if you run in an activated venv).
Copy (or symlink) the resulting :file:`search.cpython-XXX.so` file into the
:file:`sixdeskdb` folder
(if it is not already there because you ran from that folder)
**b)** Run
.. code-block:: bash
python setup.py build_ext
on the :file:`setup.py` in :file:`SixDesk/utilities/externals/SixDeskDB`.
Then copy (or symlink) the resulting :file:`build/lib.XXX/sixdeskdb/search.cpython-XXX.so` into
the :file:`sixdeskdb` folder (the name needs to stay as it is).
Arguments:
*--Required--*
- **mask** *(Path)*:
Path to the program's mask to use.
- **replace_dict** *(DictAsString)*:
Dict with keys of the strings to be replaced in the mask (required) as
well as the mask_sixdeskenv and mask_sysenv files in the sixdesk_tools
module. Required fields are TURNS, AMPMIN, AMPMAX, AMPSTEP, ANGLES.
Optional fields are RESUBMISSION, PLATFORM, LOGLEVEL, FIRSTSEED,
LASTSEED, RUNTYPE, NPAIRS, EMITTANCE, DIMENSIONS, WRITEBINS, ENERGY.
These keys can also be used in the mask if needed. The values of this
dict are lists of values to replace these or single entries.
- **working_directory** *(Path)*:
Directory where data should be put into.
*--Optional--*
- **apply_mad6t_hacks**:
Apply two hacks: Removes '<' in binary call and ignore the check for
'Twiss fail' in the submission file. This is hack needed in case this
check greps the wrong lines, e.g. in madx-comments. USE WITH CARE!!
action: ``store_true``
- **da_turnstep** *(int)*:
Step between turns used in DA-vs-Turns plot.
default: ``100``
- **executable** *(PathOrStr)*:
Path to executable or 'madx', 'python2', 'python3' to use the OMC
default paths on AFS.Defaults to the latest MADX-Binary on AFS.
default: ``/afs/cern.ch/user/m/mad/bin/madx``
- **jobid_mask** *(str)*:
Mask-String to name jobs, with placeholders from the ``replace_dict``
keys.
- **max_materialize** *(int)*:
Maximum jobs to be materialized in scheduler (per SixDesk Workspace!)..
Here: ``None`` leaves the settings as defined in the SixDesk
htcondor_run_six.sub template and ``0`` removes it from the template.
Warning: This setting modifies the template in the ``sixdesk_directory``
permanently. For more details see the htcondor API.
- **max_stage** *(str)*:
Last stage to be run. All following stages are skipped.
- **python2** *(PathOrStr)*:
Path to python to use with run_six.sh (python2 with requirements
installed). ONLY THE PATH TO THE DIRECTORY OF THE python BINARY IS
NEEDED! And it can't be an Anaconda Distribution. If ``None`` the
system's ``python`` is used (SixDesk internally).
default: ``None``
- **python3** *(PathOrStr)*:
Path to python to use with sixdb (python3 with requirements
installed).Defaults to the system's ``python3``.
default: ``python3``
- **resubmit**:
Resubmits to HTCondor if needed (i.e. in case it finds errors with the
previous run).
action: ``store_true``
- **sixdesk_directory** *(Path)*:
Path to the directory of SixDesk. Defaults to the PRO-version on AFS.
If you are using your own SixDesk Environment and it does not run,
check the AutoSix doc.
default: ``/afs/cern.ch/project/sixtrack/SixDesk_utilities/pro``
- **ssh** *(str)*:
Run htcondor from this machine via ssh (needs access to the
``working_directory``)
- **stop_workspace_init**:
Stops the workspace creation before initialization, so one can make
manual changes.
action: ``store_true``
- **unlock**:
Forces unlocking of folders (if they have been locked by Sixdesk).
action: ``store_true``
:author: jdilly
"""
import itertools
import logging
from pathlib import Path
import numpy as np
import tfs
from generic_parser import EntryPointParameters, entrypoint
from generic_parser.entry_datatypes import DictAsString
from pylhc_submitter.constants.autosix import (HEADER_BASEDIR, SIXENV_OPTIONAL, SIXENV_REQUIRED,
AutoSixEnvironment)
from pylhc_submitter.constants.job_submitter import COLUMN_JOBID, JOBSUMMARY_FILE
from pylhc_submitter.submitter.mask import generate_jobdf_index
from pylhc_submitter.sixdesk_tools.stages import STAGE_ORDER, Stage
from pylhc_submitter.sixdesk_tools.utils import check_mask, is_locked
from pylhc_submitter.utils.iotools import (PathOrStr, keys_to_path, make_replace_entries_iterable,
save_config)
from pylhc_submitter.utils.logging_tools import log_setup
LOG = logging.getLogger(__name__)
def get_params():
params = EntryPointParameters()
params.add_parameter(
name="mask",
type=Path,
required=True,
help="Path to the program's mask to use.",
)
params.add_parameter(
name="working_directory",
type=Path,
required=True,
help="Directory where data should be put into.",
)
params.add_parameter(
name="replace_dict",
help=(
"Dict with keys of the strings to be replaced in the mask (required) "
"as well as the mask_sixdeskenv and mask_sysenv files "
"in the sixdesk_tools module. "
f"Required fields are {', '.join(SIXENV_REQUIRED)}. "
f"Optional fields are {', '.join(SIXENV_OPTIONAL)}. "
"These keys can also be used in the mask if needed. "
"The values of this dict are lists of values to replace "
"these or single entries."
),
type=DictAsString,
required=True,
)
params.add_parameter(
name="sixdesk_directory",
type=Path,
default=AutoSixEnvironment.sixdesk_directory,
help="Path to the directory of SixDesk. Defaults to the PRO-version on AFS."
" If you are using your own SixDesk Environment and it does not run, "
" check the AutoSix doc.",
)
params.add_parameter(
name="executable",
default=AutoSixEnvironment.executable,
type=PathOrStr,
help="Path to executable or 'madx', 'python2', 'python3' "
"to use the OMC default paths on AFS."
"Defaults to the latest MADX-Binary on AFS.",
)
params.add_parameter(
name="python2",
default=AutoSixEnvironment.python2,
type=PathOrStr,
help=("Path to python to use with run_six.sh (python2 with requirements installed)."
" ONLY THE PATH TO THE DIRECTORY OF THE python BINARY IS NEEDED!"
" And it can't be an Anaconda Distribution."
" If ``None`` the system's ``python`` is used (SixDesk internally)."),
)
params.add_parameter(
name="python3",
default=AutoSixEnvironment.python3,
type=PathOrStr,
help="Path to python to use with sixdb (python3 with requirements installed)."
"Defaults to the system's ``python3``.",
)
params.add_parameter(
name="jobid_mask",
help="Mask-String to name jobs, with placeholders from the ``replace_dict`` keys.",
type=str,
)
params.add_parameter(
name="ssh",
help="Run htcondor from this machine via ssh (needs access to the ``working_directory``)",
type=str,
)
params.add_parameter(
name="unlock",
help="Forces unlocking of folders (if they have been locked by Sixdesk).",
action="store_true",
)
params.add_parameter(
name="apply_mad6t_hacks",
help=(
"Apply two hacks: Removes '<' in binary call and "
"ignore the check for 'Twiss fail' in the submission file. "
"This is hack needed in case this check greps the wrong lines, "
"e.g. in madx-comments. USE WITH CARE!!"
),
action="store_true",
)
params.add_parameter(
name="stop_workspace_init",
help=(
"Stops the workspace creation before initialization,"
" so one can make manual changes."
),
action="store_true",
)
params.add_parameter(
name="resubmit",
help="Resubmits to HTCondor if needed "
"(i.e. in case it finds errors with the previous run).",
action="store_true",
)
params.add_parameter(
name="da_turnstep",
type=int,
help="Step between turns used in DA-vs-Turns plot.",
default=AutoSixEnvironment.da_turnstep,
)
params.add_parameter(
name="max_stage",
type=str,
help="Last stage to be run. All following stages are skipped.",
)
params.add_parameter(
name="max_materialize",
type=int,
help="Maximum jobs to be materialized in scheduler (per SixDesk Workspace!). "
"Here: ``None`` leaves the settings as defined in the SixDesk "
"htcondor_run_six.sub template and ``0`` removes it from the "
"template. Warning: This setting modifies the template in the "
"``sixdesk_directory`` permanently. For more details see the "
"htcondor API.",
)
return params
@entrypoint(get_params(), strict=True)
def main(opt):
""" Loop to create jobs from replace dict product matrix. """
LOG.info("Starting autosix.")
opt = _check_opts(opt)
save_config(opt.working_directory, opt, "autosix")
jobdf = _generate_jobs(
opt.working_directory,
opt.pop('jobid_mask'), # not needed anymore
**opt.pop('replace_dict') # not needed anymore
)
env = AutoSixEnvironment(**opt) # basically checks that everything is there
for jobname, jobargs in jobdf.iterrows():
run_job(jobname=jobname, jobargs=jobargs, env=env)
def run_job(jobname: str, jobargs: dict, env: AutoSixEnvironment):
"""Main submitting procedure for single job.
Args:
jobname (str): Name of the job/study
env (DotDict): The ensemble of autosix settings as an ``AutoSixEnvironment`` object.
jobargs(dict): All Key=Values needed to fill the mask!
"""
if is_locked(jobname, env.working_directory, unlock=env.unlock):
LOG.info(f"{jobname} is locked. Try 'unlock' flag if this causes errors.")
Stage.run_all_stages(jobname, jobargs, env)
# Helper ----------------------------------------------------------------------
def _check_opts(opt):
opt = keys_to_path(opt, "mask", "working_directory", "executable")
opt.mask_text = opt.mask.read_text()
check_mask(opt.mask_text, opt.replace_dict)
del opt.mask
opt.replace_dict = make_replace_entries_iterable(opt.replace_dict)
if opt.max_stage is not None and not isinstance(opt.max_stage, Stage):
opt.max_stage = STAGE_ORDER[opt.max_stage]
return opt
def get_jobs_and_values(jobid_mask, **kwargs):
values_grid = np.array(list(itertools.product(*kwargs.values())), dtype=object)
job_names = generate_jobdf_index(None, jobid_mask, kwargs.keys(), values_grid)
return job_names, values_grid
def _generate_jobs(basedir, jobid_mask, **kwargs) -> tfs.TfsDataFrame:
""" Generates product matrix for job-values and stores it as TfsDataFrame. """
LOG.debug("Creating Jobs")
job_names, values_grid = get_jobs_and_values(jobid_mask, **kwargs)
job_df = tfs.TfsDataFrame(
headers={HEADER_BASEDIR: basedir},
index=job_names,
columns=list(kwargs.keys()),
data=values_grid,
)
tfs.write(basedir / JOBSUMMARY_FILE, job_df, save_index=COLUMN_JOBID)
return job_df
if __name__ == "__main__":
log_setup()
main()