Skip to content

Commit 560b5c0

Browse files
committed
Working version
1 parent a95786a commit 560b5c0

5 files changed

Lines changed: 792 additions & 55 deletions

File tree

Modules/Cluster.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,10 +1511,11 @@ def compute_single_jobarray(jobs_id, calc):
15111511
if error_struct > 1e-2:
15121512
print("ERROR IDENTIFYING STRUCTURE!")
15131513
MSG = """
1514-
Error in thread {}.
1515-
Displacement between the expected structure {} and the one readed from the calculator
1516-
is of {} A.
1517-
""".format(threading.get_native_id(), jobs_id[i], error_struct)
1514+
Error in thread {}.
1515+
Displacement between the expected structure {}
1516+
and the one readed from the calculator
1517+
is of {} A.
1518+
""".format(threading.get_native_id(), jobs_id[i], error_struct)
15181519
print(MSG)
15191520
ensemble.structures[jobs_id[i]].save_scf('t_{}_error_struct_generated_{}.scf'.format(threading.get_native_id(), jobs_id[i]))
15201521
structures[i].save_scf('t_{}_error_struct_cmp_local_{}.scf'.format(threading.get_native_id(), jobs_id[i]))

Modules/aiida_ensemble.py

Lines changed: 117 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33

44
import time
55
import numpy as np
6+
from copy import copy
7+
import cellconstructor
68

79
from .Ensemble import Ensemble
810
try:
911
import aiida
1012
import aiida_quantumespresso
13+
from qe_tools import CONSTANTS
14+
15+
gpa_to_rybohr3 = 1. / (CONSTANTS.ry_si/ CONSTANTS.bohr_si**3 / 1.0e9) # GPa -> Ry/Bohr^3
1116
except ImportError:
1217
import warnings
1318
warnings.warn('aiida or aiida-quantumespresso are not installed')
@@ -19,10 +24,10 @@ class AiiDAEnsemble(Ensemble):
1924
def compute_ensemble(
2025
self,
2126
pw_code: str,
22-
profile: str | None = None,
2327
protocol: str = 'moderate',
2428
options: dict = None,
2529
overrides: dict = None,
30+
group_label: str = None,
2631
**kwargs
2732
):
2833
"""Get ensemble properties.
@@ -35,39 +40,35 @@ def compute_ensemble(
3540
---------
3641
pw_code:
3742
The string associated with the AiiDA code for `pw.x`
38-
profile:
39-
AiiDA profile to use during the calculations. If None, the default is used
4043
protocol:
4144
The protocol to be used; available protocols are 'fast', 'moderate' and 'precise'
4245
options:
4346
The options for the calculations, such as the resources, wall-time, etc.
4447
overrides:
4548
The overrides for the get_builder_from_protocol
49+
group_label:
50+
The group label where to add the submitted nodes for eventual future inspection
4651
kwargs:
4752
The kwargs for the get_builder_from_protocol
4853
"""
49-
from aiida import load_profile
50-
from aiida.engine import submit
51-
from aiida.plugins import WorkflowFactory
52-
from aiida.orm import StructureData
54+
from aiida.orm import load_group
5355

54-
PwBaseWorkChain = WorkflowFactory('quantumespresso.pw')
55-
56-
if profile:
57-
load_profile(profile)
58-
else:
59-
load_profile()
56+
group = None if group_label is None else load_group(group_label)
6057

6158
# Check if not all the calculation needs to be done
62-
n_calcs = np.sum( self.force_computed.astype(int))
59+
if self.force_computed is None:
60+
self.force_computed = np.array([False] * self.N, dtype=bool)
61+
62+
n_calcs = np.sum(self.force_computed.astype(int))
6363
computing_ensemble = self
6464

65+
self.has_stress = True # by default we calculate stresses with the `get_builder_from_protocol`
6566
if overrides:
6667
try:
6768
tstress = overrides['pw']['parameters']['CONTROL']['tstress']
6869
self.has_stress = tstress
6970
except KeyError:
70-
self.has_stress = True # by default we calculate stresses with the `get_builder_from_protocol`
71+
pass
7172

7273
# Check wheter compute the whole ensemble, or just a small part
7374
should_i_merge = False
@@ -76,43 +77,37 @@ def compute_ensemble(
7677
computing_ensemble = self.get_noncomputed()
7778
self.remove_noncomputed()
7879

79-
# ============= HERE AIIDA PART ============= #
80-
structures_data = [StructureData(ase=cc.get_ase_atoms()) for cc in self.structures]
81-
workchains = []
82-
83-
for i, structure in enumerate(structures_data):
84-
builder = PwBaseWorkChain.get_builder_from_protocol(
85-
code=pw_code,
86-
structure=structure,
87-
options=options,
88-
overrides=overrides,
89-
**kwargs
90-
)
91-
builder.label = str(i)
92-
workchains.append(submit(builder))
80+
# ============= AIIDA SECTION ============= #
81+
workchains = submit_and_get_workchains(
82+
structures=computing_ensemble.structures,
83+
pw_code=pw_code,
84+
temperature=self.current_T,
85+
protocol=protocol,
86+
options=options,
87+
overrides=overrides,
88+
**kwargs
89+
)
9390

94-
workchains_success = []
95-
workchains_failed = []
96-
97-
while(workchains):
98-
for workchain in workchains:
99-
if workchain.is_finished:
100-
if workchain.is_failed:
101-
workchains_failed.append(workchain)
102-
else:
103-
workchains_success.append(workchain)
104-
workchains.remove(workchain) # here it may be critical
105-
106-
time.sleep(60) # wait before checking again
91+
if group:
92+
group.add_nodes(workchains)
93+
94+
workchains_copy = copy(workchains)
95+
while(workchains_copy):
96+
workchains_copy = get_running_workchains(workchains_copy, computing_ensemble.force_computed)
97+
if workchains_copy:
98+
time.sleep(60) # wait before checking again
10799

108-
# ---- TO DO
109-
# * Take energies, forces, stresses
110-
# * Print which one was successfull
111-
# * Add correct labels to workchains to gather afterwards
112-
# * For sure something else
113-
# * Add the possibility of adding wc to group
114-
# * Keep track is some better way (printing?) of the workchains submitted
115-
# ============= HERE AIIDA PART ============= #
100+
for i, is_computed in enumerate(computing_ensemble.force_computed):
101+
if is_computed:
102+
out = workchains[i].outputs
103+
computing_ensemble.energies[i] = out.output_parameters.dict.energy / CONSTANTS.ry_to_ev
104+
computing_ensemble.forces[i] = out.output_trajectory.get_array('forces')[-1] / CONSTANTS.ry_to_ev
105+
if self.has_stress:
106+
computing_ensemble.stresses[i] = out.output_trajectory.get_array('stress')[-1] * gpa_to_rybohr3
107+
# ============= AIIDA SECTION ============= #
108+
109+
if self.has_stress:
110+
computing_ensemble.stress_computed = copy(computing_ensemble.force_computed)
116111

117112
print("CE BEFORE MERGE:", len(self.force_computed))
118113

@@ -121,5 +116,76 @@ def compute_ensemble(
121116
self.merge(computing_ensemble)
122117
print("CE AFTER MERGE:", len(self.force_computed))
123118

124-
print('ENSEMBLE ALL PROPERTIES:', self.all_properties)
125119

120+
def get_running_workchains(workchains: list, success: list[bool]) -> list:
121+
"""Get the running workchains popping the finished ones.
122+
123+
Two extra array should be given to populate the successfully finished runs.
124+
"""
125+
wcs_left = copy(workchains)
126+
127+
for workchain in workchains:
128+
if workchain.is_finished:
129+
if workchain.is_failed:
130+
print(f'[FAILURE] for <PwBaseWorkChain> with PK={workchain.pk}')
131+
else:
132+
index = int(workchain.label.split('_')[-1])
133+
success[index] = True
134+
print(f'[SUCCESS] for <PwBaseWorkChain> with PK={workchain.pk}')
135+
136+
wcs_left.remove(workchain) # here it may be critical
137+
138+
return wcs_left
139+
140+
141+
def submit_and_get_workchains(
142+
structures: list[cellconstructor.Structure.Structure],
143+
pw_code: str,
144+
temperature: float | int,
145+
protocol: str = 'moderate',
146+
options: dict = None,
147+
overrides: dict = None,
148+
**kwargs
149+
):
150+
"""Submit and return the workchains for a list of :class:`~cellconstructor.Structure.Structure`.
151+
152+
Parameters
153+
---------
154+
structures:
155+
a list of :class:`~cellconstructor.Structure.Structure` to run via PwBaseWorkChain.
156+
pw_code:
157+
The string associated with the AiiDA code for `pw.x`
158+
temperature:
159+
The temperature corresponding to the structures ensemble
160+
protocol:
161+
The protocol to be used; available protocols are 'fast', 'moderate' and 'precise'
162+
options:
163+
The options for the calculations, such as the resources, wall-time, etc.
164+
overrides:
165+
The overrides for the get_builder_from_protocol
166+
kwargs:
167+
The kwargs for the get_builder_from_protocol
168+
"""
169+
from aiida.engine import submit
170+
from aiida.plugins import WorkflowFactory
171+
from aiida.orm import StructureData
172+
173+
PwBaseWorkChain = WorkflowFactory('quantumespresso.pw.base')
174+
175+
structures_data = [StructureData(ase=cc.get_ase_atoms()) for cc in structures]
176+
workchains = []
177+
178+
for i, structure in enumerate(structures_data):
179+
builder = PwBaseWorkChain.get_builder_from_protocol(
180+
code=pw_code,
181+
structure=structure,
182+
protocol=protocol,
183+
options=options,
184+
overrides=overrides,
185+
**kwargs
186+
)
187+
builder.metadata.label = 'T_{}_id_{}'.format(temperature, i)
188+
workchains.append(submit(builder))
189+
print(f'Launched <PwBaseWorkChain> with PK={workchains[-1].pk}')
190+
191+
return workchains

tests/aiida_ensemble/__init__.py

Whitespace-only changes.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Tests for :mod:`sscha.aiida_ensemble`."""
2+
import pytest
3+
4+
5+
@pytest.mark.usefixtures('aiida_profile')
6+
def test_get_running_workchains(generate_workchain_pw_node):
7+
"""Test the :func:`sscha.aiida_ensemble.get_running_workchains` method."""
8+
from plumpy import ProcessState
9+
from sscha.aiida_ensemble import get_running_workchains
10+
11+
finished = ProcessState.FINISHED
12+
running = ProcessState.RUNNING
13+
14+
workchains = [
15+
generate_workchain_pw_node(process_state=finished, exit_status=0, label='T_300_id_0'),
16+
generate_workchain_pw_node(process_state=finished, exit_status=300, label='T_300_id_1'),
17+
]
18+
success = [False, False]
19+
20+
wcs_left = get_running_workchains(workchains=workchains, success=success)
21+
22+
assert not wcs_left
23+
assert success == [True, False]
24+
25+
workchains = [
26+
generate_workchain_pw_node(process_state=running, label='T_300_id_0'),
27+
generate_workchain_pw_node(process_state=finished, label='T_300_id_1'),
28+
generate_workchain_pw_node(process_state=running, label='T_300_id_2'),
29+
]
30+
success = [False, False, False]
31+
32+
wcs_left = get_running_workchains(workchains=workchains, success=success)
33+
34+
assert wcs_left == [workchains[0], workchains[2]]
35+
assert success == [False, True, False]
36+
37+
38+
@pytest.mark.usefixtures('aiida_profile')
39+
def test_submit_and_get_workchains(fixture_code):
40+
"""Test the :func:`sscha.aiida_ensemble.submit_and_get_workchains` method."""
41+
from cellconstructor.Structure import Structure
42+
from sscha.aiida_ensemble import submit_and_get_workchains
43+
44+
pw_code = fixture_code('quantumespresso.pw')
45+
structures = [Structure(nat=1) for _ in range(5)]
46+
47+
workchains = submit_and_get_workchains(
48+
structures=structures,
49+
pw_code=pw_code,
50+
temperature=300,
51+
)
52+
53+
assert len(workchains) == 5
54+
55+

0 commit comments

Comments
 (0)