Skip to content

Commit 001ac57

Browse files
authored
Merge pull request #3233 from Crivella/feature-MetalWalls
[testlib] Added MetalWalls scalability test for `mw`
2 parents 2ef03f0 + dd67b10 commit 001ac57

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

docs/hpctestlib.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ Scientific Applications
106106
:members:
107107
:show-inheritance:
108108

109+
.. automodule:: hpctestlib.sciapps.metalwalls.benchmarks
110+
:members:
111+
:show-inheritance:
112+
109113
System
110114
=======================
111115

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
"""ReFrame benchmark for MetalWalls"""
2+
import re
3+
4+
import reframe as rfm
5+
import reframe.utility.sanity as sn
6+
from reframe.core.builtins import (performance_function, run_after, run_before,
7+
sanity_function)
8+
from reframe.core.parameters import TestParam as parameter
9+
from reframe.core.variables import TestVar as variable
10+
11+
address_tpl = (
12+
'https://gitlab.com/ampere2/metalwalls/-/raw/{version}/{bench}/{file}'
13+
'?ref_type=tags&inline=false'
14+
)
15+
16+
extract_fields = [
17+
#######
18+
('Ions->Atoms Coulomb potential', 'long range', 'I->A Cpot'),
19+
('Ions->Atoms Coulomb potential', 'k==0', 'I->A Cpot'),
20+
('Ions->Atoms Coulomb potential', 'short range', 'I->A Cpot'),
21+
22+
#######
23+
('Atoms->Atoms Coulomb potential', 'long range', 'A->A Cpot'),
24+
('Atoms->Atoms Coulomb potential', 'k==0', 'A->A Cpot'),
25+
('Atoms->Atoms Coulomb potential', 'short range', 'A->A Cpot'),
26+
('Atoms->Atoms Coulomb potential', 'self', 'A->A Cpot'),
27+
28+
#######
29+
('Atoms->Atoms Coulomb grad Q', 'long range', 'A->A gradQ'),
30+
('Atoms->Atoms Coulomb grad Q', 'k==0', 'A->A gradQ'),
31+
('Atoms->Atoms Coulomb grad Q', 'short range', 'A->A gradQ'),
32+
('Atoms->Atoms Coulomb grad Q', 'self', 'A->A gradQ'),
33+
34+
#######
35+
('Ions Coulomb forces', 'long range', 'Ions Cfrc'),
36+
('Ions Coulomb forces', 'k==0', 'Ions Cfrc'),
37+
('Ions Coulomb forces', 'short range', 'Ions Cfrc'),
38+
('Ions Coulomb forces', 'intramolecular', 'Ions Cfrc'),
39+
40+
#######
41+
('Ions Coulomb potential', 'long range', 'Ions Cpot'),
42+
('Ions Coulomb potential', 'k==0', 'Ions Cpot'),
43+
('Ions Coulomb potential', 'short range', 'Ions Cpot'),
44+
('Ions Coulomb potential', 'intramolecular', 'Ions Cpot'),
45+
('Ions Coulomb potential', 'self', 'Ions Cpot'),
46+
47+
#######
48+
(
49+
'Ions Coulomb electric field (due to charges)',
50+
'long range',
51+
'Ions Cfield(charges)'
52+
),
53+
(
54+
'Ions Coulomb electric field (due to charges)',
55+
'k==0',
56+
'Ions Cfield(charges)'
57+
),
58+
(
59+
'Ions Coulomb electric field (due to charges)',
60+
'short range',
61+
'Ions Cfield(charges)'
62+
),
63+
(
64+
'Ions Coulomb electric field (due to charges)',
65+
'intramolecular',
66+
'Ions Cfield(charges)'
67+
),
68+
69+
#######
70+
(
71+
'Ions Coulomb electric field (due to dipoles)',
72+
'long range',
73+
'Ions Cfield(dipoles)'
74+
),
75+
(
76+
'Ions Coulomb electric field (due to dipoles)',
77+
'k==0',
78+
'Ions Cfield(dipoles)'
79+
),
80+
(
81+
'Ions Coulomb electric field (due to dipoles)',
82+
'short range',
83+
'Ions Cfield(dipoles)'
84+
),
85+
(
86+
'Ions Coulomb electric field (due to dipoles)',
87+
'intramolecular',
88+
'Ions Cfield(dipoles)'
89+
),
90+
(
91+
'Ions Coulomb electric field (due to dipoles)',
92+
'self',
93+
'Ions Cfield(dipoles)'
94+
),
95+
96+
#######
97+
('Ions Coulomb electric field gradient', 'long range', 'Ions Cfield grad'),
98+
(
99+
'Ions Coulomb electric field gradient',
100+
'short range',
101+
'Ions Cfield grad'
102+
),
103+
('Ions Coulomb electric field gradient', 'self', 'Ions Cfield grad'),
104+
105+
#######
106+
('Ions Coulomb gradient mu', 'long range', 'Ions C mu_grad'),
107+
('Ions Coulomb gradient mu', 'k==0', 'Ions C mu_grad'),
108+
('Ions Coulomb gradient mu', 'short range', 'Ions C mu_grad'),
109+
('Ions Coulomb gradient mu', 'self', 'Ions C mu_grad'),
110+
111+
#######
112+
('Rattle', 'positions', 'Rattle'),
113+
('Rattle', 'velocities', 'Rattle'),
114+
115+
#######
116+
('van der Waals', 'vdW forces', 'van der Waals'),
117+
('van der Waals', 'vdW potential', 'van der Waals'),
118+
119+
#######
120+
('Intramolecular', 'Intramolecular forces', 'Intramolecular'),
121+
('Intramolecular', 'Intramolecular potential', 'Intramolecular'),
122+
123+
#######
124+
('Additional degrees', 'Electrode charge computation', 'Deg of freedom'),
125+
('Additional degrees', 'Inversion of the matrix', 'Deg of freedom'),
126+
('Additional degrees', 'One matrix-vector product', 'Deg of freedom'),
127+
('Additional degrees', 'Melt dipoles computation', 'Deg of freedom'),
128+
('Additional degrees', 'Inversion of the matrix', 'Deg of freedom'),
129+
('Additional degrees', 'One matrix-vector product', 'Deg of freedom'),
130+
('Additional degrees', 'AIM DOFs computation', 'Deg of freedom'),
131+
132+
#######
133+
('Diagnostics', 'diagnostics computations', 'Diagnostics'),
134+
('Diagnostics', 'IO', 'Diagnostics'),
135+
]
136+
137+
138+
@rfm.simple_test
139+
class MetalWallsCheck(rfm.RunOnlyRegressionTest):
140+
"""MetalWalls benchmark test.
141+
142+
`MetalWalls <https://gitlab.com/ampere2/metalwalls>`__ is a molecular
143+
dynamics code dedicated to the modelling of electrochemical systems.
144+
Its main originality is the inclusion of a series of methods allowing to
145+
apply a constant potential within the electrode materials.
146+
147+
The benchmarks consist of a set of different inputs files that vary in the
148+
number of atoms and the type of simulation performed.
149+
They can be found in the following repository, which is also versioned:
150+
https://gitlab.com/ampere2/metalwalls/.
151+
152+
"""
153+
154+
#: The name of the output files to keep.
155+
#:
156+
#: :type: :class:`List[str]`
157+
#: :default: ``'run.out'``
158+
keep_files = ['run.out', ]
159+
160+
#: The version of the benchmark suite to use.
161+
#:
162+
#: :type: :class:`str`
163+
#: :default: ``'21.06.1'``
164+
benchmark_version = variable(str, value='21.06.1', loggable=True)
165+
166+
executable = 'mw'
167+
tags = {'sciapp', 'chemistry'}
168+
descr = 'MetalWalls `mw` benchmark'
169+
170+
#: Collect and report detailed performance metrics.
171+
#:
172+
#: :type: :class:`bool`
173+
#: :default: ``False``
174+
debug_metrics = variable(bool, value=False, loggable=True)
175+
176+
#: Parameter pack encoding the benchmark information.
177+
#:
178+
#: The first element of the tuple refers to the benchmark name,
179+
#: the second is the final kinetic energy the third is the related
180+
#: tolerance, the fourth is the absolute temperature and the fifth is
181+
#: the related tolerance
182+
#:
183+
#: :type: `Tuple[str, float, float, float, float]`
184+
#: :values:
185+
benchmark_info = parameter([
186+
('hackathonGPU/benchmark', 14.00, 0.05, 301.74, 0.5),
187+
('hackathonGPU/benchmark2', 14.00, 0.05, 301.74, 0.5),
188+
('hackathonGPU/benchmark3', 16.08, 0.05, 293.42, 0.5),
189+
('hackathonGPU/benchmark4', 16.08, 0.05, 293.42, 0.5),
190+
('hackathonGPU/benchmark5', 25.72, 0.05, 297.47, 0.5),
191+
('hackathonGPU/benchmark6', 25.72, 0.05, 297.47, 0.5),
192+
], fmt=lambda x: x[0], loggable=True)
193+
194+
@run_after('init')
195+
def prepare_test(self):
196+
"""Hook to the set the downloading of the pseudo-potentials"""
197+
self.__bench, _, _, _, _ = self.benchmark_info
198+
self.descr = f'MetalWalls {self.__bench} benchmark'
199+
files_addresses = [
200+
address_tpl.format(
201+
version=self.benchmark_version,
202+
bench=self.__bench,
203+
file=_
204+
) for _ in ['data.inpt', 'runtime.inpt']
205+
]
206+
self.prerun_cmds += [
207+
f"curl -LJO '{_}'" for _ in files_addresses
208+
]
209+
210+
@performance_function('s')
211+
def total_elapsed_time(self):
212+
"""Extract the total elapsed time from the output file"""
213+
return sn.extractsingle(
214+
r'Total elapsed time:\s+(?P<time>\S+)', 'run.out', 'time', float
215+
)
216+
217+
@sn.deferrable
218+
def extract_kinetic_energy(self):
219+
"""Extract the final kinetic energy from the output file"""
220+
rgx = r'\|step\| +kinetic energy: +(?P<flag>\S+)'
221+
app = sn.extractall(rgx, 'run.out', 'flag', float)
222+
return app[-1]
223+
224+
@sn.deferrable
225+
def extract_temperature(self):
226+
"""Extract the final temperature from the output file"""
227+
rgx = r'\|step\| +temperature: +(?P<flag>\S+)'
228+
app = sn.extractall(rgx, 'run.out', 'flag', float)
229+
return app[-1]
230+
231+
def extract_time(
232+
self, name: str = None, parent: str = None, kind: str = None
233+
) -> float:
234+
"""Extract the time from a specific report section of the output file
235+
236+
Args:
237+
name (str): The name of the report to extract
238+
parent (str): The parent section of the report
239+
kind (str): The kind of time to extract (avg or cumul)
240+
"""
241+
if kind is None:
242+
return 0
243+
244+
kind = kind.lower()
245+
if kind == 'avg':
246+
tag = 1
247+
elif kind == 'cumul':
248+
tag = 2
249+
else:
250+
raise ValueError(f'Unknown kind: {kind}')
251+
252+
# Example Fromat:
253+
# Ions->Atoms Coulomb potential
254+
# -----------------------------
255+
# long range 9.55040E-03 9.64590E-01 0.71
256+
# k==0 2.48271E-02 2.50754E+00 1.84
257+
# short range 8.72464E-02 8.81189E+00 6.46
258+
#
259+
# NEXT SECTION
260+
res = -1
261+
rgx = re.compile(rf'^ +{name}\s+(\S+) +(\S+)')
262+
flag = False
263+
with open('run.out', 'r') as file:
264+
for line in file:
265+
if flag:
266+
if rgx.match(line):
267+
res = float(rgx.match(line).group(tag))
268+
break
269+
elif not line.strip():
270+
break
271+
else:
272+
if parent in line:
273+
flag = True
274+
275+
return res
276+
277+
@run_before('performance')
278+
def set_perf_variables(self):
279+
"""Build a dictionary of performance variables"""
280+
self.perf_variables['total_elapsed_time'] = self.total_elapsed_time()
281+
282+
if self.debug_metrics:
283+
for parent, name, short in extract_fields:
284+
name2 = name.replace(' ', '_')
285+
for kind in ['avg', 'cumul']:
286+
app = self.extract_time(name, parent, kind)
287+
self.perf_variables[f'{short}__{name2}__{kind}'] = app
288+
289+
@sanity_function
290+
def assert_job_finished(self):
291+
"""Check if the job finished successfully"""
292+
energy = self.extract_kinetic_energy()
293+
temp = self.extract_temperature()
294+
_, energy_ref, energy_tol, temp_ref, temp_tol = self.benchmark_info
295+
en_rtol = energy_tol / energy_ref
296+
t_rtol = temp_tol / temp_ref
297+
return sn.all([
298+
sn.assert_found(r'Total elapsed time', 'run.out'),
299+
sn.assert_reference(energy, energy_ref, -en_rtol, en_rtol),
300+
sn.assert_reference(temp, temp_ref, -t_rtol, t_rtol)
301+
])

0 commit comments

Comments
 (0)