# Copyright 2016 Autodesk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import imp
import numpy as np
import moldesign as mdt
from moldesign import units as u
from moldesign import compute
from . import openmm as opm
try:
imp.find_module('pdbfixer')
except (ImportError, OSError) as exc:
print 'PDBFixer could not be imported; using remote docker container'
force_remote = True
else:
force_remote = False
@compute.runsremotely(enable=force_remote)
[docs]def add_hydrogen(mol, pH=7.4):
fixer = _get_fixer(mol)
fixer.addMissingHydrogens(pH)
return _get_mol(fixer)
@compute.runsremotely(enable=force_remote)
[docs]def mutate(mol, mutationmap):
fixer = _get_fixer(mol)
chain_mutations = {}
for res in mutationmap:
chain_mutations.setdefault(res.chain.pdbname, {})[res] = mutationmap[res]
for chainid, mutations in chain_mutations.iteritems():
mutstrings = ['%s-%d-%s' % (res.resname, res.pdbindex, newname) for res,newname in mutations.iteritems()]
print 'Applying mutations to chain %s: %s' % (chainid, ', '.join(mutstrings))
fixer.applyMutations(mutations, chainid)
return _get_mol(fixer)
@compute.runsremotely(enable=force_remote)
[docs]def add_water(mol, min_box_size=None, padding=None,
ion_concentration=None, neutralize=True,
positive_ion='Na+', negative_ion='Cl-'):
""" Solvate a molecule in a water box with optional ions
Args:
mol (moldesign.Molecule): solute molecule
min_box_size (u.Scalar[length] or u.Vector[length]): size of the water box - either
a vector of x,y,z dimensions, or just a uniform cube length. Either this or
``padding`` (or both) must be passed
padding (u.Scalar[length]): distance to edge of water box from the solute in each dimension
neutralize (bool): add ions to neutralize solute charge (in
addition to specified ion concentration)
positive_ion (str): type of positive ions to add, if needed. Allowed values
(from OpenMM modeller) are Cs, K, Li, Na (the default) and Rb
negative_ion (str): type of negative ions to add, if needed. Allowed values
(from OpenMM modeller) are Cl (the default), Br, F, and I
ion_concentration (float or u.Scalar[molarity]): ionic concentration in addition to
whatever is needed to neutralize the solute. (if float is passed, we assume the
number is Molar)
Returns:
moldesign.Molecule: new Molecule object containing both solvent and solute
"""
import pdbfixer
if padding is None and min_box_size is None:
raise ValueError('Solvate arguments: must pass padding or min_box_size or both.')
# add +s and -s to ion names if not already present
if positive_ion[-1] != '+':
assert positive_ion[-1] != '-'
positive_ion += '+'
if negative_ion[-1] != '-':
assert negative_ion[-1] != '+'
negative_ion += '-'
if ion_concentration is not None:
ion_concentration = u.MdtQuantity(ion_concentration)
if ion_concentration.dimensionless:
ion_concentration *= u.molar
# calculate box size - in each dimension, use the largest of min_box_size or
# the calculated padding
boxsize = np.zeros(3) * u.angstrom
if min_box_size:
boxsize[:] = min_box_size
if padding:
ranges = mol.positions.max(axis=0) - mol.positions.min(axis=0)
for idim, r in enumerate(ranges):
boxsize[idim] = max(boxsize[idim], r+padding)
assert (boxsize >= 0.0).all()
modeller = opm.mol_to_modeller(mol)
# TODO: this is like 10 bad things at once. Should probably submit a
# PR to PDBFixer to make this a public staticmethod instead of a private instancemethod
# Alternatively, PR to create Fixers directly from Topology objs
ff = pdbfixer.PDBFixer.__dict__['_createForceField'](None, modeller.getTopology(), True)
modeller.addSolvent(ff,
boxSize=opm.pint2simtk(boxsize),
positiveIon=positive_ion,
negativeIon=negative_ion,
ionicStrength=opm.pint2simtk(ion_concentration),
neutralize=neutralize)
return opm.topology_to_mol(modeller.getTopology(),
positions=modeller.getPositions(),
name='%s with water box' % mol.name)
def _get_fixer(mol):
import pdbfixer
mol.write('/tmp/tmp.pdb', format='pdb')
fixer = pdbfixer.PDBFixer('/tmp/tmp.pdb')
return fixer
def _get_mol(f):
return opm.topology_to_mol(f.topology, positions=f.positions)