ForceBalance API  1.3
Automated optimization of force fields and empirical potentials
evaluator_io.py
Go to the documentation of this file.
1 """ @package forcebalance.evaluator
2 A target which employs the OpenFF Evaluator framework to compute condensed
3 phase physical properties. Currently only force fields in the OpenFF SMIRNOFF
4 format are supported.
5 
6 author Yudong Qiu, Simon Boothroyd, Lee-Ping Wang
7 @date 06/2019
8 """
9 from __future__ import division, print_function
10 
11 import json
12 import os
13 import re
14 import tempfile
15 
16 import numpy as np
17 from forcebalance.nifty import warn_once, printcool, printcool_dictionary
18 from forcebalance.output import getLogger
19 from forcebalance.target import Target
20 
21 try:
22  from openff.evaluator import unit
23  from openff.evaluator.attributes import UNDEFINED
24  from openff.evaluator.client import EvaluatorClient, ConnectionOptions, RequestOptions
25  from openff.evaluator.datasets import PhysicalPropertyDataSet
26  from openff.evaluator.utils.exceptions import EvaluatorException
27  from openff.evaluator.utils.openmm import openmm_quantity_to_pint
28  from openff.evaluator.utils.serialization import TypedJSONDecoder, TypedJSONEncoder
29  from openff.evaluator.forcefield import ParameterGradientKey
30 except ImportError:
31  warn_once("Note: Failed to import the optional openff.evaluator package. ")
32 
33 try:
34  from openforcefield.typing.engines import smirnoff
35 except ImportError:
36  warn_once("Note: Failed to import the optional openforcefield package. ")
37 
38 logger = getLogger(__name__)
39 
40 
41 class Evaluator_SMIRNOFF(Target):
42  """A custom optimisation target which employs the `openff-evaluator`
43  package to rapidly estimate a collection of condensed phase physical
44  properties at each optimisation epoch."""
45 
46  class OptionsFile:
47  """Represents the set of options that a `Evaluator_SMIRNOFF`
48  target will be run with.
49 
50  Attributes
51  ----------
52  connection_options: openff.evaluator.client.ConnectionOptions
53  The options for how the `evaluator` client should
54  connect to a running server instance.
55  estimation_options: openff.evaluator.client.RequestOptions
56  The options for how properties should be estimated by the
57  `evaluator` (e.g. the uncertainties to which properties
58  should be estimated).
59  data_set_path: str
60  The path to a JSON serialized PhysicalPropertyDataSet which
61  contains those physical properties which will be optimised
62  against.
63  weights: dict of float
64  The weighting of each property which will be optimised against.
65  denominators: dict of str and unit.Quantity
66  The denominators will be used to remove units from the properties
67  and scale their values.
68  polling_interval: float
69  The time interval with which to check whether the evaluator has
70  finished fulfilling the request (in seconds).
71  """
72 
73  def __init__(self):
74 
75  self.connection_options = ConnectionOptions()
76  self.estimation_options = RequestOptions()
77 
78  self.data_set_path = ""
79  self.weights = {}
80  self.denominators = {}
81 
82  self.polling_interval = 600
83 
84  def to_json(self):
85  """Converts this class into a JSON string.
86 
87  Returns
88  -------
89  str
90  The JSON representation of this class.
91  """
92 
93  value = {
94  "connection_options": self.connection_options.__getstate__(),
95  "estimation_options": self.estimation_options.__getstate__(),
96  "data_set_path": self.data_set_path,
97  "weights": {
98  property_name: self.weights[property_name]
99  for property_name in self.weights
100  },
101  "denominators": {
102  property_name: self.denominators[property_name]
103  for property_name in self.denominators
104  },
105  "polling_interval": self.polling_interval
106  }
107 
108  return json.dumps(
109  value,
110  sort_keys=True,
111  indent=4,
112  separators=(",", ": "),
113  cls=TypedJSONEncoder,
114  )
115 
116  @classmethod
117  def from_json(cls, json_source):
118  """Creates this class from a JSON string.
119 
120  Parameters
121  -------
122  json_source: str or file-like object
123  The JSON representation of this class.
124  """
125 
126  if isinstance(json_source, str):
127  with open(json_source, "r") as file:
128  dictionary = json.load(file, cls=TypedJSONDecoder)
129  else:
130  dictionary = json.load(json_source, cls=TypedJSONDecoder)
131 
132  if "polling_interval" not in dictionary:
133  dictionary["polling_interval"] = 600
134 
135  assert (
136  "connection_options" in dictionary
137  and "estimation_options" in dictionary
138  and "data_set_path" in dictionary
139  and "weights" in dictionary
140  and "denominators" in dictionary
141  and "polling_interval" in dictionary
142  )
143 
144  value = cls()
145 
146  value.connection_options = ConnectionOptions()
147  value.connection_options.__setstate__(dictionary["connection_options"])
148 
149  value.estimation_options = RequestOptions()
150  value.estimation_options.__setstate__(dictionary["estimation_options"])
151 
152  value.data_set_path = dictionary["data_set_path"]
153 
154  value.weights = {
155  property_name: dictionary["weights"][property_name]
156  for property_name in dictionary["weights"]
157  }
158  value.denominators = {
159  property_name: dictionary["denominators"][property_name]
160  for property_name in dictionary["denominators"]
161  }
162 
163  value.polling_interval = dictionary["polling_interval"]
164 
165  return value
166 
167  def __init__(self, options, tgt_opts, forcefield):
168 
169  super(Evaluator_SMIRNOFF, self).__init__(options, tgt_opts, forcefield)
170 
171  self._options = None # The options for this target loaded from JSON.
172  self._default_units = (
173  {}
174  ) # The default units to convert each type of property to.
175 
176  self._client = None # The client object which will communicate with an already spun up server.
177 
178  self._reference_data_set = None # The data set of properties to estimate.
179  self._normalised_weights = (
180  None # The normalised weights of the different properties.
181  )
182 
183  # Store a `Future` like object which can be queried for the results of
184  # a property estimation.
185  self._pending_estimate_request = None
186 
187  # Store a mapping between gradient keys and the force balance string representation.
188  self._gradient_key_mappings = {}
189  self._parameter_units = {}
190 
191  # Store a copy of the objective function details from the previous optimisation cycle.
192  self._last_obj_details = {}
193 
194  # Get the filename for the evaluator input file.
195  self.set_option(tgt_opts, "evaluator_input", forceprint=True)
196 
197  # Initialize the target.
198  self._initialize()
199 
200  def _initialize(self):
201  """Initializes the evaluator target from an input json file.
202 
203  1. Reads the user specified input file.
204  2. Creates a `evaluator` client object.
205  3. Loads in a reference experimental data set.
206  4. Assigns and normalises weights for each property.
207  """
208 
209  # Load in the options from a user provided JSON file.
210  print(os.path.join(self.tgtdir, self.evaluator_input))
211  options_file_path = os.path.join(self.tgtdir, self.evaluator_input)
212  self._options = self.OptionsFile.from_json(options_file_path)
213 
214  for property_type, denominator in self._options.denominators.items():
215  self._default_units[property_type] = denominator.units
216 
217  # Attempt to create an evaluator client object using the specified
218  # connection options.
219  self._client = EvaluatorClient(self._options.connection_options)
220 
221  # Load in the experimental data set.
222  data_set_path = os.path.join(self.tgtdir, self._options.data_set_path)
223  self._reference_data_set = PhysicalPropertyDataSet.from_json(data_set_path)
224 
225  if len(self._reference_data_set) == 0:
226 
227  raise ValueError(
228  "The physical property data set to optimise against is empty."
229  )
230 
231  # Print the reference data, and count the number of instances of
232  # each property type.
233  printcool("Loaded experimental data.")
234 
235  property_types = self._reference_data_set.property_types
236 
237  number_of_properties = {
238  x: sum(1 for y in self._reference_data_set.properties_by_type(x))
239  for x in property_types
240  }
241 
242  for substance in self._reference_data_set.substances:
243 
244  dict_for_print = {}
245 
246  for physical_property in self._reference_data_set.properties_by_substance(
247  substance
248  ):
249 
250  property_type = physical_property.__class__.__name__
251 
252  value = physical_property.value.to(self._default_units[property_type])
253  uncertainty = np.nan
254 
255  if physical_property.uncertainty != UNDEFINED:
256 
257  uncertainty = physical_property.uncertainty.to(
258  self._default_units[property_type]
259  )
260 
261  tuple_key = (
262  property_type,
263  physical_property.thermodynamic_state.temperature,
264  physical_property.thermodynamic_state.pressure,
265  )
266 
267  dict_for_print["%s %s-%s" % tuple_key] = "%s+/-%s" % (
268  value,
269  uncertainty,
270  )
271 
273  dict_for_print, title="Reference %s data" % substance.identifier,
274  )
275 
276  # Assign and normalize weights for each phase point (average for now)
277  self._normalised_weights = {}
278 
279  for property_type in self._reference_data_set.property_types:
280 
281  self._normalised_weights[property_type] = (
282  self._options.weights[property_type]
283  / number_of_properties[property_type]
284  )
285 
286  def _parameter_value_from_gradient_key(self, gradient_key):
287  """Extracts the value of the parameter in the current
288  open force field object pointed to by a given
289  `ParameterGradientKey` object.
290 
291  Parameters
292  ----------
293  gradient_key: openff.evaluator.forcefield.ParameterGradientKey
294  The gradient key which points to the parameter of interest.
295 
296  Returns
297  -------
298  unit.Quantity
299  The value of the parameter.
300  bool
301  Returns True if the parameter is a cosmetic one.
302  """
303 
304  parameter_handler = self.FF.openff_forcefield.get_parameter_handler(
305  gradient_key.tag
306  )
307  parameter = parameter_handler.parameters[gradient_key.smirks]
308 
309  attribute_split = re.split(r"(\d+)", gradient_key.attribute)
310  attribute_split = list(filter(None, attribute_split))
311 
312  parameter_attribute = None
313  parameter_value = None
314 
315  if hasattr(parameter, gradient_key.attribute):
316 
317  parameter_attribute = gradient_key.attribute
318  parameter_value = getattr(parameter, parameter_attribute)
319 
320  elif len(attribute_split) == 2:
321 
322  parameter_attribute = attribute_split[0]
323 
324  if hasattr(parameter, parameter_attribute):
325  parameter_index = int(attribute_split[1]) - 1
326 
327  parameter_value_list = getattr(parameter, parameter_attribute)
328  parameter_value = parameter_value_list[parameter_index]
329 
330  is_cosmetic = False
331 
332  if (
333  parameter_attribute is None
334  or parameter_attribute in parameter._cosmetic_attribs
335  ):
336  is_cosmetic = True
337 
338  return openmm_quantity_to_pint(parameter_value), is_cosmetic
339 
340  def _extract_physical_parameter_values(self):
341  """Extracts an array of the values of the physical parameters
342  (which are not cosmetic) from the current `FF.openff_forcefield`
343  object.
344 
345  Returns
346  -------
347  np.ndarray
348  The array of values of shape (len(self._gradient_key_mappings),)
349  """
350 
351  parameter_values = np.zeros(len(self._gradient_key_mappings))
352 
353  for gradient_key, parameter_index in self._gradient_key_mappings.items():
354 
355  parameter_value, _ = self._parameter_value_from_gradient_key(gradient_key)
356  expected_unit = self._parameter_units[gradient_key]
357 
358  parameter_values[parameter_index] = parameter_value.to(
359  expected_unit
360  ).magnitude
361 
362  return parameter_values
363 
364  def _build_pvals_jacobian(self, mvals, perturbation_amount=1.0e-4):
365  """Build the matrix which maps the gradients of properties with
366  respect to physical parameters to gradients with respect to
367  force balance mathematical parameters.
368 
369  Parameters
370  ----------
371  mvals: np.ndarray
372  The current force balance mathematical parameters.
373  perturbation_amount: float
374  The amount to perturb the mathematical parameters by
375  when calculating the finite difference gradients.
376 
377  Returns
378  -------
379  np.ndarray
380  A matrix of d(Physical Parameter)/d(Mathematical Parameter).
381  """
382 
383  jacobian_list = []
384 
385  for index in range(len(mvals)):
386 
387  reverse_mvals = mvals.copy()
388  reverse_mvals[index] -= perturbation_amount
389 
390  self.FF.make(reverse_mvals)
391  reverse_physical_values = self._extract_physical_parameter_values()
392 
393  forward_mvals = mvals.copy()
394  forward_mvals[index] += perturbation_amount
395 
396  self.FF.make(forward_mvals)
397  forward_physical_values = self._extract_physical_parameter_values()
398 
399  gradients = (forward_physical_values - reverse_physical_values) / (
400  2.0 * perturbation_amount
401  )
402  jacobian_list.append(gradients)
403 
404  # Make sure to restore the FF object back to its original state.
405  self.FF.make(mvals)
406 
407  jacobian = np.array(jacobian_list)
408  return jacobian
409 
410  def submit_jobs(self, mvals, AGrad=True, AHess=True):
411  """
412  Submit jobs for evaluating the objective function
413 
414  Parameters
415  ----------
416  mvals: np.ndarray
417  mvals array containing the math values of the parameters
418  AGrad: bool
419  Flag for computing gradients of not
420  AHess: bool
421  Flag for computing hessian or not
422 
423  Notes
424  -----
425  1. This function is called before wq_complete() and get().
426  2. This function should not block.
427  """
428 
429  # Make the force field based on the current values of the parameters.
430  self.FF.make(mvals)
431 
432  force_field = smirnoff.ForceField(
433  self.FF.offxml, allow_cosmetic_attributes=True
434  )
435 
436  # strip out cosmetic attributes
437  with tempfile.NamedTemporaryFile(mode="w", suffix=".offxml") as file:
438  force_field.to_file(file.name, discard_cosmetic_attributes=True)
439  force_field = smirnoff.ForceField(file.name)
440 
441  # Determine which gradients (if any) we should be estimating.
442  parameter_gradient_keys = []
443 
444  self._gradient_key_mappings = {}
445  self._parameter_units = {}
446 
447  if AGrad is True:
448 
449  index_counter = 0
450 
451  for field_list in self.FF.pfields:
452 
453  string_key = field_list[0]
454  key_split = string_key.split("/")
455 
456  parameter_tag = key_split[0].strip()
457  parameter_smirks = key_split[3].strip()
458  parameter_attribute = key_split[2].strip()
459 
460  # Use the full attribute name (e.g. k1) for the gradient key.
461  parameter_gradient_key = ParameterGradientKey(
462  tag=parameter_tag,
463  smirks=parameter_smirks,
464  attribute=parameter_attribute,
465  )
466 
467  # Find the unit of the gradient parameter.
468  parameter_value, is_cosmetic = self._parameter_value_from_gradient_key(
469  parameter_gradient_key
470  )
471 
472  if parameter_value is None or is_cosmetic:
473  # We don't wan't gradients w.r.t. cosmetic parameters.
474  continue
475 
476  parameter_unit = parameter_value.units
477  parameter_gradient_keys.append(parameter_gradient_key)
478 
479  self._gradient_key_mappings[parameter_gradient_key] = index_counter
480  self._parameter_units[parameter_gradient_key] = parameter_unit
481 
482  index_counter += 1
483 
484  # Submit the estimation request.
485  self._pending_estimate_request, _ = self._client.request_estimate(
486  property_set=self._reference_data_set,
487  force_field_source=force_field,
488  options=self._options.estimation_options,
489  parameter_gradient_keys=parameter_gradient_keys,
490  )
491 
492  logger.info(
493  "Requesting the estimation of {} properties, and their "
494  "gradients with respect to {} parameters.\n".format(
495  len(self._reference_data_set), len(parameter_gradient_keys)
496  )
497  )
498 
499  if (
500  self._pending_estimate_request.results(
501  True, polling_interval=self._options.polling_interval
502  )[0] is None
503  ):
504 
505  raise RuntimeError(
506  "No `EvaluatorServer` could be found to submit the calculations to. "
507  "Please double check that a server is running, and that the connection "
508  "settings specified in the input script are correct."
509  )
510 
511  @staticmethod
512  def _check_estimation_request(estimation_request):
513  """Checks whether an estimation request has finished with any exceptions.
514 
515  Parameters
516  ----------
517  estimation_request: openff.evaluator.client.Request
518  The request to check.
519  """
520  results, _ = estimation_request.results()
521 
522  if results is None:
523  raise ValueError("Trying to extract the results of an unfinished request.")
524 
525  # Check for any exceptions that were raised while estimating
526  # the properties.
527  if isinstance(results, EvaluatorException):
528 
529  raise ValueError(
530  "An uncaught exception occured within the evaluator "
531  "framework: %s" % str(results)
532  )
533 
534  if len(results.unsuccessful_properties) > 0:
535 
536  exceptions = "\n".join(str(result) for result in results.exceptions)
537 
538  raise ValueError(
539  "Some properties could not be estimated:\n\n%s." % exceptions
540  )
541 
542  def _extract_property_data(self, estimation_request, mvals, AGrad):
543  """Extract the property estimates #and their gradients#
544  from a relevant evaluator request object.
545 
546  Parameters
547  ----------
548  estimation_request: openff.evaluator.client.Request
549  The request to extract the data from.
550 
551  Returns
552  -------
553  estimated_data: openff.evaluator.datasets.PhysicalPropertyDataSet
554  The data set of estimated properties.
555  estimated_gradients: dict of str and np.array
556  The estimated gradients in a dictionary with keys of the estimated
557  properties unique ids, and values of the properties gradients of shape
558  (n_params,).
559  """
560  # Make sure the request actually finished and was error free.
561  Evaluator_SMIRNOFF._check_estimation_request(estimation_request)
562 
563  # Extract the results from the request.
564  results, _ = estimation_request.results()
565 
566  # Save a copy of the results to the temporary directory
567  results_path = os.path.join(self.root, self.rundir, "results.json")
568  results.json(results_path)
569 
570  # Print out some statistics about the calculation
571  calculation_layer_counts = {}
572 
573  for physical_property in results.estimated_properties:
574 
575  calculation_layer = physical_property.source.fidelity
576 
577  if calculation_layer not in calculation_layer_counts:
578  calculation_layer_counts[calculation_layer] = 0
579 
580  calculation_layer_counts[calculation_layer] += 1
581 
582  logger.info("\n")
583 
584  for layer_type in calculation_layer_counts:
585 
586  count = calculation_layer_counts[layer_type]
587 
588  logger.info(
589  "{} properties were estimated using the {} layer.\n".format(
590  count, layer_type
591  )
592  )
593 
594  logger.info("\n")
595 
596  if len(results.exceptions) > 0:
597 
598  exceptions = "\n\n".join(str(result) for result in results.exceptions)
599  exceptions = exceptions.replace("\\n", "\n")
600 
601  # In some cases, an exception will be raised when executing a property but
602  # it will not stop the property from being estimated (e.g an error occured
603  # while reweighting so a simulation was used to estimate the property
604  # instead).
605  exceptions_path = os.path.join(
606  self.root, self.rundir, "non_fatal_exceptions.txt"
607  )
608 
609  with open(exceptions_path, "w") as file:
610  file.write(exceptions)
611 
612  logger.warning(
613  "A number of non-fatal exceptions occurred. These were saved to "
614  "the %s file." % exceptions_path
615  )
616 
617  estimated_gradients = {}
618 
619  if AGrad is False:
620  return results.estimated_properties, estimated_gradients
621 
622  jacobian = self._build_pvals_jacobian(mvals)
623 
624  # The below snippet will extract any evaluated gradients
625  # and map them from gradients with respect to FF parameters,
626  # to gradients with respect to FB mathematical parameters.
627  for physical_property in results.estimated_properties:
628 
629  property_class = physical_property.__class__.__name__
630 
631  estimated_gradients[physical_property.id] = np.zeros(
632  len(self._gradient_key_mappings)
633  )
634 
635  for gradient in physical_property.gradients:
636 
637  parameter_index = self._gradient_key_mappings[gradient.key]
638 
639  gradient_unit = (
640  self._default_units[property_class]
641  / self._parameter_units[gradient.key]
642  )
643 
644  if isinstance(gradient.value, unit.Quantity):
645  gradient_value = gradient.value.to(gradient_unit).magnitude
646  else:
647  gradient_value = gradient.value
648  assert isinstance(gradient_value, float)
649 
650  estimated_gradients[physical_property.id][
651  parameter_index
652  ] = gradient_value
653 
654  for property_id in estimated_gradients:
655 
656  pval_gradients = estimated_gradients[property_id]
657  mval_gradients = np.matmul(jacobian, pval_gradients)
658 
659  estimated_gradients[property_id] = mval_gradients
660 
661  return results.estimated_properties, estimated_gradients
662 
663  def wq_complete(self):
664  """
665  Check if all jobs are finished
666  This function should have a sleep in it if not finished.
667 
668  Returns
669  -------
670  finished: bool
671  True if all jobs are finished, False if not
672  """
673 
674  estimation_results, _ = self._pending_estimate_request.results()
675 
676  return (
677  isinstance(estimation_results, EvaluatorException)
678  or len(estimation_results.queued_properties) == 0
679  )
680 
681  def get(self, mvals, AGrad=True, AHess=True):
682  """
683  Get the objective function value, gradient, and hessian
684 
685  Parameters
686  ----------
687  mvals: np.ndarray
688  mvals array containing the math values of the parameters
689  AGrad: bool
690  Flag for computing gradients of not
691  AHess: bool
692  Flag for computing hessian or not
693 
694  Returns
695  -------
696  Answer: dict
697  Answer = {'X':obj_value, 'G':obj_grad, 'H':obj_hess}
698  obj_value: float
699  obj_grad: np.ndarray of shape (n_param, )
700  obj_hess: np.ndarray of shape (n_param, n_param)
701 
702  Notes
703  -----
704  1. obj_grad is all zero when AGrad == False
705  2. obj_hess is all zero when AHess == False or AGrad == False, because the
706  hessian estimate depends on gradients
707  """
708 
709  # Ensure the input flags are actual booleans.
710  AGrad = bool(AGrad)
711  AHess = bool(AHess)
712 
713  # Extract the properties estimated using the unperturbed parameters.
714  estimated_data_set, estimated_gradients = self._extract_property_data(
715  self._pending_estimate_request, mvals, AGrad
716  )
717 
718  # compute objective value
719  obj_value = 0.0
720  obj_grad = np.zeros(self.FF.np)
721  obj_hess = np.zeros((self.FF.np, self.FF.np))
722 
723  # store details for printing
724  self._last_obj_details = {}
725 
726  for property_type in self._reference_data_set.property_types:
727 
728  self._last_obj_details[property_type] = []
729 
730  denominator = (
731  self._options.denominators[property_type]
732  .to(self._default_units[property_type])
733  .magnitude
734  )
735 
736  weight = self._normalised_weights[property_type]
737 
738  for reference_property in self._reference_data_set.properties_by_type(
739  property_type
740  ):
741 
742  reference_value = reference_property.value.to(
743  self._default_units[property_type]
744  ).magnitude
745 
746  target_property = next(
747  x for x in estimated_data_set if x.id == reference_property.id
748  )
749  target_value = target_property.value.to(
750  self._default_units[property_type]
751  ).magnitude
752 
753  target_error = np.nan
754 
755  if target_property.uncertainty != UNDEFINED:
756 
757  target_error = target_property.uncertainty.to(
758  self._default_units[property_type]
759  ).magnitude
760 
761  diff = target_value - reference_value
762 
763  obj_contrib = weight * (diff / denominator) ** 2
764  obj_value += obj_contrib
765 
766  temperature = reference_property.thermodynamic_state.temperature
767  pressure = reference_property.thermodynamic_state.pressure
768 
769  self._last_obj_details[property_type].append(
770  (
771  temperature.to(unit.kelvin),
772  pressure.to(unit.atmosphere),
773  target_property.substance.identifier,
774  reference_value,
775  target_value,
776  target_error,
777  diff,
778  weight,
779  denominator,
780  obj_contrib,
781  )
782  )
783 
784  # compute objective gradient
785  if AGrad is True:
786 
787  # get gradients in physical unit
788  grad_array = estimated_gradients[reference_property.id]
789  # compute objective gradient
790  obj_grad += 2.0 * weight * diff * grad_array / denominator ** 2
791 
792  if AHess is True:
793  obj_hess += (
794  2.0
795  * weight
796  * (np.outer(grad_array, grad_array))
797  / denominator ** 2
798  )
799 
800  return {"X": obj_value, "G": obj_grad, "H": obj_hess}
801 
802  def indicate(self):
803  """
804  print information into the output file about the last objective function evaluated
805  This function should be called after get()
806  """
807  for property_name, details in self._last_obj_details.items():
808  dict_for_print = {
809  " %s %s %s"
810  % detail[:3]: "%9.3f %14.3f +- %-7.3f % 7.3f % 9.5f % 9.5f % 9.5f "
811  % detail[3:]
812  for detail in details
813  }
814  title = (
815  "%s %s\nTemperature Pressure Substance Reference Calculated +- "
816  "Stdev Delta Weight Denom Term " % (self.name, property_name)
817  )
819  dict_for_print, title=title, bold=True, color=4, keywidth=15
820  )
Nifty functions, intended to be imported by any module within ForceBalance.
def _build_pvals_jacobian(self, mvals, perturbation_amount=1.0e-4)
Build the matrix which maps the gradients of properties with respect to physical parameters to gradie...
def wq_complete(self)
Check if all jobs are finished This function should have a sleep in it if not finished.
def submit_jobs(self, mvals, AGrad=True, AHess=True)
Submit jobs for evaluating the objective function.
def __init__(self, options, tgt_opts, forcefield)
def _initialize(self)
Initializes the evaluator target from an input json file.
Represents the set of options that a Evaluator_SMIRNOFF target will be run with.
Definition: evaluator_io.py:74
def to_json(self)
Converts this class into a JSON string.
Definition: evaluator_io.py:95
def get(self, mvals, AGrad=True, AHess=True)
Get the objective function value, gradient, and hessian.
def printcool_dictionary(Dict, title="Dictionary Keys : Values", bold=False, color=2, keywidth=25, topwidth=50, center=True, leftpad=0)
See documentation for printcool; this is a nice way to print out keys/values in a dictionary...
Definition: nifty.py:366
A custom optimisation target which employs the openff-evaluator package to rapidly estimate a collect...
Definition: evaluator_io.py:46
def printcool(text, sym="#", bold=False, color=2, ansi=None, bottom='-', minwidth=50, center=True, sym2="=")
Cool-looking printout for slick formatting of output.
Definition: nifty.py:321
def _extract_physical_parameter_values(self)
Extracts an array of the values of the physical parameters (which are not cosmetic) from the current ...
def indicate(self)
print information into the output file about the last objective function evaluated This function shou...
def warn_once(warning, warnhash=None)
Prints a warning but will only do so once in a given run.
Definition: nifty.py:1611
def _extract_property_data(self, estimation_request, mvals, AGrad)
Extract the property estimates #and their gradients# from a relevant evaluator request object...
def from_json(cls, json_source)
Creates this class from a JSON string.
def _parameter_value_from_gradient_key(self, gradient_key)
Extracts the value of the parameter in the current open force field object pointed to by a given Para...