Skip to content
713 changes: 507 additions & 206 deletions docs/source/user_guide/CCS.ipynb

Large diffs are not rendered by default.

19 changes: 7 additions & 12 deletions isicle/conformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,13 +398,10 @@ def _check_attributes(self, attr):
If all members do not have `attr`.

'''

value = [x.get___dict__() for x in self]
for key in safelist(attr):
if not all(key in x for x in value):
raise AttributeError('"{}" not found for all conformational '
'ensemble members.'.format(attr))
value = [x.get(key) for x in value]

if not all(hasattr(x, attr) for x in self):
raise AttributeError('"{}" not found for all conformational '
'ensemble members.'.format(attr))

def reduce(self, attr, func='boltzmann', **kwargs):
'''
Expand Down Expand Up @@ -438,9 +435,7 @@ def reduce(self, attr, func='boltzmann', **kwargs):
self._check_attributes('energy')

# Extract (possibly nested) value attribute
value = [x.get___dict__() for x in self]
for key in safelist(attr):
value = [x.get(key) for x in value]
value = [getattr(x, attr) for x in self]

# Check nested values
if isinstance(value[0], dict):
Expand All @@ -463,7 +458,7 @@ def reduce(self, attr, func='boltzmann', **kwargs):
value = np.array([x['mean'] for x in value]).flatten()

else:
value = np.array([x[attr] for x in value]).flatten()
value = np.array([getattr(x, attr) for x in value]).flatten()

# Not nested
else:
Expand All @@ -474,7 +469,7 @@ def reduce(self, attr, func='boltzmann', **kwargs):
# Extract energy attribute
if _energy_based(f):
energy = np.array(
[np.repeat(x.get___dict__()['energy'], pad) for x in self])
[np.repeat(getattr(x, 'energy'), pad) for x in self])
energy = energy.flatten()

# Exectue energy-based method
Expand Down
18 changes: 17 additions & 1 deletion isicle/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ class Geometry(GeometryInterface):
"_frequency",
"_molden",
"_charge",
"_connectivity"
"_connectivity",
"_formal_charge",
"_ccs",
]
_default_value = None

Expand Down Expand Up @@ -154,6 +156,20 @@ def formal_charge(self):

return Chem.rdmolops.GetFormalCharge(self.to_mol())

@property
def ccs(self):
"""
Get CCS of the molecule.

Returns
-------
float
Collision cross section.

"""

return self._ccs

def view(self):
"""
View internal rdkit mol object representation.
Expand Down
5 changes: 0 additions & 5 deletions isicle/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ def parse(self):
"""Extract relevant information from data"""
raise NotImplementedError

@abc.abstractmethod
def save(self, path: str):
"""Write parsed object to file"""
raise NotImplementedError


class XYZGeometryInterface(metaclass=abc.ABCMeta):

Expand Down
76 changes: 37 additions & 39 deletions isicle/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from collections import defaultdict

from rdkit import Chem
from rdkit.Chem import rdDetermineBonds, rdDistGeom
from rdkit.Chem import rdDistGeom

import isicle
from isicle.geometry import Geometry
Expand All @@ -16,43 +16,43 @@
"""


def _program_selector(program):
def _backend_selector(backend):
"""
Selects a supported molecular dynamics program for associated simulation.
Selects a supported molecular dynamics backend for associated simulation.
Currently only NWChem has been implemented.

Parameters
----------
program : str
Alias for program selection (xtb).
backend : str
Alias for backend selection (xtb).

Returns
-------
program
Wrapped functionality of the selected program. Must implement
backend
Wrapped functionality of the selected backend. Must implement
:class:`~isicle.interfaces.WrapperInterface`.

"""

program_map = {"xtb": XTBWrapper, "rdkit": RDKitWrapper}
backend_map = {"xtb": XTBWrapper, "rdkit": RDKitWrapper}

if program.lower() in program_map.keys():
return program_map[program.lower()]()
if backend.lower() in backend_map.keys():
return backend_map[backend.lower()]()
else:
raise ValueError(
"{} not a supported molecular dynamics program.".format(program)
"{} not a supported molecular dynamics backend.".format(backend)
)


def md(geom, program="xtb", **kwargs):
def md(geom, backend="xtb", **kwargs):
"""
Optimize geometry via molecular dyanmics using supplied forcefield
and basis set.

Parameters
----------
program : str
Alias for program selection (xtb).
backend : str
Alias for backend selection (xtb).

Returns
-------
Expand All @@ -61,8 +61,8 @@ def md(geom, program="xtb", **kwargs):

"""

# Select program
return _program_selector(program).run(geom, **kwargs)
# Select backend
return _backend_selector(backend).run(geom, **kwargs)


class XTBWrapper(WrapperInterface):
Expand All @@ -75,25 +75,19 @@ class XTBWrapper(WrapperInterface):
----------
temp_dir : str
Path to temporary directory used for simulation.
task_map : dict
Alias mapper for supported molecular dynamic presets. Includes
"optimize", "crest", "nmr", "protonate", "deprotonate", and "tautomer".
geom : :obj:`isicle.geometry.Geometry`
Internal molecule representation.
fmt : str
File extension indicator.
job_list : str
List of commands for simulation.
result : dict
Dictionary containing simulation results.

"""

_defaults = ["geom", "result"]
_defaults = ["geom", "result", "temp_dir"]
_default_value = None

def __init__(self, **kwargs):
self.temp_dir = isicle.utils.mkdtemp()
def __init__(self):
self.__dict__.update(dict.fromkeys(self._defaults, self._default_value))
self.__dict__.update(**kwargs)
self.temp_dir = isicle.utils.mkdtemp()

def set_geometry(self, geom):
"""
Expand Down Expand Up @@ -124,13 +118,13 @@ def save_geometry(self, fmt="xyz"):

"""
# Path operations
self.fmt = fmt.lower()
self._fmt = fmt.lower()
geomfile = os.path.join(
self.temp_dir, "{}.{}".format(self.geom.basename, self.fmt.lower())
self.temp_dir, "{}.{}".format(self.geom.basename, self._fmt.lower())
)

# All other formats
isicle.io.save(geomfile, self.geom)
isicle.save(geomfile, self.geom)
self.geom.path = geomfile

def _configure_xtb(self, forcefield="gfn2", optlevel="normal", solvation=None):
Expand All @@ -154,7 +148,7 @@ def _configure_xtb(self, forcefield="gfn2", optlevel="normal", solvation=None):
s = "xtb "

# Add geometry
s += "{}.{}".format(self.geom.basename, self.fmt.lower())
s += "{}.{}".format(self.geom.basename, self._fmt.lower())

# Add optimize tag
s += " --opt " + optlevel + " "
Expand Down Expand Up @@ -226,7 +220,7 @@ def _configure_crest(
# Add geometry
s += str(
os.path.join(
self.temp_dir, "{}.{}".format(self.geom.basename, self.fmt.lower())
self.temp_dir, "{}.{}".format(self.geom.basename, self._fmt.lower())
)
)

Expand Down Expand Up @@ -354,17 +348,17 @@ def configure(
"Task not assigned properly, please choose optimize, conformer, protonate, deprotonate, or tautomerize"
)

self.task = task
self._task = task

self.config = config
self._config = config

def submit(self):
"""
Run xtb or crest simulation according to configured inputs.
"""
cwd = os.getcwd()
os.chdir(self.temp_dir)
subprocess.call(self.config, shell=True)
subprocess.call(self._config, shell=True)
os.chdir(cwd)

def finish(self):
Expand Down Expand Up @@ -560,18 +554,19 @@ class RDKitWrapper(Geometry, WrapperInterface):
----------
geom : :obj:`isicle.geometry.Geometry`
Internal molecule representation.
method: str
method : str
Method of RDKit conformer generation specified.
numConfs: int
numConfs : int
The number of conformers to generate.
result : dict
Dictionary containing simulation results.
"""

_defaults = ["geom", "method", "numConfs", "result"]
_default_value = None

def __init__(self, **kwargs):
def __init__(self):
self.__dict__.update(dict.fromkeys(self._defaults, self._default_value))
self.__dict__.update(**kwargs)

def set_geometry(self, geom):
"""
Expand Down Expand Up @@ -704,6 +699,9 @@ def submit(self):
raise ValueError(
"Failure to run RDKit MD, method and/or variant not recognized"
)

def parse(self):
print("No need to parse RDKit result. Simply access `result` attribute.")

def finish(self):
"""
Expand Down
Loading
Loading