calphy 1.4.5__py3-none-any.whl → 1.4.12__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.
- calphy/__init__.py +1 -1
- calphy/alchemy.py +278 -158
- calphy/composition_transformation.py +298 -136
- calphy/helpers.py +45 -29
- calphy/input.py +81 -3
- calphy/liquid.py +13 -0
- calphy/phase.py +55 -4
- calphy/phase_diagram.py +196 -53
- calphy/routines.py +6 -0
- calphy/scheduler.py +100 -105
- calphy/solid.py +243 -186
- {calphy-1.4.5.dist-info → calphy-1.4.12.dist-info}/METADATA +1 -1
- calphy-1.4.12.dist-info/RECORD +25 -0
- {calphy-1.4.5.dist-info → calphy-1.4.12.dist-info}/WHEEL +1 -1
- calphy-1.4.5.dist-info/RECORD +0 -25
- {calphy-1.4.5.dist-info → calphy-1.4.12.dist-info}/entry_points.txt +0 -0
- {calphy-1.4.5.dist-info → calphy-1.4.12.dist-info}/licenses/LICENSE +0 -0
- {calphy-1.4.5.dist-info → calphy-1.4.12.dist-info}/top_level.txt +0 -0
calphy/phase_diagram.py
CHANGED
|
@@ -254,6 +254,142 @@ matcolors = {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
def read_structure_composition(lattice_file, element_list):
|
|
258
|
+
"""
|
|
259
|
+
Read a LAMMPS data file and determine the input chemical composition.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
lattice_file : str
|
|
264
|
+
Path to the LAMMPS data file
|
|
265
|
+
element_list : list
|
|
266
|
+
List of element symbols in order (element[0] = type 1, element[1] = type 2, etc.)
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
dict
|
|
271
|
+
Dictionary mapping element symbols to atom counts
|
|
272
|
+
Elements not present in the structure will have count 0
|
|
273
|
+
"""
|
|
274
|
+
from ase.io import read
|
|
275
|
+
from collections import Counter
|
|
276
|
+
|
|
277
|
+
# Read the structure file
|
|
278
|
+
structure = read(lattice_file, format='lammps-data', style='atomic')
|
|
279
|
+
|
|
280
|
+
# Get the species/types from the structure
|
|
281
|
+
# ASE reads LAMMPS types as species strings ('1', '2', etc.)
|
|
282
|
+
if 'species' in structure.arrays:
|
|
283
|
+
types_in_structure = structure.arrays['species']
|
|
284
|
+
else:
|
|
285
|
+
# Fallback: get atomic numbers and convert to strings
|
|
286
|
+
types_in_structure = [str(x) for x in structure.get_atomic_numbers()]
|
|
287
|
+
|
|
288
|
+
# Count atoms by type
|
|
289
|
+
type_counts = Counter(types_in_structure)
|
|
290
|
+
|
|
291
|
+
# Build composition mapping element names to counts
|
|
292
|
+
# element[0] corresponds to LAMMPS type '1', element[1] to type '2', etc.
|
|
293
|
+
input_chemical_composition = {}
|
|
294
|
+
for idx, element in enumerate(element_list):
|
|
295
|
+
lammps_type = str(idx + 1) # LAMMPS types are 1-indexed
|
|
296
|
+
input_chemical_composition[element] = type_counts.get(lammps_type, 0)
|
|
297
|
+
|
|
298
|
+
return input_chemical_composition
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# Constants for phase diagram preparation
|
|
302
|
+
COMPOSITION_TOLERANCE = 1E-5
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _create_composition_array(comp_range, interval, reference):
|
|
306
|
+
"""
|
|
307
|
+
Create composition array from range specification.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
comp_range : list or scalar
|
|
312
|
+
Composition range [min, max] or single value
|
|
313
|
+
interval : float
|
|
314
|
+
Composition interval
|
|
315
|
+
reference : float
|
|
316
|
+
Reference composition value
|
|
317
|
+
|
|
318
|
+
Returns
|
|
319
|
+
-------
|
|
320
|
+
tuple
|
|
321
|
+
(comp_arr, is_reference) - composition array and boolean array marking reference compositions
|
|
322
|
+
"""
|
|
323
|
+
# Convert to list if scalar
|
|
324
|
+
if not isinstance(comp_range, list):
|
|
325
|
+
comp_range = [comp_range]
|
|
326
|
+
|
|
327
|
+
if len(comp_range) == 2:
|
|
328
|
+
comp_arr = np.arange(comp_range[0], comp_range[-1], interval)
|
|
329
|
+
last_val = comp_range[-1]
|
|
330
|
+
if last_val not in comp_arr:
|
|
331
|
+
comp_arr = np.append(comp_arr, last_val)
|
|
332
|
+
is_reference = np.abs(comp_arr - reference) < COMPOSITION_TOLERANCE
|
|
333
|
+
elif len(comp_range) == 1:
|
|
334
|
+
comp_arr = [comp_range[0]]
|
|
335
|
+
# Check if this single composition equals the reference
|
|
336
|
+
is_reference = [np.abs(comp_range[0] - reference) < COMPOSITION_TOLERANCE]
|
|
337
|
+
else:
|
|
338
|
+
raise ValueError("Composition range should be scalar or list of two values!")
|
|
339
|
+
|
|
340
|
+
return comp_arr, is_reference
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _create_temperature_array(temp_range, interval):
|
|
344
|
+
"""
|
|
345
|
+
Create temperature array from range specification.
|
|
346
|
+
|
|
347
|
+
Parameters
|
|
348
|
+
----------
|
|
349
|
+
temp_range : list or scalar
|
|
350
|
+
Temperature range [min, max] or single value
|
|
351
|
+
interval : float
|
|
352
|
+
Temperature interval
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
ndarray
|
|
357
|
+
Temperature array
|
|
358
|
+
"""
|
|
359
|
+
# Convert to list if scalar
|
|
360
|
+
if not isinstance(temp_range, list):
|
|
361
|
+
temp_range = [temp_range]
|
|
362
|
+
|
|
363
|
+
if len(temp_range) == 2:
|
|
364
|
+
ntemps = int((temp_range[-1] - temp_range[0]) / interval) + 1
|
|
365
|
+
temp_arr = np.linspace(temp_range[0], temp_range[-1], ntemps, endpoint=True)
|
|
366
|
+
elif len(temp_range) == 1:
|
|
367
|
+
temp_arr = [temp_range[0]]
|
|
368
|
+
else:
|
|
369
|
+
raise ValueError("Temperature range should be scalar or list of two values!")
|
|
370
|
+
|
|
371
|
+
return temp_arr
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _add_temperature_calculations(calc_dict, temp_arr, all_calculations):
|
|
375
|
+
"""
|
|
376
|
+
Helper to add calculations for each temperature point.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
calc_dict : dict
|
|
381
|
+
Base calculation dictionary
|
|
382
|
+
temp_arr : array
|
|
383
|
+
Array of temperatures
|
|
384
|
+
all_calculations : list
|
|
385
|
+
List to append calculations to
|
|
386
|
+
"""
|
|
387
|
+
for temp in temp_arr:
|
|
388
|
+
calc_for_temp = copy.deepcopy(calc_dict)
|
|
389
|
+
calc_for_temp['temperature'] = int(temp)
|
|
390
|
+
all_calculations.append(calc_for_temp)
|
|
391
|
+
|
|
392
|
+
|
|
257
393
|
def fix_data_file(datafile, nelements):
|
|
258
394
|
"""
|
|
259
395
|
Change the atom types keyword in the structure file
|
|
@@ -309,6 +445,31 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
309
445
|
calculation_base_name = inputyamlfile
|
|
310
446
|
|
|
311
447
|
for phase in data['phases']:
|
|
448
|
+
# Validate binary system assumption
|
|
449
|
+
n_elements = len(phase['element'])
|
|
450
|
+
if n_elements != 2:
|
|
451
|
+
raise ValueError(
|
|
452
|
+
f"Phase diagram preparation currently supports only binary systems. "
|
|
453
|
+
f"Found {n_elements} elements: {phase['element']}"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Validate element ordering consistency with pair_coeff
|
|
457
|
+
# This ensures element[0] -> type 1, element[1] -> type 2
|
|
458
|
+
if 'pair_coeff' in phase:
|
|
459
|
+
from calphy.input import _extract_elements_from_pair_coeff
|
|
460
|
+
# pair_coeff can be a list or a string - handle both
|
|
461
|
+
pair_coeff = phase['pair_coeff']
|
|
462
|
+
if isinstance(pair_coeff, list):
|
|
463
|
+
pair_coeff = pair_coeff[0] if pair_coeff else None
|
|
464
|
+
pair_coeff_elements = _extract_elements_from_pair_coeff(pair_coeff)
|
|
465
|
+
if pair_coeff_elements != phase['element']:
|
|
466
|
+
raise ValueError(
|
|
467
|
+
f"Element ordering mismatch for phase '{phase.get('phase_name', 'unnamed')}'!\n"
|
|
468
|
+
f"Elements in 'element' field: {phase['element']}\n"
|
|
469
|
+
f"Elements from pair_coeff: {pair_coeff_elements}\n"
|
|
470
|
+
f"These must match exactly in order (element[0] -> LAMMPS type 1, element[1] -> type 2)."
|
|
471
|
+
)
|
|
472
|
+
|
|
312
473
|
phase_reference_state = phase['reference_phase']
|
|
313
474
|
phase_name = phase['phase_name']
|
|
314
475
|
|
|
@@ -325,34 +486,18 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
325
486
|
other_element_list.remove(reference_element)
|
|
326
487
|
other_element = other_element_list[0]
|
|
327
488
|
|
|
328
|
-
#
|
|
329
|
-
|
|
330
|
-
comps[
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
comp_arr = np.append(comp_arr, last_val)
|
|
336
|
-
ncomps = len(comp_arr)
|
|
337
|
-
is_reference = np.abs(comp_arr-comps['reference']) < 1E-5
|
|
338
|
-
elif len(comps["range"]) == 1:
|
|
339
|
-
ncomps = 1
|
|
340
|
-
comp_arr = [comps["range"][0]]
|
|
341
|
-
is_reference = [True]
|
|
342
|
-
else:
|
|
343
|
-
raise ValueError("Composition range should be scalar of list of two values!")
|
|
489
|
+
# Create composition array using helper function
|
|
490
|
+
comp_arr, is_reference = _create_composition_array(
|
|
491
|
+
comps['range'],
|
|
492
|
+
comps['interval'],
|
|
493
|
+
comps['reference']
|
|
494
|
+
)
|
|
495
|
+
ncomps = len(comp_arr)
|
|
344
496
|
|
|
497
|
+
# Create temperature array using helper function
|
|
345
498
|
temps = phase["temperature"]
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if len(temps["range"]) == 2:
|
|
349
|
-
ntemps = int((temps['range'][-1]-temps['range'][0])/temps['interval'])+1
|
|
350
|
-
temp_arr = np.linspace(temps['range'][0], temps['range'][-1], ntemps, endpoint=True)
|
|
351
|
-
elif len(temps["range"]) == 1:
|
|
352
|
-
ntemps = 1
|
|
353
|
-
temp_arr = [temps["range"][0]]
|
|
354
|
-
else:
|
|
355
|
-
raise ValueError("Temperature range should be scalar of list of two values!")
|
|
499
|
+
temp_arr = _create_temperature_array(temps['range'], temps['interval'])
|
|
500
|
+
ntemps = len(temp_arr)
|
|
356
501
|
|
|
357
502
|
all_calculations = []
|
|
358
503
|
|
|
@@ -372,27 +517,30 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
372
517
|
outfile = fix_data_file(calc['lattice'], len(calc['element']))
|
|
373
518
|
|
|
374
519
|
#add ref phase, needed
|
|
375
|
-
calc['reference_phase'] =
|
|
520
|
+
calc['reference_phase'] = phase_reference_state
|
|
376
521
|
calc['reference_composition'] = comps['reference']
|
|
377
|
-
calc['mode'] =
|
|
522
|
+
calc['mode'] = 'fe'
|
|
378
523
|
calc['folder_prefix'] = f'{phase_name}-{comp:.2f}'
|
|
379
|
-
calc['lattice'] =
|
|
524
|
+
calc['lattice'] = outfile
|
|
380
525
|
|
|
381
|
-
#
|
|
382
|
-
|
|
383
|
-
calc_for_temp = copy.deepcopy(calc)
|
|
384
|
-
calc_for_temp['temperature'] = int(temp)
|
|
385
|
-
all_calculations.append(calc_for_temp)
|
|
526
|
+
# Add calculations for each temperature
|
|
527
|
+
_add_temperature_calculations(calc, temp_arr, all_calculations)
|
|
386
528
|
else:
|
|
387
529
|
#off stoichiometric
|
|
388
530
|
#copy the dict
|
|
389
531
|
calc = copy.deepcopy(phase)
|
|
390
532
|
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
|
|
533
|
+
#read the structure file to determine input composition automatically
|
|
534
|
+
input_chemical_composition = read_structure_composition(calc['lattice'], calc['element'])
|
|
535
|
+
|
|
536
|
+
#calculate total number of atoms from structure
|
|
537
|
+
n_atoms = sum(input_chemical_composition.values())
|
|
538
|
+
|
|
539
|
+
if n_atoms == 0:
|
|
540
|
+
raise ValueError(f"No atoms found in structure file {calc['lattice']}")
|
|
394
541
|
|
|
395
|
-
#find number of atoms of second species
|
|
542
|
+
#find number of atoms of second species based on target composition
|
|
543
|
+
#we follow the convention that composition is always given with the reference element
|
|
396
544
|
output_chemical_composition = {}
|
|
397
545
|
n_species_b = int(np.round(comp*n_atoms, decimals=0))
|
|
398
546
|
output_chemical_composition[reference_element] = n_species_b
|
|
@@ -400,11 +548,8 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
400
548
|
n_species_a = int(n_atoms-n_species_b)
|
|
401
549
|
output_chemical_composition[other_element] = n_species_a
|
|
402
550
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
#create input comp dict and output comp dict
|
|
406
|
-
input_chemical_composition = {element:number for element, number in zip(calc['element'],
|
|
407
|
-
calc['composition']['number_of_atoms'])}
|
|
551
|
+
# Note: Pure phases (n_species_a == 0 or n_species_b == 0) are allowed
|
|
552
|
+
# Composition transformation can handle 100% replacement
|
|
408
553
|
|
|
409
554
|
#good, now we need to write such a structure out; likely better to use working directory for that
|
|
410
555
|
folder_prefix = f'{phase_name}-{comp:.2f}'
|
|
@@ -421,7 +566,7 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
421
566
|
|
|
422
567
|
#just submit comp scales
|
|
423
568
|
#add ref phase, needed
|
|
424
|
-
calc['mode'] =
|
|
569
|
+
calc['mode'] = 'composition_scaling'
|
|
425
570
|
calc['folder_prefix'] = folder_prefix
|
|
426
571
|
calc['composition_scaling'] = {}
|
|
427
572
|
calc['composition_scaling']['output_chemical_composition'] = output_chemical_composition
|
|
@@ -447,22 +592,20 @@ def prepare_inputs_for_phase_diagram(inputyamlfile, calculation_base_name=None):
|
|
|
447
592
|
_ = calc.pop(key, None)
|
|
448
593
|
|
|
449
594
|
#add ref phase, needed
|
|
450
|
-
calc['mode'] =
|
|
595
|
+
calc['mode'] = 'fe'
|
|
451
596
|
calc['folder_prefix'] = folder_prefix
|
|
452
|
-
calc['lattice'] =
|
|
597
|
+
calc['lattice'] = outfile
|
|
453
598
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
calc_for_temp = copy.deepcopy(calc)
|
|
457
|
-
calc_for_temp['temperature'] = int(temp)
|
|
458
|
-
all_calculations.append(calc_for_temp)
|
|
599
|
+
# Add calculations for each temperature
|
|
600
|
+
_add_temperature_calculations(calc, temp_arr, all_calculations)
|
|
459
601
|
|
|
460
602
|
#finish and write up the file
|
|
461
603
|
output_data = {"calculations": all_calculations}
|
|
604
|
+
base_name = os.path.basename(calculation_base_name)
|
|
462
605
|
for rep in ['.yml', '.yaml']:
|
|
463
|
-
|
|
606
|
+
base_name = base_name.replace(rep, '')
|
|
464
607
|
|
|
465
|
-
outfile_phase = phase_name + '_' +
|
|
608
|
+
outfile_phase = phase_name + '_' + base_name + ".yaml"
|
|
466
609
|
with open(outfile_phase, 'w') as fout:
|
|
467
610
|
yaml.safe_dump(output_data, fout)
|
|
468
611
|
print(f'Total {len(all_calculations)} calculations found for phase {phase_name}, written to {outfile_phase}')
|
calphy/routines.py
CHANGED
|
@@ -94,6 +94,9 @@ class MeltingTemp:
|
|
|
94
94
|
calc["mode"] = "ts"
|
|
95
95
|
calc["temperature"] = [int(self.tmin), int(self.tmax)]
|
|
96
96
|
calc["reference_phase"] = 'solid'
|
|
97
|
+
# Preserve n_iterations from the original melting_temperature calculation
|
|
98
|
+
if "n_iterations" in data["calculations"][int(self.calc.kernel)]:
|
|
99
|
+
calc["n_iterations"] = data["calculations"][int(self.calc.kernel)]["n_iterations"]
|
|
97
100
|
calculations["calculations"].append(calc)
|
|
98
101
|
|
|
99
102
|
with open(self.calc.inputfile, 'r') as fin:
|
|
@@ -103,6 +106,9 @@ class MeltingTemp:
|
|
|
103
106
|
calc["mode"] = "ts"
|
|
104
107
|
calc["temperature"] = [int(self.tmin), int(self.tmax)]
|
|
105
108
|
calc["reference_phase"] = 'liquid'
|
|
109
|
+
# Preserve n_iterations from the original melting_temperature calculation
|
|
110
|
+
if "n_iterations" in data["calculations"][int(self.calc.kernel)]:
|
|
111
|
+
calc["n_iterations"] = data["calculations"][int(self.calc.kernel)]["n_iterations"]
|
|
106
112
|
calculations["calculations"].append(calc)
|
|
107
113
|
|
|
108
114
|
outfile = f'{self.calc.create_identifier()}.{self.attempts}.yaml'
|
calphy/scheduler.py
CHANGED
|
@@ -3,14 +3,14 @@ calphy: a Python library and command line interface for automated free
|
|
|
3
3
|
energy calculations.
|
|
4
4
|
|
|
5
5
|
Copyright 2021 (c) Sarath Menon^1, Yury Lysogorskiy^2, Ralf Drautz^2
|
|
6
|
-
^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany
|
|
6
|
+
^1: Max Planck Institut für Eisenforschung, Dusseldorf, Germany
|
|
7
7
|
^2: Ruhr-University Bochum, Bochum, Germany
|
|
8
8
|
|
|
9
|
-
calphy is published and distributed under the Academic Software License v1.0 (ASL).
|
|
10
|
-
calphy is distributed in the hope that it will be useful for non-commercial academic research,
|
|
11
|
-
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
9
|
+
calphy is published and distributed under the Academic Software License v1.0 (ASL).
|
|
10
|
+
calphy is distributed in the hope that it will be useful for non-commercial academic research,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
12
|
calphy API is published and distributed under the BSD 3-Clause "New" or "Revised" License
|
|
13
|
-
See the LICENSE FILE for more details.
|
|
13
|
+
See the LICENSE FILE for more details.
|
|
14
14
|
|
|
15
15
|
More information about the program can be found in:
|
|
16
16
|
Menon, Sarath, Yury Lysogorskiy, Jutta Rogal, and Ralf Drautz.
|
|
@@ -25,25 +25,26 @@ import subprocess as sub
|
|
|
25
25
|
import os
|
|
26
26
|
import stat
|
|
27
27
|
|
|
28
|
+
|
|
28
29
|
class Local:
|
|
29
30
|
"""
|
|
30
31
|
Local submission script
|
|
31
32
|
"""
|
|
33
|
+
|
|
32
34
|
def __init__(self, options, cores=1, directory=os.getcwd()):
|
|
33
|
-
self.queueoptions = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for (key, val) in options.items():
|
|
35
|
+
self.queueoptions = {
|
|
36
|
+
"scheduler": "local",
|
|
37
|
+
"jobname": "tis",
|
|
38
|
+
"queuename": None,
|
|
39
|
+
"memory": None,
|
|
40
|
+
"cores": cores,
|
|
41
|
+
"hint": None,
|
|
42
|
+
"directory": directory,
|
|
43
|
+
"options": [],
|
|
44
|
+
"commands": [],
|
|
45
|
+
"header": "#!/bin/bash",
|
|
46
|
+
}
|
|
47
|
+
for key, val in options.items():
|
|
47
48
|
if key in self.queueoptions.keys():
|
|
48
49
|
if val is not None:
|
|
49
50
|
self.queueoptions[key] = val
|
|
@@ -60,14 +61,10 @@ class Local:
|
|
|
60
61
|
fout.write(self.queueoptions["header"])
|
|
61
62
|
fout.write("\n")
|
|
62
63
|
|
|
63
|
-
#now
|
|
64
|
-
for module in self.queueoptions["modules"]:
|
|
65
|
-
fout.write("module load %s\n" %module)
|
|
66
|
-
|
|
67
|
-
#now finally commands
|
|
64
|
+
# now finally commands
|
|
68
65
|
for command in self.queueoptions["commands"]:
|
|
69
|
-
fout.write("%s\n"
|
|
70
|
-
fout.write("%s > %s 2> %s\n"
|
|
66
|
+
fout.write("%s\n" % command)
|
|
67
|
+
fout.write("%s > %s 2> %s\n" % (self.maincommand, jobout, joberr))
|
|
71
68
|
self.script = outfile
|
|
72
69
|
|
|
73
70
|
def submit(self):
|
|
@@ -77,41 +74,42 @@ class Local:
|
|
|
77
74
|
st = os.stat(self.script)
|
|
78
75
|
os.chmod(self.script, st.st_mode | stat.S_IEXEC)
|
|
79
76
|
cmd = [self.script]
|
|
80
|
-
proc = sub.Popen(cmd, stdin=sub.PIPE,stdout=sub.PIPE,stderr=sub.PIPE)
|
|
77
|
+
proc = sub.Popen(cmd, stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE)
|
|
81
78
|
return proc
|
|
82
79
|
|
|
80
|
+
|
|
83
81
|
class SLURM:
|
|
84
82
|
"""
|
|
85
83
|
Slurm class for writing submission script
|
|
86
84
|
"""
|
|
85
|
+
|
|
87
86
|
def __init__(self, options, cores=1, directory=os.getcwd()):
|
|
88
87
|
"""
|
|
89
88
|
Create class
|
|
90
89
|
"""
|
|
91
|
-
self.queueoptions = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
for
|
|
90
|
+
self.queueoptions = {
|
|
91
|
+
"scheduler": "slurm",
|
|
92
|
+
"jobname": "tis",
|
|
93
|
+
"queuename": None,
|
|
94
|
+
"walltime": "23:59:00",
|
|
95
|
+
"memory": "3GB",
|
|
96
|
+
"cores": cores,
|
|
97
|
+
"hint": "nomultithread",
|
|
98
|
+
"directory": directory,
|
|
99
|
+
"options": [],
|
|
100
|
+
"commands": [
|
|
101
|
+
"uss=$(whoami)",
|
|
102
|
+
"find /dev/shm/ -user $uss -type f -mmin +30 -delete",
|
|
103
|
+
],
|
|
104
|
+
"header": "#!/bin/bash",
|
|
105
|
+
}
|
|
106
|
+
for key, val in options.items():
|
|
108
107
|
if key in self.queueoptions.keys():
|
|
109
108
|
if val is not None:
|
|
110
109
|
if val != "":
|
|
111
110
|
self.queueoptions[key] = val
|
|
112
111
|
self.maincommand = ""
|
|
113
112
|
|
|
114
|
-
|
|
115
113
|
def write_script(self, outfile):
|
|
116
114
|
"""
|
|
117
115
|
Write the script file
|
|
@@ -123,28 +121,24 @@ class SLURM:
|
|
|
123
121
|
fout.write(self.queueoptions["header"])
|
|
124
122
|
fout.write("\n")
|
|
125
123
|
|
|
126
|
-
#write the main header options
|
|
127
|
-
fout.write("#SBATCH --job-name=%s\n" %self.queueoptions["jobname"])
|
|
128
|
-
fout.write("#SBATCH --time=%s\n"
|
|
124
|
+
# write the main header options
|
|
125
|
+
fout.write("#SBATCH --job-name=%s\n" % self.queueoptions["jobname"])
|
|
126
|
+
fout.write("#SBATCH --time=%s\n" % self.queueoptions["walltime"])
|
|
129
127
|
if self.queueoptions["queuename"] is not None:
|
|
130
|
-
fout.write("#SBATCH --partition=%s\n"%self.queueoptions["queuename"])
|
|
131
|
-
fout.write("#SBATCH --ntasks=%s\n"
|
|
132
|
-
fout.write("#SBATCH --mem-per-cpu=%s\n"%self.queueoptions["memory"])
|
|
133
|
-
fout.write("#SBATCH --hint=%s\n"
|
|
134
|
-
fout.write("#SBATCH --chdir=%s\n"
|
|
128
|
+
fout.write("#SBATCH --partition=%s\n" % self.queueoptions["queuename"])
|
|
129
|
+
fout.write("#SBATCH --ntasks=%s\n" % str(self.queueoptions["cores"]))
|
|
130
|
+
fout.write("#SBATCH --mem-per-cpu=%s\n" % self.queueoptions["memory"])
|
|
131
|
+
fout.write("#SBATCH --hint=%s\n" % self.queueoptions["hint"])
|
|
132
|
+
fout.write("#SBATCH --chdir=%s\n" % self.queueoptions["directory"])
|
|
135
133
|
|
|
136
|
-
#now write extra options
|
|
134
|
+
# now write extra options
|
|
137
135
|
for option in self.queueoptions["options"]:
|
|
138
|
-
fout.write("#SBATCH %s\n"
|
|
136
|
+
fout.write("#SBATCH %s\n" % option)
|
|
139
137
|
|
|
140
|
-
#now
|
|
141
|
-
for module in self.queueoptions["modules"]:
|
|
142
|
-
fout.write("module load %s\n" %module)
|
|
143
|
-
|
|
144
|
-
#now finally commands
|
|
138
|
+
# now finally commands
|
|
145
139
|
for command in self.queueoptions["commands"]:
|
|
146
|
-
fout.write("%s\n"
|
|
147
|
-
fout.write("%s > %s 2> %s\n"
|
|
140
|
+
fout.write("%s\n" % command)
|
|
141
|
+
fout.write("%s > %s 2> %s\n" % (self.maincommand, jobout, joberr))
|
|
148
142
|
|
|
149
143
|
self.script = outfile
|
|
150
144
|
|
|
@@ -152,40 +146,41 @@ class SLURM:
|
|
|
152
146
|
"""
|
|
153
147
|
Submit the job
|
|
154
148
|
"""
|
|
155
|
-
cmd = [
|
|
156
|
-
proc = sub.Popen(cmd, stdin=sub.PIPE,stdout=sub.PIPE,stderr=sub.PIPE)
|
|
149
|
+
cmd = ["sbatch", self.script]
|
|
150
|
+
proc = sub.Popen(cmd, stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE)
|
|
157
151
|
print(f'submitting {self.queueoptions["jobname"]}')
|
|
158
152
|
proc.communicate()
|
|
159
153
|
return proc
|
|
160
154
|
|
|
161
155
|
|
|
162
|
-
|
|
163
156
|
class SGE:
|
|
164
157
|
"""
|
|
165
158
|
Slurm class for writing submission script
|
|
166
159
|
"""
|
|
160
|
+
|
|
167
161
|
def __init__(self, options, cores=1, directory=os.getcwd()):
|
|
168
162
|
"""
|
|
169
163
|
Create class
|
|
170
164
|
"""
|
|
171
|
-
self.queueoptions = {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
165
|
+
self.queueoptions = {
|
|
166
|
+
"scheduler": "sge",
|
|
167
|
+
"jobname": "tis",
|
|
168
|
+
"walltime": "23:59:00",
|
|
169
|
+
"queuename": None,
|
|
170
|
+
"memory": "3GB",
|
|
171
|
+
"system": "smp",
|
|
172
|
+
"commands": [],
|
|
173
|
+
"options": [
|
|
174
|
+
"-j y",
|
|
175
|
+
"-R y",
|
|
176
|
+
"-P ams.p",
|
|
177
|
+
],
|
|
178
|
+
"cores": cores,
|
|
179
|
+
"hint": None,
|
|
180
|
+
"directory": directory,
|
|
181
|
+
"header": "#!/bin/bash",
|
|
182
|
+
}
|
|
183
|
+
for key, val in options.items():
|
|
189
184
|
if key in self.queueoptions.keys():
|
|
190
185
|
if val is not None:
|
|
191
186
|
self.queueoptions[key] = val
|
|
@@ -195,40 +190,40 @@ class SGE:
|
|
|
195
190
|
"""
|
|
196
191
|
Write the script file
|
|
197
192
|
"""
|
|
193
|
+
jobout = ".".join([outfile, "out"])
|
|
194
|
+
joberr = ".".join([outfile, "err"])
|
|
195
|
+
|
|
198
196
|
with open(outfile, "w") as fout:
|
|
199
197
|
fout.write(self.queueoptions["header"])
|
|
200
198
|
fout.write("\n")
|
|
201
199
|
|
|
202
|
-
#write the main header options
|
|
203
|
-
fout.write("#$ -N %s\n" %self.queueoptions["jobname"])
|
|
204
|
-
fout.write("#$ -l h_rt=%s\n"
|
|
205
|
-
fout.write("#$ -l qname=%s\n"%self.queueoptions["queuename"])
|
|
206
|
-
fout.write(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
200
|
+
# write the main header options
|
|
201
|
+
fout.write("#$ -N %s\n" % self.queueoptions["jobname"])
|
|
202
|
+
fout.write("#$ -l h_rt=%s\n" % self.queueoptions["walltime"])
|
|
203
|
+
fout.write("#$ -l qname=%s\n" % self.queueoptions["queuename"])
|
|
204
|
+
fout.write(
|
|
205
|
+
"#$ -pe %s %s\n"
|
|
206
|
+
% (self.queueoptions["system"], str(self.queueoptions["cores"]))
|
|
207
|
+
)
|
|
208
|
+
fout.write("#$ -l h_vmem=%s\n" % self.queueoptions["memory"])
|
|
209
|
+
fout.write("#$ -cwd %s\n" % self.queueoptions["directory"])
|
|
210
|
+
|
|
211
|
+
# now write extra options
|
|
211
212
|
for option in self.queueoptions["options"]:
|
|
212
|
-
fout.write("#$ %s\n"
|
|
213
|
-
|
|
214
|
-
#now write modules
|
|
215
|
-
for module in self.queueoptions["modules"]:
|
|
216
|
-
fout.write("module load %s\n" %module)
|
|
213
|
+
fout.write("#$ %s\n" % option)
|
|
217
214
|
|
|
218
|
-
#now finally commands
|
|
215
|
+
# now finally commands
|
|
219
216
|
for command in self.queueoptions["commands"]:
|
|
220
|
-
fout.write("%s\n"
|
|
217
|
+
fout.write("%s\n" % command)
|
|
221
218
|
|
|
222
|
-
fout.write("%s > %s 2> %s\n"
|
|
223
|
-
|
|
224
|
-
self.script = outfile
|
|
219
|
+
fout.write("%s > %s 2> %s\n" % (self.maincommand, jobout, joberr))
|
|
225
220
|
|
|
221
|
+
self.script = outfile
|
|
226
222
|
|
|
227
223
|
def submit(self):
|
|
228
224
|
"""
|
|
229
225
|
Submit the job
|
|
230
226
|
"""
|
|
231
|
-
cmd = [
|
|
232
|
-
proc = sub.Popen(cmd, stdin=sub.PIPE,stdout=sub.PIPE,stderr=sub.PIPE)
|
|
227
|
+
cmd = ["qsub", self.script]
|
|
228
|
+
proc = sub.Popen(cmd, stdin=sub.PIPE, stdout=sub.PIPE, stderr=sub.PIPE)
|
|
233
229
|
return proc
|
|
234
|
-
|