-
Notifications
You must be signed in to change notification settings - Fork 109
/
Copy pathbenchmarks.py
202 lines (170 loc) · 6.08 KB
/
benchmarks.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
"""ReFrame benchmark for QuantumESPRESSO"""
import os
from typing import TypeVar
import reframe as rfm
import reframe.utility.sanity as sn
from reframe.core.builtins import (performance_function, run_after, run_before,
sanity_function)
from reframe.core.exceptions import SanityError
from reframe.core.parameters import TestParam as parameter
from reframe.core.variables import TestVar as variable
R = TypeVar('R')
INPUT_TEMPLATE = """&CONTROL
calculation = "scf",
prefix = "Si",
pseudo_dir = ".",
outdir = "./out",
restart_mode = "from_scratch"
verbosity = 'high'
/
&SYSTEM
ibrav = 2,
celldm(1) = 10.2,
nat = 2,
ntyp = 1,
nbnd = {nbnd}
ecutwfc = {ecut}
/
&ELECTRONS
conv_thr = 1.D-8,
mixing_beta = 0.7D0,
/
ATOMIC_SPECIES
Si 28.086 {pseudo}
ATOMIC_POSITIONS
Si 0.00 0.00 0.00
Si 0.25 0.25 0.25
K_POINTS {{automatic}}
15 15 15 0 0 0
"""
@rfm.simple_test
class QEspressoPWCheck(rfm.RunOnlyRegressionTest):
"""QuantumESPRESSO benchmark test.
`QuantumESPRESSO <https://www.quantum-espresso.org/>`__ is an integrated
suite of Open-Source computer codes for electronic-structure calculations
and materials modeling at the nanoscale.
The benchmarks consist of one input file templated inside the code and
a pseudo-potential file that is downloaded from the official repository.
This tests aims at measuring the scalability of the pw.x executable, in
particular the FFT and diagonalization algorithms, by running a simple
silicon calculation with high `ecut` (increases size of FFTs) and `nbnd`
(increases size of matrices to diagonalize) values."""
#: Parametert to tests the performance of the FFTW algorithm,
#: higher `ecut` implicates more FFTs
#:
#: :type: :class:`int`
#: :values: ``[50, 150]``
ecut = parameter([50, 150], loggable=True)
#: Parameter to Tests the performance of the diagonalization algorithm, higher ecut -> more FFTs
#: higher `nbnd` implicates bigger matrices
#:
#: :type: :class:`int`
#: :values: ``[10, 200]``
nbnd = parameter([10, 200], loggable=True)
executable = 'pw.x'
tags = {'sciapp', 'chemistry'}
descr = 'QuantumESPRESSO pw.x benchmark'
#: The name of the input file used.
#:
#: :type: :class:`str`
#: :default: ``'Si.scf.in'``
input_name: str = variable(str, value='Si.scf.in')
#: The pseudo-potential file to be used check
#: https://www.quantum-espresso.org/pseudopotentials/ for more info
#:
#: :type: :class:`str`
#: :default: ``'Si.pbe-n-kjpaw_psl.1.0.0.UPF'``
pp_name: str = variable(str, value='Si.pbe-n-kjpaw_psl.1.0.0.UPF')
@run_after('init')
def prepare_test(self):
"""Hook to the set the downloading of the pseudo-potentials"""
self.prerun_cmds = [(
'wget -q http://pseudopotentials.quantum-espresso.org/upf_files/'
f'{self.pp_name}'
)]
self.executable_opts += [f'-in {self.input_name}']
@run_after('setup')
def write_input(self):
"""Write the input file for the calculation"""
inp_file = os.path.join(self.stagedir, self.input_name)
with open(inp_file, 'w', encoding='utf-8') as file:
file.write(
INPUT_TEMPLATE.format(
ecut=self.ecut,
nbnd=self.nbnd,
pseudo=self.pp_name,
))
@staticmethod
@sn.deferrable
def extractsingle_or_val(*args, on_except_value: str = '0s') -> str:
"""Wrap extractsingle_or_val to return a default value if the regex is
not found.
Returns:
str: The value of the regular expression
"""
try:
res = sn.extractsingle(*args).evaluate()
except SanityError:
res = on_except_value
return res
@staticmethod
@sn.deferrable
def convert_timings(timing: str) -> float:
"""Convert timings to seconds"""
if timing is None:
return 0
days, timing = (['0', '0'] + timing.split('d'))[-2:]
hours, timing = (['0', '0'] + timing.split('h'))[-2:]
minutes, timing = (['0', '0'] + timing.split('m'))[-2:]
seconds = timing.split('s')[0]
return (
float(days) * 86400 +
float(hours) * 3600 +
float(minutes) * 60 +
float(seconds)
)
@performance_function('s')
def extract_report_time(self, name: str = None, kind: str = None) -> float:
"""Extract timings from pw.x stdout
Args:
name (str, optional): Name of the timing to extract.
Defaults to None.
kind (str, optional): Kind ('cpu' or 'wall) of timing to extract.
Defaults to None.
Raises:
ValueError: If the kind is not 'cpu' or 'wall'
Returns:
float: The timing in seconds
"""
if kind is None:
return 0
kind = kind.lower()
if kind == 'cpu':
tag = 1
elif kind == 'wall':
tag = 2
else:
raise ValueError(f'unknown kind: {kind}')
# Possible formats
# PWSCF : 4d 6h19m CPU 10d14h38m WALL
# --> (Should also catch spaces)
return self.convert_timings(
self.extractsingle_or_val(
fr'{name}\s+:\s+(.+)\s+CPU\s+(.+)\s+WALL',
self.stdout, tag, str
))
@run_before('performance')
def set_perf_variables(self):
"""Build a dictionary of performance variables"""
timings = [
'PWSCF', 'electrons', 'c_bands', 'cegterg', 'calbec',
'fft', 'ffts', 'fftw'
]
for name in timings:
for kind in ['cpu', 'wall']:
res = self.extract_report_time(name, kind)
self.perf_variables[f'{name}_{kind}'] = res
@sanity_function
def assert_job_finished(self):
"""Check if the job finished successfully"""
return sn.assert_found(r'JOB DONE', self.stdout)