diff --git a/CAT/__version__.py b/CAT/__version__.py index e4e49b3b..8969d496 100644 --- a/CAT/__version__.py +++ b/CAT/__version__.py @@ -1 +1 @@ -__version__ = '0.9.0' +__version__ = '0.9.1' diff --git a/CAT/base.py b/CAT/base.py index 66920e21..fef47ff2 100644 --- a/CAT/base.py +++ b/CAT/base.py @@ -60,16 +60,17 @@ from nanoCAT.bde.bde_workflow import init_bde from nanoCAT.ligand_solvation import init_solv from nanoCAT.ff.ff_assignment import init_ff_assignment + from nanoCAT.cdft import init_cdft NANO_CAT: Optional[ImportError] = None except ImportError as ex: - NANO_CAT: Optional[ImportError] = ex + NANO_CAT = ex try: import dataCAT DATA_CAT: Optional[ImportError] = None except ImportError as ex: - DATA_CAT: Optional[ImportError] = ex + DATA_CAT = ex __all__ = ['prep'] @@ -261,6 +262,7 @@ def prep_ligand(ligand_df: SettingsDataFrame) -> SettingsDataFrame: * Ligand geometry optimization * Ligand bulkiness calculations * Ligand COSMO-RS calculations + * Ligand conceptual DFT calculations .. _Nano-CAT: https://github.com/nlesc-nano/nano-CAT @@ -284,6 +286,7 @@ def prep_ligand(ligand_df: SettingsDataFrame) -> SettingsDataFrame: forcefield = ligand_df.settings.optional.forcefield optimize = ligand_df.settings.optional.ligand.optimize crs = ligand_df.settings.optional.ligand.crs + cdft = ligand_df.settings.optional.ligand.cdft # Identify functional groups within the ligand. ligand_df = init_ligand_anchoring(ligand_df) @@ -310,6 +313,11 @@ def prep_ligand(ligand_df: SettingsDataFrame) -> SettingsDataFrame: "(Multipurpose Atom-Typer for CHARMM) and the nano-CAT package") init_ff_assignment(ligand_df) + # Run conceptual DFT calculations + if cdft: + val_nano_cat("Ligand conceptual DFT calculations require the nano-CAT package") + init_cdft(ligand_df) + return ligand_df diff --git a/CAT/data_handling/validate_input.py b/CAT/data_handling/validate_input.py index ccb9b9a8..cb1003fe 100644 --- a/CAT/data_handling/validate_input.py +++ b/CAT/data_handling/validate_input.py @@ -33,7 +33,8 @@ asa_schema, ligand_opt_schema, subset_schema, - multi_ligand_schema + multi_ligand_schema, + cdft_schema ) from .validate_ff import validate_ff, update_ff_jobs @@ -111,12 +112,14 @@ def validate_input(s: Settings) -> None: if s.optional.ligand.optimize: s.optional.ligand.optimize = ligand_opt_schema.validate(s.optional.ligand.optimize) + if s.optional.ligand.cdft: + s.optional.ligand.cdft = cdft_schema.validate(s.optional.ligand.cdft) if s.optional.ligand['cosmo-rs']: crs = s.optional.ligand.pop('cosmo-rs') s.optional.ligand.crs = crs_schema.validate(crs) + if s.optional.qd.optimize: s.optional.qd.optimize = qd_opt_schema.validate(s.optional.qd.optimize) - if s.optional.qd.dissociate: s.optional.qd.dissociate = bde_schema.validate(s.optional.qd.dissociate) if s.optional.qd.activation_strain: diff --git a/CAT/data_handling/validation_schemas.py b/CAT/data_handling/validation_schemas.py index d58a25f2..c6b0325d 100644 --- a/CAT/data_handling/validation_schemas.py +++ b/CAT/data_handling/validation_schemas.py @@ -483,6 +483,13 @@ def _get_crsjob() -> type: And(bool, Use(lambda n: {'job1': 'AMSJob'} if n else False)), error='optional.ligand.cosmo-rs expects a boolean or dictionary' ), + + Optional_('cdft', default=False): # Settings specific to ligand conceptual dft calculations + Or( + dict, + And(bool, Use(lambda n: {'job1': 'ADFJob'} if n else False)), + error='optional.ligand.cdft expects a boolean or dictionary' + ), }) @@ -962,3 +969,35 @@ def _get_crsjob() -> type: And(val_float, lambda n: 0 <= float(n) <= 1, Use(float)) ) }) + + +#: Schema for validating the ``['optional']['ligand']['cdft']`` block. +cdft_schema: Schema = Schema({ + # Delete files after the calculations are finished + Optional_('keep_files', default=True): + And(bool, error='optional.ligand.cdft.keep_files expects a boolean'), + + # The Job type for the final geometry optimization + Optional_('job1', default=lambda n: ADFJob): + Or( + And( + And(type, lambda n: issubclass(n, Job), Use(val_job_type)), + error=('optional.ligand.cdft.job1 expects a type object ' + 'that is a subclass of plams.Job') + ), + And( + str, Use(str_to_job_type), + error=('optional.ligand.cdft.job1 expects a string ' + 'that is a valid plams.Job alias') + ), + ), + + # The Job Settings for the final geometry optimization + Optional_('s2', default=Settings): + Or( + None, + dict, + And(str, Use(lambda n: get_template(n, from_cat_data=False))), + error='optional.ligand.cdft.s1 expects a string or a dictionary' + ), +}) diff --git a/CAT/jobs.py b/CAT/jobs.py index c682e87f..4f46a250 100644 --- a/CAT/jobs.py +++ b/CAT/jobs.py @@ -108,8 +108,11 @@ def pre_process_settings(mol: Molecule, s: Settings, ret.input.ams.system.bondorders._1 = adf_connectivity(mol) if 'uff' not in s.input: ret.input.ams.system.charge = sum( - [at.properties.charge for at in mol if 'charge' in at.properties] + [at.properties.get('charge', 0) for at in mol] ) + elif job_type is ADFJob: + if not s.input.charge: + s.input.charge = int(sum(at.properties.get('charge', 0) for at in mol)) return ret diff --git a/CAT/recipes.py b/CAT/recipes.py index 43db67ab..3c7fc966 100644 --- a/CAT/recipes.py +++ b/CAT/recipes.py @@ -18,8 +18,6 @@ """ -import warnings - try: from nanoCAT import recipes as _recipes from nanoCAT.recipes import * @@ -28,12 +26,15 @@ del _recipes except ImportError as ex: + import warnings as _warnings + __all__ = [] _warning = ImportWarning(str(ex)) _warning.__cause__ = ex - warnings.warn(_warning) + _warnings.warn(_warning) del _warning + del _warnings finally: from . import dye as _dye @@ -41,4 +42,3 @@ __all__ += _dye.__all__ del _dye - del warnings diff --git a/CAT/workflows/__init__.pyi b/CAT/workflows/__init__.pyi index bd325a8c..eafda580 100644 --- a/CAT/workflows/__init__.pyi +++ b/CAT/workflows/__init__.pyi @@ -13,6 +13,22 @@ JOB_SETTINGS_QD_OPT: Tuple[str, str] = ... JOB_SETTINGS_CRS: Tuple[str, str] = ... JOB_SETTINGS_BDE: Tuple[str, str] = ... JOB_SETTINGS_ASA: Tuple[str, str] = ... +JOB_SETTINGS_CDFT: Tuple[str, str] = ... +CDFT_MU: Tuple[str, str] = ... +CDFT_CHI: Tuple[str, str] = ... +CDFT_ETA: Tuple[str, str] = ... +CDFT_S: Tuple[str, str] = ... +CDFT_GAMMA: Tuple[str, str] = ... +CDFT_OMEGA: Tuple[str, str] = ... +CDFT_NUCLEOFUGE: Tuple[str, str] = ... +CDFT_ELECTROFUGE: Tuple[str, str] = ... +CDFT_W_MINUS: Tuple[str, str] = ... +CDFT_W_PLUS: Tuple[str, str] = ... +CDFT_ELECTROPHILICITY: Tuple[str, str] = ... +CDFT_DELTAF_MINUS: Tuple[str, str] = ... +CDFT_DELTAF_PLUS: Tuple[str, str] = ... +CDFT_MU_MINUS: Tuple[str, str] = ... +CDFT_MU_PLUS: Tuple[str, str] = ... ASA_INT: Tuple[str, str] = ... ASA_STRAIN: Tuple[str, str] = ... ASA_E: Tuple[str, str] = ... @@ -23,4 +39,5 @@ SETTINGS_SOLV2: Tuple[str, str] = ... SETTINGS_ASA: Tuple[str, str] = ... SETTINGS_BDE1: Tuple[str, str] = ... SETTINGS_BDE2: Tuple[str, str] = ... +SETTINGS_CDFT: Tuple[str, str] = ... V_BULK: Tuple[str, str] = ... diff --git a/CAT/workflows/key_map.py b/CAT/workflows/key_map.py index 9eacc0a9..e3fe7de5 100644 --- a/CAT/workflows/key_map.py +++ b/CAT/workflows/key_map.py @@ -30,6 +30,22 @@ 'JOB_SETTINGS_CRS': ('job_settings_crs', ''), 'JOB_SETTINGS_BDE': ('job_settings_BDE', ''), 'JOB_SETTINGS_ASA': ('job_settings_ASA', ''), + 'JOB_SETTINGS_CDFT': ('job_settings_cdft', ''), + 'CDFT_MU': ('cdft', 'Electronic chemical potential (mu)'), + 'CDFT_CHI': ('cdft', 'Electronegativity (chi=-mu)'), + 'CDFT_ETA': ('cdft', 'Hardness (eta)'), + 'CDFT_S': ('cdft', 'Softness (S)'), + 'CDFT_GAMMA': ('cdft', 'Hyperhardness (gamma)'), + 'CDFT_OMEGA': ('cdft', 'Electrophilicity index (w=omega)'), + 'CDFT_NUCLEOFUGE': ('cdft', 'Dissocation energy (nucleofuge)'), + 'CDFT_ELECTROFUGE': ('cdft', 'Dissociation energy (electrofuge)'), + 'CDFT_W_MINUS': ('cdft', 'Electrodonating power (w-)'), + 'CDFT_W_PLUS': ('cdft', 'Electroaccepting power(w+)'), + 'CDFT_ELECTROPHILICITY': ('cdft', 'Net Electrophilicity'), + 'CDFT_DELTAF_MINUS': ('cdft', 'Global Dual Descriptor Deltaf-'), + 'CDFT_DELTAF_PLUS': ('cdft', 'Global Dual Descriptor Deltaf+'), + 'CDFT_MU_MINUS': ('cdft', 'Electronic chemical potential (mu-)'), + 'CDFT_MU_PLUS': ('cdft', 'Electronic chemical potential (mu+)'), 'ASA_INT': ('ASA', 'E_int'), 'ASA_STRAIN': ('ASA', 'E_strain'), 'ASA_E': ('ASA', 'E'), @@ -40,6 +56,7 @@ 'SETTINGS_ASA': ('settings', 'ASA 1'), 'SETTINGS_BDE1': ('settings', 'BDE 1'), 'SETTINGS_BDE2': ('settings', 'BDE 2'), + 'SETTINGS_CDFT': ('settings', 'cdft 1'), 'V_BULK': ('V_bulk', '') }) diff --git a/CAT/workflows/key_map.pyi b/CAT/workflows/key_map.pyi index 792d5e2c..59247efe 100644 --- a/CAT/workflows/key_map.pyi +++ b/CAT/workflows/key_map.pyi @@ -12,6 +12,22 @@ JOB_SETTINGS_QD_OPT: Tuple[str, str] = ... JOB_SETTINGS_CRS: Tuple[str, str] = ... JOB_SETTINGS_BDE: Tuple[str, str] = ... JOB_SETTINGS_ASA: Tuple[str, str] = ... +JOB_SETTINGS_CDFT: Tuple[str, str] = ... +CDFT_MU: Tuple[str, str] = ... +CDFT_CHI: Tuple[str, str] = ... +CDFT_ETA: Tuple[str, str] = ... +CDFT_S: Tuple[str, str] = ... +CDFT_GAMMA: Tuple[str, str] = ... +CDFT_OMEGA: Tuple[str, str] = ... +CDFT_NUCLEOFUGE: Tuple[str, str] = ... +CDFT_ELECTROFUGE: Tuple[str, str] = ... +CDFT_W_MINUS: Tuple[str, str] = ... +CDFT_W_PLUS: Tuple[str, str] = ... +CDFT_ELECTROPHILICITY: Tuple[str, str] = ... +CDFT_DELTAF_MINUS: Tuple[str, str] = ... +CDFT_DELTAF_PLUS: Tuple[str, str] = ... +CDFT_MU_MINUS: Tuple[str, str] = ... +CDFT_MU_PLUS: Tuple[str, str] = ... ASA_INT: Tuple[str, str] = ... ASA_STRAIN: Tuple[str, str] = ... ASA_E: Tuple[str, str] = ... @@ -22,4 +38,5 @@ SETTINGS_SOLV2: Tuple[str, str] = ... SETTINGS_ASA: Tuple[str, str] = ... SETTINGS_BDE1: Tuple[str, str] = ... SETTINGS_BDE2: Tuple[str, str] = ... +SETTINGS_CDFT: Tuple[str, str] = ... V_BULK: Tuple[str, str] = ... diff --git a/CAT/workflows/workflow_dicts.py b/CAT/workflows/workflow_dicts.py index 47c57e7f..1afc1d23 100644 --- a/CAT/workflows/workflow_dicts.py +++ b/CAT/workflows/workflow_dicts.py @@ -21,6 +21,7 @@ JOB_SETTINGS_CRS, JOB_SETTINGS_BDE, JOB_SETTINGS_ASA, + JOB_SETTINGS_CDFT, ASA_INT, ASA_STRAIN, ASA_E, @@ -31,20 +32,33 @@ SETTINGS_ASA, SETTINGS_BDE1, SETTINGS_BDE2, - V_BULK + SETTINGS_CDFT, + V_BULK, + CDFT_MU, + CDFT_CHI, + CDFT_ETA, + CDFT_S, + CDFT_GAMMA, + CDFT_OMEGA, + CDFT_NUCLEOFUGE, + CDFT_ELECTROFUGE, + CDFT_W_MINUS, + CDFT_W_PLUS, + CDFT_ELECTROPHILICITY, + CDFT_DELTAF_MINUS, + CDFT_DELTAF_PLUS, + CDFT_MU_MINUS, + CDFT_MU_PLUS ) if TYPE_CHECKING: - if sys.version_info >= (3, 8): - from typing import TypedDict - else: - from typing_extensions import TypedDict + from nanoutils import TypedDict class _TemplateMapping(TypedDict): description: str mol_type: str template: Mapping[str, Tuple[str, ...]] - import_columns: Mapping[Tuple[str, str], float] + import_columns: Mapping[Tuple[str, str], Any] export_columns: Tuple[Tuple[str, str], ...] else: @@ -89,6 +103,18 @@ def finalize_templates(): 'export_columns': (V_BULK,)}, 'multi_ligand': {'import_columns': {HDF5_INDEX: -1, OPT: False}, 'export_columns': (HDF5_INDEX, OPT)}, + 'cdft': {'import_columns': {CDFT_MU: np.nan, CDFT_CHI: np.nan, CDFT_ETA: np.nan, + CDFT_S: np.nan, CDFT_GAMMA: np.nan, CDFT_OMEGA: np.nan, + CDFT_NUCLEOFUGE: np.nan, CDFT_ELECTROFUGE: np.nan, + CDFT_W_MINUS: np.nan, CDFT_W_PLUS: np.nan, + CDFT_ELECTROPHILICITY: np.nan, CDFT_DELTAF_MINUS: np.nan, + CDFT_DELTAF_PLUS: np.nan, CDFT_MU_MINUS: np.nan, + CDFT_MU_PLUS: np.nan}, + 'export_columns': (JOB_SETTINGS_CDFT, SETTINGS_CDFT, CDFT_MU, CDFT_CHI, CDFT_ETA, + CDFT_S, CDFT_GAMMA, CDFT_OMEGA, CDFT_NUCLEOFUGE, + CDFT_ELECTROFUGE, CDFT_W_MINUS, CDFT_W_PLUS, + CDFT_ELECTROPHILICITY, CDFT_DELTAF_MINUS, CDFT_DELTAF_PLUS, + CDFT_MU_MINUS, CDFT_MU_PLUS)}, } templates = _load_templates() diff --git a/CAT/workflows/workflow_yaml.yaml b/CAT/workflows/workflow_yaml.yaml index ff08575e..c80e6c4a 100644 --- a/CAT/workflows/workflow_yaml.yaml +++ b/CAT/workflows/workflow_yaml.yaml @@ -152,3 +152,17 @@ multi_ligand: cluster_size: [optional, qd, multi_ligand, cluster_size] weight: [optional, qd, multi_ligand, weight] randomness: [optional, qd, multi_ligand, randomness] + +cdft: + description: Conceptual DFT property calculation + mol_type: ligand + template: + db: [optional, database, db] + read: [optional, database, read] + write: [optional, database, write] + overwrite: [optional, database, overwrite] + + path: [optional, ligand, dirname] + keep_files: [optional, ligand, cdft, keep_files] + job1: [optional, ligand, cdft, job1] + s1: [optional, ligand, cdft, s1] diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d7d4bf6e..fc7cd85a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,12 @@ This project adheres to `Semantic Versioning `_. * WiP: Added an option the import pre-built quantum dots. +0.9.1 +***** +* Added a new conceptual DFT (CDFT) workflow to Nano-CAT + (https://github.com/nlesc-nano/nano-CAT/pull/57). + + 0.9.0 ***** * Moved a number of functions to the `nanoutils `_ package. diff --git a/README.rst b/README.rst index c46e7dc9..bec8f6de 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ ############################## -Compound Attachment Tool 0.9.0 +Compound Attachment Tool 0.9.1 ############################## **CAT** is a collection of tools designed for the construction of various chemical compounds. diff --git a/docs/4_optional.rst b/docs/4_optional.rst index 3ad10588..d7c57248 100644 --- a/docs/4_optional.rst +++ b/docs/4_optional.rst @@ -33,6 +33,7 @@ Option Description :attr:`optional.ligand.functional_groups` Manually specify SMILES strings representing functional groups. :attr:`optional.ligand.split` If the ligand should be attached in its entirety to the core or not. :attr:`optional.ligand.cosmo-rs` Perform a property calculation with COSMO-RS on the ligand. +:attr:`optional.ligand.cdft` Perform a conceptual DFT calculation with ADF on the ligand. :attr:`optional.qd.dirname` The name of the directory where all quantum dots will be stored. :attr:`optional.qd.construct_qd` Whether or not the quantum dot should actually be constructed or not. @@ -69,6 +70,7 @@ Default Settings functional_groups: null split: True cosmo-rs: False + cdft: False qd: dirname: qd @@ -546,6 +548,7 @@ Ligand functional_groups: null split: True cosmo-rs: False + cdft: False | @@ -588,7 +591,6 @@ Ligand job2: ADFJob - .. attribute:: optional.ligand.functional_groups :Parameter: * **Type** - :class:`str` or :class:`tuple` [:class:`str`] @@ -612,6 +614,7 @@ Ligand .. note:: The yaml format uses ``null`` rather than ``None`` as in Python. + .. attribute:: optional.ligand.split :Parameter: * **Type** - :class:`bool` @@ -657,6 +660,58 @@ Ligand dimethyl formamide (DMF), dimethyl sulfoxide (DMSO), ethyl acetate, ethanol, *n*-hexane, toluene and water. + + .. attribute:: optional.ligand.cdft + + :Parameter: * **Type** - :class:`bool` or :class:`dict` + * **Default value** – ``False`` + + + Perform a conceptual DFT (CDFT) calculation with `ADF `_ on the ligand. + + All global descriptors are, if installed, stored in the database. + This includes the following properties: + + * Electronic chemical potential (mu) + * Electronic chemical potential (mu+) + * Electronic chemical potential (mu-) + * Electronegativity (chi=-mu) + * Hardness (eta) + * Softness (S) + * Hyperhardness (gamma) + * Electrophilicity index (w=omega) + * Dissocation energy (nucleofuge) + * Dissociation energy (electrofuge) + * Electrodonating power (w-) + * Electroaccepting power(w+) + * Net Electrophilicity + * Global Dual Descriptor Deltaf+ + * Global Dual Descriptor Deltaf- + + This block can be furthermore customized with one or more of the following keys: + + * ``"keep_files"``: Whether or not to delete the ADF output afterwards. + * ``"job1"``: The type of PLAMS Job used for running the calculation. + The only value that should be supplied here (if any) is ``"ADFJob"``. + * ``"s1"``: The job Settings used for running the CDFT calculation. + Can be left blank to use the default template (:data:`nanoCAT.cdft.cdft`). + + .. admonition:: Examples + + .. code:: yaml + + optional: + ligand: + cdft: True + + .. code:: yaml + + optional: + ligand: + cdft: + job1: ADFJob + s1: ... # Insert custom settings here + | QD diff --git a/docs/conf.py b/docs/conf.py index 9841a872..df42e2e4 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,7 +69,7 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the built documents. -release = '0.9.0' # The full version, including alpha/beta/rc tags. +release = '0.9.1' # The full version, including alpha/beta/rc tags. version = release.rsplit('.', maxsplit=1)[0] diff --git a/tests/test_schemas.py b/tests/test_schemas.py index a1b8f464..c2d9c007 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -124,7 +124,8 @@ def test_ligand_schema() -> None: 'functional_groups': None, 'optimize': {'job1': None}, 'split': True, - 'cosmo-rs': False + 'cosmo-rs': False, + 'cdft': False } assertion.eq(ligand_schema.validate(lig_dict), ref) diff --git a/tests/test_validate_input.py b/tests/test_validate_input.py index 5dd77708..bed2b60e 100644 --- a/tests/test_validate_input.py +++ b/tests/test_validate_input.py @@ -45,6 +45,7 @@ def test_validate_input() -> None: ref.ligand.optimize = {'job1': None, 'job2': None, 's1': None, 's2': Settings(), 'use_ff': False, 'keep_files': True} ref.ligand.split = True + ref.ligand.cdft = False ref.qd.bulkiness = False ref.qd.construct_qd = True