Source code for schrodinger.application.jaguar.neural_network_optimizer
"""
functions for energy minimization using a neural network potential energy surface
"""
import datetime
import json
import os
import time
import requests
from schrodinger import structure
HOST_VAR = "SCHRODINGER_ANI_HOSTNAME"
PROJ_VAR = "SCHRODINGER_ANI_PROJECT"
SUPPORTED_ELEMENTS = ["H", "C", "N", "O"]
MAE_STRING = 'mae_string'
FF_NO_PREOPT = 'b_ml_no_ff_min'
[docs]class UnsupportedElementError(ValueError):
"""
Unsupported element type
"""
[docs]class AniServerError(RuntimeError):
"""
Server is not responding
"""
[docs]class OptimizationFailure(AniServerError):
"""
Optimization failed for an unknown reason
"""
# PaperPusherClient defined below is to avoid the following import:
#from paperpusher_client import PaperPusherClient
class _JobStatus(object):
READY = 'READY'
RUNNING = "RUNNING"
COMPLETED = 'COMPLETED'
FAILED = 'FAILED'
class _PaperPusherClient(object):
def __init__(self, base_url, project):
"""
:type base_url: str
:param base_url: url of host
:type project: str
:param project: project name
"""
self.base_url = base_url
self.project = project
def ping_server(self):
"""
ping the server for health
:return: dict of health status
"""
url = "%s/v1/health" % self.base_url
r = requests.get(url)
return r.json()
def is_running(self):
"""
is there a server responding to requests at the current address
"""
try:
response = self.ping_server()
except (requests.exceptions.ConnectionError,
json.decoder.JSONDecodeError) as e:
is_running = False
else:
is_running = response.get("is_up", False)
return is_running
def add_job(self, kwargs_string, block_for_results=False, timeout=120.0):
"""
:type: kwargs_string, str
:param kwargs_string: keyword arguments sent to host
:type: block_for_results: bool
:param block_for_results: if true poll until status is in [COMPLETED, FAILED]
or until timeout seconds
:type timeout: float
:param timeout: wait this many seconds before returning to caller
if status does not change
:return: job dictionary
"""
url = "%s/v1/job" % self.base_url
payload = {'project': self.project, 'kwargs': kwargs_string}
payload = json.dumps(payload)
r = requests.post(url, data=payload)
r = r.json()
if not block_for_results:
return r
start_time = datetime.datetime.now()
end_time = datetime.datetime.now()
job_id = r['id']
details = r
while (end_time - start_time).seconds < timeout:
details = self.job_details(job_id)
if details['status'] in [_JobStatus.COMPLETED, _JobStatus.FAILED]:
return details
end_time = datetime.datetime.now()
time.sleep(2)
return details
def job_details(self, job_id):
"""
:type job_id: str
:param job_id: id of the job details are requested for
:return: details of job
"""
url = "%s/v1/job/%s" % (self.base_url, job_id)
r = requests.get(url)
return r.json()
def _get_client():
"""
read env variables and return a _PaperPusherClient
if server is not running a AniServerError is raised
if env variables not set a ValueError is raised
"""
try:
hostname = os.environ[HOST_VAR]
project = os.environ[PROJ_VAR]
except KeyError:
raise ValueError(
"%s and %s env variables must be set to use ANI optimization" %
(HOST_VAR, PROJ_VAR))
client = _PaperPusherClient(hostname, project)
if not client.is_running():
raise AniServerError("ANI Server cannot be contacted")
return client
[docs]def nn_optimize(st, ff_preopt=True):
"""
Optimize a structure using the ANI potential energy function.
If atom property s_ff_tors_atoms is present, that natural torsion is constrained
use of this function requires two environment variables to be set
%s and %s define the host and project
name.
If these variables are not defined a ValueError is raised
If they are set but the server is not responding an AniServerError is raised
If unsupported elements are present and the server is responding
an UnsupportedElementError is raised
If the optimization for any other reason we raise a OptimizationFailure
:type st: Structure
:param st: Structure to optimize
""" % (HOST_VAR, PROJ_VAR)
client = _get_client()
# package structure into string, then string into json
if not ff_preopt:
st.property[FF_NO_PREOPT] = True
mae_string = st.writeToString('maestro')
if not ff_preopt:
del st.property[FF_NO_PREOPT]
json_to_send = {MAE_STRING: mae_string}
send_data = json.dumps(json_to_send)
# send data and get response
response = client.add_job(send_data, block_for_results=True)
# even if we don't support this system we send the data
if any(at.element not in SUPPORTED_ELEMENTS for at in st.atom):
raise UnsupportedElementError(
"Cannot optimize structure with unsupported element types")
if response["status"] == _JobStatus.COMPLETED:
# load string from json, then structure from string
json_result = json.loads(response['result'])
mae_string = json_result[MAE_STRING]
st = next(structure.StructureReader.fromString(mae_string))
else:
raise OptimizationFailure("optimization failed with status %s" %
response["status"])
return st