55from typing import Tuple
66
77import numpy as np
8- import qcportal as ptl
8+ import qcportal
99import torch
1010from openmm import unit
1111from openmm .unit import Quantity
2121# =============================================================================
2222# UTILITY FUNCTIONS
2323# =============================================================================
24- def get_client ():
25- return ptl .FractalClient ()
24+ def get_client (url : str = "api.qcarchive.molssi.org" ) -> qcportal .client .PortalClient :
25+ """
26+ Returns a instance of the qcportal client.
27+
28+ Parameters
29+ ----------
30+ url: str, default="api.qcarchive.molssi.org"
31+ qcportal instance to connect
32+
33+ Returns
34+ -------
35+ qcportal.client.PortalClient
36+ qcportal client instance.
37+ """
38+ # Note, this may need to be modified to include username/password for non-public servers
39+ return qcportal .PortalClient (url )
2640
2741
2842def get_collection (
29- client ,
30- collection_type = "OptimizationDataset " ,
31- name = "OpenFF Full Optimization Benchmark 1" ,
43+ client ,
44+ collection_type = "optimization " ,
45+ name = "OpenFF Full Optimization Benchmark 1" ,
3246):
33- collection = client .get_collection (
34- collection_type ,
35- name ,
47+ """
48+ Connects to a specific dataset on qcportal
49+
50+ Parameters
51+ ----------
52+ client: qcportal.client, required
53+ The qcportal client instance
54+ collection_type: str, default="optimization"
55+ The type of qcarchive collection, options are
56+ "torsiondrive", "optimization", "gridoptimization", "reaction", "singlepoint" "manybody"
57+ name: str, default="OpenFF Full Optimization Benchmark 1"
58+ Name of the dataset
59+
60+ Returns
61+ -------
62+ (qcportal dataset, list(str))
63+ Tuple with an instance of qcportal dataset and list of record names
64+
65+ """
66+ collection = client .get_dataset (
67+ dataset_type = collection_type ,
68+ dataset_name = name ,
3669 )
3770
38- record_names = list ( collection .data . records )
71+ record_names = collection .entry_names
3972
4073 return collection , record_names
4174
4275
43- def get_graph (collection , record_name ):
44- # get record and trajectory
45- record = collection .get_record (record_name , specification = "default" )
46- entry = collection .get_entry (record_name )
47- from openff .toolkit .topology import Molecule
76+ def process_record (record , entry ):
77+ """
78+ Processes a given record/entry pair from a dataset and returns the graph
4879
49- mol = Molecule .from_qcschema (entry )
80+ Parameters
81+ ----------
82+ record: qcportal.optimization.record_models.OptimizationRecord
83+ qcportal record
84+ entry: cportal.optimization.dataset_models.OptimizationDatasetEntry
85+ qcportal entry
5086
51- try :
52- trajectory = record . get_trajectory ()
53- except :
54- return None
87+ Returns
88+ -------
89+ esp.Graph
90+ """
5591
56- if trajectory is None :
57- return None
92+ from openff .toolkit .topology import Molecule
5893
94+ if record .record_type == "optimization" :
95+ trajectory = record .trajectory
96+ if trajectory is None :
97+ return None
98+ else :
99+ raise Exception (
100+ f"{ record .record_type } is not supported: only optimization datasets can be processed."
101+ )
102+ mol = Molecule .from_qcschema (entry .dict ())
59103 g = esp .Graph (mol )
60104
61105 # energy is already hartree
62106 g .nodes ["g" ].data ["u_ref" ] = torch .tensor (
63107 [
64108 Quantity (
65- snapshot .properties . scf_total_energy ,
109+ snapshot .properties [ " scf_total_energy" ] ,
66110 esp .units .HARTREE_PER_PARTICLE ,
67111 ).value_in_unit (esp .units .ENERGY_UNIT )
68112 for snapshot in trajectory
@@ -74,7 +118,7 @@ def get_graph(collection, record_name):
74118 np .stack (
75119 [
76120 Quantity (
77- snapshot .get_molecule () .geometry ,
121+ snapshot .molecule .geometry ,
78122 unit .bohr ,
79123 ).value_in_unit (esp .units .DISTANCE_UNIT )
80124 for snapshot in trajectory
@@ -89,7 +133,7 @@ def get_graph(collection, record_name):
89133 [
90134 torch .tensor (
91135 Quantity (
92- snapshot . dict () ["return_result" ],
136+ np . array ( snapshot . properties ["return_result" ]). reshape (( - 1 , 3 )) ,
93137 esp .units .HARTREE_PER_PARTICLE / unit .bohr ,
94138 ).value_in_unit (esp .units .FORCE_UNIT ),
95139 dtype = torch .get_default_dtype (),
@@ -102,21 +146,106 @@ def get_graph(collection, record_name):
102146 return g
103147
104148
105- def fetch_td_record (record : ptl .models .torsiondrive .TorsionDriveRecord ):
106- final_molecules = record .get_final_molecules ()
107- final_results = record .get_final_results ()
149+ def get_graph (collection , record_name , spec_name = "default" ):
150+ """
151+ Processes the qcportal data for a given record name.
152+
153+ This supports optimization and singlepoint datasets.
154+
155+ Parameters
156+ ----------
157+ collection, qcportal dataset, required
158+ The instance of the qcportal dataset
159+ record_name, str, required
160+ The name of a give record
161+ spec_name, str, default="default"
162+ Retrieve data for a given qcportal specification.
163+ Returns
164+ -------
165+ Graph
166+ """
167+ # get record and trajectory
168+ record = collection .get_record (record_name , specification_name = spec_name )
169+ entry = collection .get_entry (record_name )
170+
171+ g = process_record (record , entry )
172+
173+ return g
174+
108175
109- angle_keys = list (final_molecules .keys ())
176+ def get_graphs (collection , record_names , spec_name = "default" ):
177+ """
178+ Processes the qcportal data for a given set of record names.
179+ This uses the qcportal iteration functions which are faster than processing
180+ records one at a time.
181+
182+ This supports optimization and singlepoint datasets.
183+
184+
185+ Parameters
186+ ----------
187+ collection, qcportal dataset, required
188+ The instance of the qcportal dataset
189+ record_name, List[str], required
190+ A list of the record_names of a give record
191+ spec_name, str, default="default"
192+ Retrieve data for a given qcportal specification.
193+ Returns
194+ -------
195+ list(graph)
196+ Returns a list of the corresponding graph for each record name
197+ """
198+ g_list = []
199+ for record , entry in zip (
200+ collection .iterate_records (record_names , specification_names = [spec_name ]),
201+ collection .iterate_entries (record_names ),
202+ ):
203+ # note iterate records returns a tuple of length 3 (name, spec_name, actual record information)
204+
205+ g = process_record (record [2 ], entry )
206+ g_list .append (g )
207+
208+ return g_list
209+
210+
211+ def fetch_td_record (record : qcportal .torsiondrive .record_models .TorsiondriveRecord ):
212+ """
213+ Fetches configuration, energy, and gradients for a given torsiondrive record as a function of different angles.
214+
215+ Parameters
216+ ----------
217+ record: qcportal.torsiondrive.record_models.TorsiondriveRecord, required
218+ Torsiondrive record of interest
219+ Returns
220+ -------
221+ tuple, ( numpy.array, numpy.array, numpy.array,numpy.array)
222+ Returned data is a tuple of numpy arrays.
223+ The first index contains angles and subsequent arrays represent
224+ molecule coordinate, energy and gradients associated with each angle.
225+
226+ """
227+ molecule_optimization = record .optimizations
228+
229+ angle_keys = list (molecule_optimization .keys ())
110230
111231 xyzs = []
112232 energies = []
113233 gradients = []
114234
115235 for angle in angle_keys :
116- result = final_results [angle ]
117- mol = final_molecules [angle ]
236+ # NOTE: this is calling the first index of the optimization array
237+ # this gives the same value as the prior implementation.
238+ # however it seems to be that this contains multiple different initial configurations
239+ # that have been optimized. Should all conformers and energies/gradients be considered?
240+ mol = molecule_optimization [angle ][0 ].final_molecule
241+ result = molecule_optimization [angle ][0 ].trajectory [- 1 ].properties
242+
243+ """Note: force = - gradient"""
244+
245+ # TODO: attach units here? or later?
118246
119- e , g = get_energy_and_gradient (result )
247+ e = result ["current energy" ]
248+ g = np .array (result ["current gradient" ]).reshape (- 1 , 3 )
120249
121250 xyzs .append (mol .geometry )
122251 energies .append (e )
@@ -146,22 +275,6 @@ def fetch_td_record(record: ptl.models.torsiondrive.TorsionDriveRecord):
146275 return flat_angles , xyz_in_order , energies_in_order , gradients_in_order
147276
148277
149- def get_energy_and_gradient (
150- snapshot : ptl .models .records .ResultRecord ,
151- ) -> Tuple [float , np .ndarray ]:
152- """Note: force = - gradient"""
153-
154- # TODO: attach units here? or later?
155-
156- d = snapshot .dict ()
157- qcvars = d ["extras" ]["qcvars" ]
158- energy = qcvars ["CURRENT ENERGY" ]
159- flat_gradient = np .array (qcvars ["CURRENT GRADIENT" ])
160- num_atoms = len (flat_gradient ) // 3
161- gradient = flat_gradient .reshape ((num_atoms , 3 ))
162- return energy , gradient
163-
164-
165278MolWithTargets = namedtuple (
166279 "MolWithTargets" , ["offmol" , "xyz" , "energies" , "gradients" ]
167280)
@@ -184,9 +297,9 @@ def get_smiles(x):
184297 g = esp .Graph (mol_ref )
185298
186299 u_ref = np .concatenate (group ["energies" ].values )
187- u_ref_prime = np .concatenate (
188- group [ "gradients" ]. values , axis = 0
189- ). transpose ( 1 , 0 , 2 )
300+ u_ref_prime = np .concatenate (group [ "gradients" ]. values , axis = 0 ). transpose (
301+ 1 , 0 , 2
302+ )
190303 xyz = np .concatenate (group ["xyz" ].values , axis = 0 ).transpose (1 , 0 , 2 )
191304
192305 assert u_ref_prime .shape [0 ] == xyz .shape [0 ] == mol_ref .n_atoms
@@ -229,7 +342,7 @@ def breakdown_along_time_axis(g, batch_size=32):
229342
230343 shuffle (idxs )
231344 chunks = [
232- idxs [_idx * batch_size : (_idx + 1 ) * batch_size ]
345+ idxs [_idx * batch_size : (_idx + 1 ) * batch_size ]
233346 for _idx in range (n_snapshots // batch_size )
234347 ]
235348
@@ -259,10 +372,7 @@ def make_batch_size_consistent(ds, batch_size=32):
259372 return esp .data .dataset .GraphDataset (
260373 list (
261374 itertools .chain .from_iterable (
262- [
263- breakdown_along_time_axis (g , batch_size = batch_size )
264- for g in ds
265- ]
375+ [breakdown_along_time_axis (g , batch_size = batch_size ) for g in ds ]
266376 )
267377 )
268378 )
0 commit comments