33
44import time
55import numpy as np
6+ from copy import copy
7+ import cellconstructor
68
79from .Ensemble import Ensemble
810try :
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
1116except 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
0 commit comments